4. 相机SDK二次开发:基于OpenCV或厂商SDK实现相机参数设置与图像采集
做镜头测试自动化,第一步就是搞定相机。说白了,你得让电脑跟相机“说上话”,能调参数、能拿图像。这事儿看着简单,但坑不少。我这些年折腾过海康、大恒、Basler好几家的SDK,也用过OpenCV的通用接口。今天就把这些经验掰开了揉碎了讲给你听。
4.1 选OpenCV还是厂商SDK?
这是个老生常谈的问题。我的建议是:别纠结,看场景。
| 对比项 | OpenCV VideoCapture | 厂商SDK |
|---|---|---|
| 上手难度 | 低,几行代码就能跑 | 中高,需要读文档 |
| 参数控制 | 有限,很多参数调不了 | 全面,曝光、增益、触发全支持 |
| 稳定性 | 一般,不同相机表现差异大 | 高,厂商自己最懂自家硬件 |
| 帧率 | 受限于驱动层,通常较低 | 可达到相机最高帧率 |
| 多相机支持 | 麻烦,容易冲突 | 原生支持,有设备管理 |
我的经验:做原型验证、快速测试时用OpenCV,省事。但一旦进入量产或高精度测试,必须上厂商SDK。我曾经在一个项目里偷懒用OpenCV,结果发现曝光时间死活调不到微秒级,最后全部重写,血的教训。
4.2 OpenCV方式:快速上手
先说说最简单的。OpenCV的VideoCapture接口,对USB相机基本是即插即用。
import cv2
# 打开相机,0是设备索引
cap = cv2.VideoCapture(0)
if not cap.isOpened():
print("相机打开失败")
exit()
# 设置参数
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1920)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 1080)
cap.set(cv2.CAP_PROP_EXPOSURE, -6) # 注意:不同系统单位不同
cap.set(cv2.CAP_PROP_GAIN, 0)
# 采集一帧
ret, frame = cap.read()
if ret:
cv2.imwrite("test.jpg", frame)
cap.release()
注意:OpenCV的set()函数返回值是布尔型,但不代表参数真的设置成功了。很多相机驱动会忽略不支持的参数,返回True但实际没变。我建议每次set之后,用get()读回来确认一下。
OpenCV的局限很明显。你想想看,工业相机常用的触发模式、软触发、硬触发这些功能,VideoCapture根本没法用。所以,别指望它干重活。
4.3 厂商SDK:以海康威视为例
厂商SDK才是正经干活的东西。我以海康的MVS SDK为例,讲一下核心流程。大恒、Basler的SDK逻辑大同小异,换汤不换药。
4.3.1 初始化与枚举设备
from mvsdk import *
# 初始化SDK
MvCameraSDKInit()
# 枚举设备
device_list = MV_CC_DEVICE_INFO_LIST()
ret = MvEnumDevices(device_list, MV_GIGE_DEVICE | MV_USB_DEVICE)
if ret != 0:
print("枚举设备失败")
exit()
# 取第一个设备
device_info = device_list.pDeviceInfo[0]
# 创建相机句柄
cam = MvCamera()
ret = cam.MV_CC_CreateHandle(device_info)
if ret != 0:
print("创建句柄失败")
exit()
嗯,这里要注意:枚举设备后一定要检查返回值。我遇到过好几次,网线松了或者USB供电不足,设备列表是空的,但程序没做判断就直接往下跑,结果崩溃。
4.3.2 参数设置
厂商SDK的参数设置比OpenCV精细得多。每个参数都有明确的类型、范围和单位。
# 打开设备
cam.MV_CC_OpenDevice(MV_ACCESS_Exclusive, 0)
# 设置曝光时间(微秒)
exp_time = 5000 # 5ms
cam.MV_CC_SetFloatValue("ExposureTime", exp_time)
# 设置增益(dB)
gain = 0.0
cam.MV_CC_SetFloatValue("Gain", gain)
# 设置触发模式
cam.MV_CC_SetEnumValue("TriggerMode", MV_TRIGGER_MODE_ON)
cam.MV_CC_SetEnumValue("TriggerSource", MV_TRIGGER_SOURCE_SOFTWARE)
# 设置ROI
cam.MV_CC_SetIntValue("Width", 1920)
cam.MV_CC_SetIntValue("Height", 1080)
cam.MV_CC_SetIntValue("OffsetX", 0)
cam.MV_CC_SetIntValue("OffsetY", 0)
避坑指南:我曾经在设置曝光时间时,直接写了个很大的值,结果相机画面全白。后来才发现,不同相机的曝光范围不一样。一定要先调用MV_CC_GetFloatValue("ExposureTime")读一下当前值,或者用MV_CC_GetFloatValue("ExposureTimeMin")和MV_CC_GetFloatValue("ExposureTimeMax")确认范围。
4.3.3 图像采集
图像采集有两种模式:主动取流和回调方式。我个人习惯用主动取流,控制更灵活。
# 开始采集
cam.MV_CC_StartGrabbing()
# 准备缓冲区
frame_info = MV_FRAME_OUT_INFO_EX()
data_buf = (c_ubyte * (1920 * 1080 * 3))()
# 主动取一帧
ret = cam.MV_CC_GetOneFrameTimeout(data_buf,
sizeof(data_buf),
frame_info,
1000) # 超时1秒
if ret == 0:
# 转换为numpy数组
import numpy as np
img = np.frombuffer(data_buf, dtype=np.uint8)
img = img.reshape((frame_info.nHeight, frame_info.nWidth, 3))
# 保存或处理
cv2.imwrite("capture.jpg", img)
else:
print("取帧失败,错误码:", ret)
# 停止采集
cam.MV_CC_StopGrabbing()
cam.MV_CC_CloseDevice()
cam.MV_CC_DestroyHandle()
关键点:缓冲区大小一定要算对。我见过有人直接写死一个固定大小,结果换了分辨率就崩。正确做法是用frame_info.nWidth * frame_info.nHeight * 3动态计算。另外,超时时间别设太短,尤其是第一次取帧时,相机可能需要时间稳定。
4.4 多相机同步采集
做镜头测试时,经常需要多台相机同时拍。比如测一个镜头的MTF,可能需要同时拍中心、边缘多个视场。
多相机同步的核心是软触发或硬触发。软触发就是软件发指令,硬触发靠外部信号。我建议能用硬触发就用硬触发,精度高得多。
# 伪代码:多相机软触发同步
cameras = [cam1, cam2, cam3]
# 所有相机设为软触发模式
for cam in cameras:
cam.MV_CC_SetEnumValue("TriggerMode", MV_TRIGGER_MODE_ON)
cam.MV_CC_SetEnumValue("TriggerSource", MV_TRIGGER_SOURCE_SOFTWARE)
cam.MV_CC_StartGrabbing()
# 同时触发所有相机
for cam in cameras:
cam.MV_CC_SoftTrigger()
# 分别取帧
for cam in cameras:
ret, frame = cam.MV_CC_GetOneFrameTimeout(...)
# 保存到不同文件
注意:软触发有延迟。我实测过,不同相机之间的触发响应时间可能差几毫秒。如果要求严格同步(比如1ms以内),必须用硬触发。用一根线把相机的触发输入连到同一个信号源上。
4.5 常见问题与调试技巧
做SDK开发,遇到问题别慌。我总结了几条经验:
- 相机连不上?先检查网口或USB口,用厂商自带的客户端(如海康MVS、大恒GalaxyView)试试。客户端能连,说明硬件没问题,问题在代码。
- 参数设置无效?很多参数需要先停止采集才能改。比如曝光时间,有些相机在Grabbing状态下不让改。先StopGrabbing,改完再Start。
- 图像花屏?大概率是像素格式不匹配。检查一下
PixelFormat,常见的有Mono8、BayerRG8、RGB8。OpenCV默认是BGR,厂商SDK可能是其他格式,需要转换。 - 内存泄漏?每次取帧后记得释放缓冲区。尤其是用回调模式时,回调函数里别做耗时操作,否则缓冲区会堆积。
我的调试习惯:先在厂商客户端里把所有参数调好,记下来。然后在代码里一个一个设,每设一个就读回来确认。这样能快速定位是哪个参数出了问题。
4.6 知识体系总览
下面这张图,是我对相机SDK二次开发核心逻辑的总结。你照着这个思路走,基本不会跑偏。
这张图把整个流程串起来了。你从初始化开始,一路走到参数设置,然后进入采集循环,最后释放资源。注意那个虚线循环箭头——采集和处理是重复的,直到你主动停止。
好了,相机SDK二次开发的核心内容就这些。记住:先读文档,再写代码;先调通单相机,再搞多相机同步。别问我怎么知道的,都是踩坑踩出来的。