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

news2024/9/21 16:47:34

今天我们来实现 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 crop
    cropX = 0
    cropY = 0
    sendCamConfig = True

    # Defaults and limits for manual focus/exposure controls
    lensPos = 150
    expTime = 20000
    sensIso = 800
    wbManual = 4000
    ae_comp = 0
    ae_lock = False
    awb_lock = False
    saturation = 0
    contrast = 0
    brightness = 0
    sharpness = 0
    luma_denoise = 0
    chroma_denoise = 0
    control = '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 camera
            if 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 JPEG
            frame = cv2.imdecode(stillFrame.getData(), cv2.IMREAD_UNCHANGED)
            # Display
            cv2.imshow('still', frame)

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

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

 key = cv2.waitKey(1)
        if key == ord('q'):
            break
        elif key == ord('/'):
            show = not show
            if 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_STEP
            if key == ord('.'): lensPos += LENS_STEP
            lensPos = 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_STEP
            if key == ord('o'): expTime += EXP_STEP
            if key == ord('k'): sensIso -= ISO_STEP
            if key == ord('l'): sensIso += ISO_STEP
            expTime = 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_STEP
            if key == ord('m'): wbManual += WB_STEP
            wbManual = 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_SIZE
                if cropX < 0: cropX = 0
            elif key == ord('d'):
                cropX = cropX + (maxCropX / camRgb.getResolutionWidth()) * STEP_SIZE
                if cropX > maxCropX: cropX = maxCropX
            elif key == ord('w'):
                cropY = cropY - (maxCropY / camRgb.getResolutionHeight()) * STEP_SIZE
                if cropY < 0: cropY = 0
            elif key == ord('s'):
                cropY = cropY + (maxCropY / camRgb.getResolutionHeight()) * STEP_SIZE
                if cropY > maxCropY: cropY = maxCropY
            sendCamConfig = True
        elif key == ord('1'):
            awb_lock = not awb_lock
            print("Auto white balance lock:", awb_lock)
            ctrl = dai.CameraControl()
            ctrl.setAutoWhiteBalanceLock(awb_lock)
            controlQueue.send(ctrl)
        elif key == ord('2'):
            ae_lock = not ae_lock
            print("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 = 0
            if key in [ord('-'), ord('_')]: change = -1
            if key in [ord('+'), ord('=')]: change = 1
            ctrl = 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.coloradmin.cn/o/697879.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Nginx重写跳转

目录 一、rewrite跳转场景 二、rewrite跳转实现 三、rewrite实际场景 四、rewrite正则表达式 五、rewrite命令语法格式 六、location分类 1、location 大致可以分为三类 2、正则匹配的常用表达式 七、location 优先级 八、rewrite和location比较 九、总结 一、rewri…

9 HAL库驱动框架简述(STM32HAL库)

目录 HAL库驱动框架简述 HAL库外设设计思想 HAL库和Cube MX相结合 一、对外设的封装——句柄结构体 二、外设初始化 初始化结构体 初始化的逻辑 三、外设使用逻辑 通用接口函数 初始化函数 I/O操作函数 控制函数 状态参数 扩展接口函数 总结 补充&#xff1a;H…

如何以最小成本通过CMMI评估?评估调查问卷收集中

CMMI评估&#xff0c;我们经常遇到&#xff1a;评估费用高、时间长&#xff0c;CMMI标准过程无法高效落地&#xff0c;那么我们如何以最小的成本通过CMMI评估&#xff1f; CoCode开发云公益直播课即将开播&#xff01;直播主题&#xff1a;如何以最小成本通过CMMI评估。为了更好…

怎么把文字生成图片?三款ai绘画生成器分享

如果你对ai绘画工具有一定了解的话&#xff0c;你就会知道&#xff1a;市面上大部分ai绘画工具都是收费。再退一步讲&#xff0c;我们暂且不论收费价格的高低&#xff0c;大多数收费的ai绘画工具也不一定能准确匹配我们的需求。 仅仅在学生党和工作党之间&#xff0c;对ai绘画…

python爬虫增加多线程获取数据

Python爬虫应用领域广泛&#xff0c;并且在数据爬取领域处于霸主位置&#xff0c;并且拥有很多性能好的框架&#xff0c;像Scrapy、Request、BeautifuSoap、urlib等框架可以实现爬行自如的功能&#xff0c;只要有能爬取的数据&#xff0c;Python爬虫均可实现。数据信息采集离不…

windows10教育版过期,记录一下重装windows11专业工作站。报错“若要在此计算机上安装windows,请重新启动安装”

准确的来说是重装 windows10或者windows11都有问题&#xff0c;而且卡了很久&#xff1b;最初的问题是 第一步解决问题的方法&#xff1a; 1、修改注册文件&#xff1a; 有些不显示鼠标&#xff0c;记住鼠标按住拖动&#xff0c;这样可以看见矩形的样子&#xff0c;可以知道大…

shardingsphere-proxy 搭建mysql的分库分表

1、docker安装mysql5.7版本 拉取mysql的镜像 docker pull mysql:5.7创建mysql的配置目录&#xff0c;日志目录&#xff0c;数据存储的目录 mkdir -p /home/sunyuhua/docker/mysql/conf mkdir -p /home/sunyuhua/docker/mysql/logs mkdir -p /home/sunyuhua/docker/mysql/dat…

服务器日志处理,文件截取关键字

临近年中述职&#xff0c;需要各种量化参数&#xff0c;服务稳定性是上半年的重中之重&#xff0c;所以需要重点列出说服性指标&#xff0c;因此各种错误吗的统计信息便是重要信息&#xff0c;因为公司的日志采集系统因上云缘故&#xff0c;导致历史数据丢失没法查询&#xff0…

Jetpack Compose实现 验证码输入框

highlight: androidstudio Jetpack Compose 作为 Android 的新一代 UI 开发框架,提供了非常强大的工具来构建用户界面。 今天,我们就利用 Compose 来实现一个炫酷的验证码输入框! 开始的思路是用6个TextField来实现 // 用于存储验证码的长度 val codeLength 6 // 定义一个…

Cesium 实战 - 使用 gltf-vscode 查看、预览以及编辑 glTF 和 GLB 模型

Cesium 实战 - 使用 gltf-vscode 查看、预览以及编辑 glTF 和 GLB 模型 VScode&#xff08;Visual Studio Code&#xff09; 安装模型必要插件VScode 预览自定义关节&#xff08;articulations&#xff09;动作VScode 导入 GLB 格式模型VScode 导出 GLB 格式模型 模型渲染作为 …

【什么是iMessage苹果推】怎样来获取设备令牌(Device Token)实现步骤

要获取设备令牌&#xff08;Device Token&#xff09;&#xff0c;您需要在应用程序中实现以下步骤&#xff1a; 在应用程序中请求用户授权&#xff1a;您需要请求用户授权允许应用程序发送远程通知。这可以通过使用 UNUserNotificationCenter&#xff08;User Notifications …

Linux学习之以openresty为例学习源码安装软件

https://github.com/openresty/openresty/tags里边有openresty各个版本的源码。 https://openresty.org/en/是官网。 wget https://github.com/openresty/openresty/archive/refs/tags/v1.15.8.1.tar.gz(github网址)或者wget https://openresty.org/download/openresty-1.15.…

6月29日第壹简报,星期四,农历五月十二

6月29日第壹简报&#xff0c;星期四&#xff0c;农历五月十二&#xff0c;早安&#xff01;坚持阅读&#xff0c;静待花开1. 中国移动元宇宙产业联盟成立&#xff0c;科大讯飞、华为、小米等为首批成员。2. 离岸人民币兑美元跌破7.25关口&#xff0c;创去年11月末来低位。3. 成…

STC89C52与LCD1602液晶显示的软硬件仿真

STC89C52与LCD1602液晶显示的软硬件仿真 硬件仿真平台&#xff1a;protues8.13 软件仿真平台&#xff1a;keil5 硬件连接图&#xff1a; 软件代码实现&#xff1a; &#xff08;复制后 粘贴到keil5中&#xff0c;即可使用&#xff0c;无需修改&#xff09; #include <RE…

SpringCloud-Nacos注册中心

文章目录 Nacos注册中心服务注册到nacos1&#xff09;引入依赖2&#xff09;配置nacos地址3&#xff09;重启 5.3.服务分级存储模型给user-service配置集群同集群优先的负载均衡 权重配置环境隔离创建namespace给微服务配置namespace Nacos与Eureka的区别 Nacos注册中心 服务注…

v8-tc39-ecma262: at,代替“arr[0]“取值

首先是语义化 其次是函数式&#xff0c;意味着加入流式调用队列 如上图&#xff0c;解释如下&#xff1a; 对象&#xff0c;调用对象函数处理类数组&#xff0c;调用类数组处理关联下标&#xff1f;转为Integer或者Infinity类型如果下标的值大于等于0&#xff0c;则设置赋值给…

Windows 驱动开发环境搭建

Windows 驱动开发环境搭建及 windbg 调试工具安装使用 引言了解 Windows 驱动开发环境下载 Windows 驱动开发环境根据需要下载安装对应版本的 Visual Studio下载安装对应的 WDK 工具包 编写第一个驱动代码总结参考资料 引言 对于 Windows 驱动开发&#xff0c;在微软官方的文档…

go定时任务crontab

在linux里可以通过crontab -e或者vi /etc/crontab编辑定时任务&#xff0c;区别在于后者只有root用户可以&#xff0c;还可以指定shell环境&#xff0c;不建议修改&#xff0c;修改前建议备份&#xff0c;前者任何用户都可以使用&#xff0c;两者修改后都不用修改自动重启。 1…

尚无忧宠物托运小程序app源码前景如何?

宠物托运市场调研分析 由于宠物托运在交通运输中并不是一个很大的类目&#xff0c;行业尚缺乏标准的流程规范与相关的监管机制&#xff0c;目前我国市面上常见的三方宠物托运公司多无正规手续&#xff0c;更有多数公司不具备相关运输资质。 如今&#xff0c;宠物经济不断崛起…

Linux:安装tomcat

注意&#xff1a;1.安装tomcat时最好用非root用户安装 2.可以选择新建一个用户&#xff0c;用户安装部署tomcat&#xff0c;本文将继续用fovace账户进行tomcat安装 一、前置条件 安装tomcat需要先安装jdk&#xff0c;所以先确定系统中是否已经有jdk&#xff0c;如下&#xff1a…