一步一步学OAK之二: RGB相机控制

今天我们来实现 RGB相机的控制程序,用来控制彩色相机的曝光、灵敏度、白平衡、亮度/色度降噪、 设备端裁剪、相机触发器等。

目录

  • Setup 1: 创建文件
  • Setup 2: 安装依赖
  • Setup 3: 导入需要的包
  • Setup 4: 全局变量
  • Setup 5: 定义clamp函数
  • Setup 6: 创建pipeline
  • Setup 7: 创建节点
  • Setup 8: 设置节点流名称
  • Setup 9: 设置视频大小
  • Setup 10: 建立链接关系
  • Setup 11: 连接设备并启动管道
  • Setup 12: 创建与DepthAI设备通信的输入队列和输出队列
  • Setup 13: 计算最大裁剪比例
  • Setup 14: 设置默认参数
  • Setup 15: 设置相机模式
  • Setup 16: 主循环
    • 获取视频帧
    • 从`ispQueue`获取所有的ISP帧
    • 从`stillQueue`获取所有的静态帧
  • Setup 17:运行程序

Setup 1: 创建文件

  • 创建新建2-rgb-camera-control文件夹
  • 用vscode打开该文件夹
  • 新建一个main.py 文件

Setup 2: 安装依赖

安装依赖前需要先创建和激活虚拟环境,我这里已经创建了虚拟环境OAKenv,在终端中输入cd…退回到OAKenv的根目录,输入 OAKenv\Scripts\activate激活虚拟环境

安装pip依赖项:

pip install numpy opencv-python depthai blobconverter --user

Setup 3: 导入需要的包

在main.py中导入项目需要的包

import depthai as dai
import cv2
from itertools import cycle

from itertools import cycle 导入了cycle函数,这是Python中itertools模块中的一个函数。cycle函数用于创建一个无限迭代器,可以循环遍历特定的序列。

Setup 4: 全局变量

# 设置 size('W','A','S','D' controls)
STEP_SIZE = 8
# 手动曝光/聚焦/白平衡设置步骤
EXP_STEP = 500 #us
ISO_STEP = 50
LENS_STEP = 3
WB_STEP = 200
  • STEP_SIZE = 8 定义步长为8。这表示在进行控制时,每次变化的单位大小为8。

  • EXP_STEP = 500 定义手动曝光的步长为500微秒(us)。这表示在手动调整曝光时,每次增加或减小的曝光时间的单位大小为500微秒。

  • ISO_STEP = 50 定义ISO值的步长为50。这表示在手动调整ISO值时,每次增加或减小的单位大小为50。

  • LENS_STEP = 3 定义镜头聚焦的步长为3。这表示在手动调整镜头聚焦时,每次增加或减小的单位大小为3。

  • WB_STEP = 200 定义白平衡的步长为200。这表示在手动调整白平衡时,每次增加或减小的单位大小为200。

Setup 5: 定义clamp函数

def clamp(num,v0,v1):return max(v0,min(num,v1))

这个clamp函数用于将输入的值限制在指定的范围内。

  • num是要进行限制的值。
  • v0是允许的最小值。
  • v1是允许的最大值。

函数的作用是检查给定的值num是否在范围[v0, v1]内。如果它小于最小值v0,则返回v0作为结果;如果它大于最大值v1,则返回v1作为结果;否则,返回num本身。

这个函数可以用于确保某个值在指定的范围内。例如,如果希望将变量x限制在0和100之间,可以使用x = clamp(x, 0, 100)来确保x的值不会小于0或大于100。

Setup 6: 创建pipeline

pipeline = dai.Pipeline()

Setup 7: 创建节点

camRgb = pipeline.create(dai.node.ColorCamera)
camRgb.setResolution(dai.ColorCameraProperties.SensorResolution.THE_1080_P)
camRgb.setIspScale(2,3) # 1080p --> 720p
stillEncoder = pipeline.create(dai.node.VideoEncoder)

这里创建了两个节点,并为其中的ColorCamera节点进行了一些配置。

通过pipeline.create()函数创建了一个名为camRgbColorCamera节点。

使用camRgb.setResolution()方法设置了相机的分辨率为1080p,即1920x1080像素。

使用camRgb.setIspScale()方法将图像缩小,将1080p的分辨率缩放为720p的分辨率。这里指定的缩放因子为2和3,表示将水平和垂直方向的分辨率都缩小为原来的2/3。因此,从1080p缩放到720p会使图像的宽度缩小为原来的2/3,高度缩小为原来的2/3。

使用pipeline.create()函数创建了一个名为stillEncoderVideoEncoder节点。

这段代码的主要目的是创建相机节点并对其进行初始化和配置,以便后续在管道中使用这些节点。

controlIn = pipeline.create(dai.node.XLinkIn)
configIn = pipeline.create(dai.node.XLinkIn)
ispOut = pipeline.create(dai.node.XLinkOut)
videoOut = pipeline.create(dai.node.XLinkOut)
stillMjpegOut = pipeline.create(dai.node.XLinkOut)

这段代码创建了四个节点,并为每个节点分配了一个名称。

使用pipeline.create()函数创建了一个名为controlInXLinkIn节点。该节点用于接收控制信息。

使用pipeline.create()函数创建了一个名为configInXLinkIn节点。该节点用于接收配置信息。

使用pipeline.create()函数创建了一个名为ispOutxLinkOut节点。该节点用于输出ISP(图像信号处理)处理后的图像。

使用pipeline.create()函数创建了一个名为videoOutxLinkOut节点。该节点用于输出视频流。

使用pipeline.create()函数创建了一个名为stillMjpegOutxLinkOut节点。该节点用于输出静态图像的MJPEG格式。

这些节点的主要作用是在管道中实现不同的数据传输和输出功能。

Setup 8: 设置节点流名称

controlIn.setStreamName("control")
configIn.setStreamName("config")
ispOut.setStreamName("isp")
videoOut.setStreamName("video")
stillMjpegOut.setStreamName("still")

使用controlIn.setStreamName()方法为之前创建的XLinkInxLinkOut节点设置了流名称。

通过为每个节点设置流名称,可以在管道中准确地分配和传输相应的数据流。这样可以更方便地管理和处理不同类型的数据。

Setup 9: 设置视频大小

camRgb.setVideoSize(640,360)
stillEncoder.setDefaultProfilePreset(1,dai.VideoEncoderProperties.Profile.MJPEG)

这段代码设置了camRgb节点的视频大小和stillEncoder节点的默认配置。

camRgb.setVideoSize(640, 360)方法设置了camRgb节点的视频大小为640x360像素。这表示输出的视频流将以该分辨率进行传输和显示。

stillEncoder.setDefaultProfilePreset(1, dai.VideoEncoderProperties.Profile.MJPEG)方法将stillEncoder节点的默认配置设置为使用MJPEG(Motion JPEG)编码器。MJPEG是一种常用的图像压缩格式,适合用于静态图像的编码和传输。这意味着stillEncoder节点将使用MJPEG编码器将静态图像转换为MJPEG格式,以便在后续流程中进行传输和处理。

Setup 10: 建立链接关系

camRgb.isp.link(ispOut.input)
camRgb.still.link(stillEncoder.input)
camRgb.video.link(videoOut.input)
controlIn.out.link(camRgb.inputControl)
configIn.out.link(camRgb.inputConfig)
stillEncoder.bitstream.link(stillMjpegOut.input)

这段代码建立了节点之间的链接关系。

camRgb.isp.link(ispOut.input)表示将camRgb节点的ISP(Image Signal Processor)输出链接到ispOut节点的输入。这将启用ISP处理,并将处理后的图像发送到ispOut节点。

camRgb.still.link(stillEncoder.input)表示将camRgb节点的静态图像输出链接到stillEncoder节点的输入。这将启用图像编码,并将编码后的图像发送到stillEncoder节点。

camRgb.video.link(videoOut.input)表示将camRgb节点的视频输出链接到videoOut节点的输入。这将启用视频传输,并将视频数据发送到videoOut节点。

controlIn.out.link(camRgb.inputControl)controlIn节点的输出链接到camRgb节点的输入控制接口,以接收来自controlIn节点的控制命令。

configIn.out.link(camRgb.inputConfig)configIn节点的输出链接到camRgb节点的配置输入接口,以接收来自configIn节点的配置数据。

stillEncoder.bitstream.link(stillMjpegOut.input)表示将stillEncoder节点的比特流输出链接到stillMjpegOut节点的输入。这将启用MJPEG格式的图像传输,并将MJPEG数据发送到stillMjpegOut节点。

Setup 11: 连接设备并启动管道

with dai.Device(pipeline) as device:

Setup 12: 创建与DepthAI设备通信的输入队列和输出队列

    controlQueue = device.getInputQueue("control")configQueue = device.getInputQueue("config")ispQueue = device.getOutputQueue("isp")videoQueue = device.getOutputQueue("video")stillQueue = device.getOutputQueue("still")

controlQueue = device.getInputQueue("control")通过getInputQueue方法创建了一个名为"control"的输入队列controlQueue,用于接收用于控制DepthAI设备的命令和指令。

configQueue = device.getInputQueue("config")通过getInputQueue方法创建了一个名为"config"的输入队列configQueue,用于接收DepthAI设备的配置信息。

ispQueue = device.getOutputQueue("isp")通过getOutputQueue方法创建了一个名为"isp"的输出队列ispQueue,用于接收经过ISP处理的图像数据。

videoQueue = device.getOutputQueue("video")通过getOutputQueue方法创建了一个名为"video"的输出队列videoQueue,用于接收视频数据。

stillQueue = device.getOutputQueue("still")通过getOutputQueue方法创建了一个名为"still"的输出队列stillQueue,用于接收静态图像数据。

Setup 13: 计算最大裁剪比例

    maxCropX = (camRgb.getIspWidth() - camRgb.getVideoWidth()) / camRgb.getIspWidth()maxCropY = (camRgb.getIspHeight() - camRgb.getVideoHeight()) / camRgb.getIspHeight()print(maxCropX,maxCropY,camRgb.getIspWidth(),camRgb.getVideoHeight())

这段代码计算了最大裁剪比例,以及获取了ISP图像的宽度、高度和视频图像的高度,并进行了打印输出。

maxCropX计算公式为(camRgb.getIspWidth() - camRgb.getVideoWidth()) / camRgb.getIspWidth(),表示ISP图像宽度与视频图像宽度之间的差值除以ISP图像宽度,得到最大裁剪比例。

maxCropY计算公式为(camRgb.getIspHeight() - camRgb.getVideoHeight()) / camRgb.getIspHeight(),表示ISP图像高度与视频图像高度之间的差值除以ISP图像高度,得到最大裁剪比例。

camRgb.getIspWidth()获取了ISP图像的宽度。

camRgb.getVideoHeight()获取了视频图像的高度。

通过print函数将最大裁剪比例maxCropXmaxCropY,以及ISP图像的宽度和视频图像的高度进行打印输出。

Setup 14: 设置默认参数

 # Default cropcropX = 0cropY = 0sendCamConfig = True# Defaults and limits for manual focus/exposure controlslensPos = 150expTime = 20000sensIso = 800wbManual = 4000ae_comp = 0ae_lock = Falseawb_lock = Falsesaturation = 0contrast = 0brightness = 0sharpness = 0luma_denoise = 0chroma_denoise = 0control = 'none'show = False

cropXcropY设置为0,表示默认情况下不进行裁剪。

sendCamConfig设置为True,表示默认情况下将发送相机配置。

用于手动对焦/曝光控制的默认值:

  • lensPos设置为150,表示默认情况下的镜头位置。
  • expTime设置为20000,表示默认情况下的曝光时间。
  • sensIso设置为800,表示默认情况下的ISO感光度。
  • wbManual设置为4000,表示默认情况下的手动白平衡。
  • ae_comp设置为0,表示默认情况下的自动曝光补偿。
  • ae_lock设置为False,表示默认情况下自动曝光未锁定。
  • awb_lock设置为False,表示默认情况下自动白平衡未锁定。
  • saturationcontrastbrightnesssharpnessluma_denoisechroma_denoise均设置为0,表示默认情况下没有进行任何图像增强或降噪。
  • control设置为’none’,表示默认情况下没有进行任何控制操作。
  • show设置为False,表示默认情况下不显示图像。

Setup 15: 设置相机模式

    awb_mode = cycle([item for name,item in vars(dai.CameraControl.AutoWhiteBalanceMode).items() if name.isupper()])anti_banding_mode = cycle([item for name,item in vars(dai.CameraControl.AntiBandingMode).items() if name.isupper()])effect_mode = cycle([item for name,item in vars(dai.CameraControl.EffectMode).items() if name.isupper()]) 

这段代码使用了循环生成器来设置相机的自动白平衡模式awb_mode、防闪烁模式anti_banding_mode和效果模式effect_mode

awb_mode的生成器从dai.CameraControl.AutoWhiteBalanceMode中获取了所有大写的属性,并使用cycle函数创建了一个循环生成器。

anti_banding_mode的生成器从dai.CameraControl.AntiBandingMode中获取了所有大写的属性,并使用cycle函数创建了一个循环生成器。

effect_mode的生成器从dai.CameraControl.EffectMode中获取了所有大写的属性,并使用cycle函数创建了一个循环生成器。

name.isupper()是一个字符串方法,用于检查字符串中的所有字符是否都是大写字母。如果是,则返回True,否则返回False

这样设置可以循环选择相机的自动白平衡模式、防闪烁模式和效果模式。

Setup 16: 主循环

    while True:

获取视频帧

        vidFrames = videoQueue.tryGetAll()for vidFrame in vidFrames:cv2.imshow('video',vidFrame.getCvFrame())

通过videoQueue获取所有的视频帧,并使用cv2.imshow()来展示每一帧。cv2.imshow()是OpenCV库中的一个函数,用于展示图片或视频帧。

ispQueue获取所有的ISP帧

  ispFrames = ispQueue.tryGetAll()for ispFrame in ispFrames:if show:txt = f"[{ispFrame.getSequenceNum()}]"txt += f"Exposure:{ispFrame.getExposureTime().total_seconds()*1000:.3f} ms, "txt += f"ISO:{ispFrame.getSensitivity()}, "txt += f"Lens position:{ispFrame.getLensPosition()}, "txt += f"Color temp:{ispFrame.getColorTemperature()} K"print(txt)cv2.imshow('isp',ispFrame.getCvFrame())# Send new cfg to cameraif sendCamConfig:cfg = dai.ImageManipConfig()cfg.setCropRect(cropX,cropY,0,0)configQueue.send(cfg)print('Sending new crop - x: ',cropX,' y: ',cropY)sendCamConfig = False

这段代码从ispQueue获取所有的ISP帧,并进行显示。对于每个ispFrame,首先检查show是否为True,如果是,则获取与帧相关的文本信息,并将其打印出来。文本信息包括帧的序列号、曝光时间、ISO值、镜头位置和色温。

使用cv2.imshow()展示每个ispFrame的OpenCV表示。每个帧都会在名为’isp’的窗口中显示。

接下来,如果sendCamConfigTrue,则创建一个新的dai.ImageManipConfig()配置,并将其发送到相机的configQueue中,这个配置包括裁剪的区域。同时,还会打印出发送的新裁剪区域的坐标(x、y),并将sendCamConfig设置为False,表示发送配置完成。

stillQueue获取所有的静态帧

        stillFrames = stillQueue.tryGetAll()for stillFrame in stillFrames:# Decode JPEGframe = cv2.imdecode(stillFrame.getData(), cv2.IMREAD_UNCHANGED)# Displaycv2.imshow('still', frame)

这段代码从stillQueue获取所有的静态帧,并进行显示。对于每个stillFrame,首先使用cv2.imdecode()函数将JPEG数据解码为图像。解码后的图像存储在frame变量中。

然后,使用cv2.imshow()展示解码后的图像。图像会在名为’still’的窗口中显示。

 key = cv2.waitKey(1)if key == ord('q'):breakelif key == ord('/'):show = not showif not show: print("Printing camera settings: OFF")elif key == ord('c'):ctrl = dai.CameraControl()ctrl.setCaptureStill(True)controlQueue.send(ctrl)elif key == ord('t'):print("Autofocus trigger (and disable continuous)")ctrl = dai.CameraControl()ctrl.setAutoFocusMode(dai.CameraControl.AutoFocusMode.AUTO)ctrl.setAutoFocusTrigger()controlQueue.send(ctrl)elif key == ord('f'):print("Autofocus enable, continuous")ctrl = dai.CameraControl()ctrl.setAutoFocusMode(dai.CameraControl.AutoFocusMode.CONTINUOUS_VIDEO)controlQueue.send(ctrl)elif key == ord('e'):print("Autoexposure enable")ctrl = dai.CameraControl()ctrl.setAutoExposureEnable()controlQueue.send(ctrl)elif key == ord('b'):print("Auto white-balance enable")ctrl = dai.CameraControl()ctrl.setAutoWhiteBalanceMode(dai.CameraControl.AutoWhiteBalanceMode.AUTO)controlQueue.send(ctrl)elif key in [ord(','), ord('.')]:if key == ord(','): lensPos -= LENS_STEPif key == ord('.'): lensPos += LENS_STEPlensPos = clamp(lensPos, 0, 255)print("Setting manual focus, lens position: ", lensPos)ctrl = dai.CameraControl()ctrl.setManualFocus(lensPos)controlQueue.send(ctrl)elif key in [ord('i'), ord('o'), ord('k'), ord('l')]:if key == ord('i'): expTime -= EXP_STEPif key == ord('o'): expTime += EXP_STEPif key == ord('k'): sensIso -= ISO_STEPif key == ord('l'): sensIso += ISO_STEPexpTime = clamp(expTime, 1, 33000)sensIso = clamp(sensIso, 100, 1600)print("Setting manual exposure, time: ", expTime, "iso: ", sensIso)ctrl = dai.CameraControl()ctrl.setManualExposure(expTime, sensIso)controlQueue.send(ctrl)elif key in [ord('n'), ord('m')]:if key == ord('n'): wbManual -= WB_STEPif key == ord('m'): wbManual += WB_STEPwbManual = clamp(wbManual, 1000, 12000)print("Setting manual white balance, temperature: ", wbManual, "K")ctrl = dai.CameraControl()ctrl.setManualWhiteBalance(wbManual)controlQueue.send(ctrl)elif key in [ord('w'), ord('a'), ord('s'), ord('d')]:if key == ord('a'):cropX = cropX - (maxCropX / camRgb.getResolutionWidth()) * STEP_SIZEif cropX < 0: cropX = 0elif key == ord('d'):cropX = cropX + (maxCropX / camRgb.getResolutionWidth()) * STEP_SIZEif cropX > maxCropX: cropX = maxCropXelif key == ord('w'):cropY = cropY - (maxCropY / camRgb.getResolutionHeight()) * STEP_SIZEif cropY < 0: cropY = 0elif key == ord('s'):cropY = cropY + (maxCropY / camRgb.getResolutionHeight()) * STEP_SIZEif cropY > maxCropY: cropY = maxCropYsendCamConfig = Trueelif key == ord('1'):awb_lock = not awb_lockprint("Auto white balance lock:", awb_lock)ctrl = dai.CameraControl()ctrl.setAutoWhiteBalanceLock(awb_lock)controlQueue.send(ctrl)elif key == ord('2'):ae_lock = not ae_lockprint("Auto exposure lock:", ae_lock)ctrl = dai.CameraControl()ctrl.setAutoExposureLock(ae_lock)controlQueue.send(ctrl)elif key >= 0 and chr(key) in '34567890[]':if   key == ord('3'): control = 'awb_mode'elif key == ord('4'): control = 'ae_comp'elif key == ord('5'): control = 'anti_banding_mode'elif key == ord('6'): control = 'effect_mode'elif key == ord('7'): control = 'brightness'elif key == ord('8'): control = 'contrast'elif key == ord('9'): control = 'saturation'elif key == ord('0'): control = 'sharpness'elif key == ord('['): control = 'luma_denoise'elif key == ord(']'): control = 'chroma_denoise'print("Selected control:", control)elif key in [ord('-'), ord('_'), ord('+'), ord('=')]:change = 0if key in [ord('-'), ord('_')]: change = -1if key in [ord('+'), ord('=')]: change = 1ctrl = dai.CameraControl()if control == 'none':print("Please select a control first using keys 3..9 0 [ ]")elif control == 'ae_comp':ae_comp = clamp(ae_comp + change, -9, 9)print("Auto exposure compensation:", ae_comp)ctrl.setAutoExposureCompensation(ae_comp)elif control == 'anti_banding_mode':abm = next(anti_banding_mode)print("Anti-banding mode:", abm)ctrl.setAntiBandingMode(abm)elif control == 'awb_mode':awb = next(awb_mode)print("Auto white balance mode:", awb)ctrl.setAutoWhiteBalanceMode(awb)elif control == 'effect_mode':eff = next(effect_mode)print("Effect mode:", eff)ctrl.setEffectMode(eff)elif control == 'brightness':brightness = clamp(brightness + change, -10, 10)print("Brightness:", brightness)ctrl.setBrightness(brightness)elif control == 'contrast':contrast = clamp(contrast + change, -10, 10)print("Contrast:", contrast)ctrl.setContrast(contrast)elif control == 'saturation':saturation = clamp(saturation + change, -10, 10)print("Saturation:", saturation)ctrl.setSaturation(saturation)elif control == 'sharpness':sharpness = clamp(sharpness + change, 0, 4)print("Sharpness:", sharpness)ctrl.setSharpness(sharpness)elif control == 'luma_denoise':luma_denoise = clamp(luma_denoise + change, 0, 4)print("Luma denoise:", luma_denoise)ctrl.setLumaDenoise(luma_denoise)elif control == 'chroma_denoise':chroma_denoise = clamp(chroma_denoise + change, 0, 4)print("Chroma denoise:", chroma_denoise)ctrl.setChromaDenoise(chroma_denoise)controlQueue.send(ctrl)

这段代码等待用户按下键盘上的某个键,并根据按键的值执行相应的操作。以下是每个按键所执行的操作:

  • 按下’q’键,退出程序。
  • 按下’/'键,切换是否显示相机设置的标志show。如果showFalse,打印出"Printing camera settings: OFF"。
  • 按下’c’键,发送消息以请求相机捕获静态帧。
  • 按下’t’键,执行自动对焦触发和禁用连续自动对焦的操作。
  • 按下’f’键,启用连续自动对焦的操作。
  • 按下’e’键,启用自动曝光的操作。
  • 按下’b’键,启用自动白平衡的操作。
  • 按下’,‘键或’.'键,调整镜头位置(焦距)。
  • 按下’i’键或’o’键,调整曝光时间。
  • 按下’k’键或’l’键,调整ISO值。
  • 按下’n’键或’m’键,调整白平衡的色温。
  • 按下’w’键、'a’键、's’键或’d’键,调整图像的裁剪区域。
  • 按下’1’键,切换自动白平衡锁的状态。
  • 按下’2’键,切换自动曝光锁的状态。
  • 按下’3’键到’0’键,或’[‘键和’]'键,选择相机控制参数。
  • 按下’-‘键、’_‘键、’+‘键或’='键,根据所选择的相机控制参数,增加或减少其值。

以上操作都会创建一个dai.CameraControl()对象,并将其发送到controlQueue队列中以更改相机设置。

Setup 17:运行程序

在终端中输入如下指令运行程序

python main.py

可以看到已经驱动OAK打开了视频,可以通过输入上面定义的按键来控制相机
在这里插入图片描述
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/1522.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

蜣螂优化算法(DBO)优化VMD参数,最小包络熵、样本熵、信息熵、排列熵(适应度函数可自行选择,一键修改)包含MATLAB源代码

蜣螂优化算法是华大学沈波教授团队&#xff0c;继麻雀搜索算法(Sparrow Search Algorithm&#xff0c;SSA&#xff09;之后&#xff0c;于2022年11月27日又提出的一种全新的群体智能优化算法。已有很多学者将算法用于实际工程问题中&#xff0c;今天咱们用蜣螂优化算法优化一下…

ASEMI代理光宝光耦LTV-6341的应用与性能分析

编辑-Z 本文将全面深入地探讨光耦LTV-6341的应用与性能。首先&#xff0c;我们将介绍光耦LTV-6341的基本概念和工作原理&#xff0c;然后&#xff0c;我们将详细分析其在电子设备中的应用&#xff0c;接着&#xff0c;我们将对其性能进行深入的分析&#xff0c;最后&#xff0…

python spider 爬虫 之 解析 xpath 、jsonpath、BeautifulSoup (二)

Jsonpath 安装&#xff1a; pip install -i https://pypi.tuna.tsinghua.edu.cn/simple jsonpath 使用&#xff1a;jsonpath 只能解析本地文件&#xff0c;跟xpath不一样 objjson.load(open(‘json文件’&#xff0c;‘r’, encoding‘utf-8’)) json.load(是文件&#xff0c;…

Flink-窗口源码

滚动窗口底层只有一个窗口&#xff0c;每次用for循环&#xff0c;把前面过期的数据移除&#xff0c;留下的数据再进行计算 滑动窗口的时间窗口&#xff0c;是有多个window的&#xff0c;因为有重叠的数据 计数窗口、计数滑动窗口&#xff0c;底层只有一个窗口 会话窗口只有一…

会声会影2023最新六大新功能,会声会影2023序列号能用多少次

会声会影2023版是一款非常实用的视频剪辑软件&#xff0c;该软件能够为广大用户带来丰富的集成化工具&#xff0c;并且优化了工作流程&#xff0c;无论你是新手还是老手都可以快速上手这款软件。会声会影2022永久激活版支持自定义码率设置&#xff0c;用户可以根据自己的需求设…

Linux--进入一个路径:cd

Linux系统中&#xff0c;磁盘上的文件和目录被组成一棵目录树&#xff0c;每个节点都是目录或文件 cd是change directory的简写 语法&#xff1a; cd 目录名 功能&#xff1a; 改变工作目录。将当前工作目录改变到指定的目录下。 举例&#xff1a; cd .. : 返回上级目录&…

67、基于51单片机ADXL345计步器系统设计(程序+原理图+PCB源文件+参考论文+开题报告+设计资料+元器件清单等)

摘 要 计步器是一种颇受欢迎的日常锻炼进度监控器&#xff0c;可以激励人们挑战自己&#xff0c;增强体质&#xff0c;帮助瘦身。早期设计利用加重的机械开关检测步伐&#xff0c;并带有一个简单的计数器。晃动这些装置时&#xff0c;可以听到有一个金属球来回滑动&#xff0c…

Spark10-11

10. 广播变量 10.1 广播变量的使用场景 在很多计算场景&#xff0c;经常会遇到两个RDD进行JOIN&#xff0c;如果一个RDD对应的数据比较大&#xff0c;一个RDD对应的数据比较小&#xff0c;如果使用JOIN&#xff0c;那么会shuffle&#xff0c;导致效率变低。广播变量就是将相对…

基于SpringBoot+Vue+微信小程序的电影平台

✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取项目下载方式&#x1f345; 一、项目背景介绍&#xff1a; 研究背景&#xff1a;…

A. Portal(dp优化枚举)

Problem - 1580A - Codeforces CQXYM发现了一个大小为nm的矩形。矩形由n行m列的方块组成&#xff0c;每个方块可以是黑曜石方块或空方块。CQXYM可以通过一次操作将黑曜石方块变为空方块&#xff0c;或将空方块变为黑曜石方块。 一个大小为ab的矩形M被称为传送门&#xff0c;当…

【adb指令】

一、什么是adb adb的全称为Android Debug Bridge&#xff0c;官方提供的用于操作安卓设备的工具。 二、adb用来干什么&#xff1f; 在电脑终端通过命令行&#xff1a; 打开收手机应用&#xff1b;传输文件&#xff1b;点击、输入、滑动等&#xff1b;硬件操作、返回、回到首…

SpringBoot04:JSR303数据校验及多环境切换

目录 一、JSR303数据校验 1、如何使用&#xff1f; 2、常见参数 二、多环境切换 1、多配置文件 2、yaml的多文档块 3、配置文件加载位置 一、JSR303数据校验 1、如何使用&#xff1f; SpringBoot中可以用Validated来校验数据&#xff0c;如果数据异常则会统一抛出异常…