4、图像采集实战:相机SDK调用与参数设置
好,咱们进入实战环节了。前面讲了那么多理论,说白了都是为了这一刻——让相机真正动起来,把图像抓到手里。
这一章我会带着你走一遍完整的流程:从SDK初始化,到参数调节,再到实时预览和保存。我会用海康威视和大恒这两款主流相机做例子,因为我在项目中用得最多。其他品牌的SDK,套路其实大同小异。
4.1 SDK初始化——第一步不能错
拿到相机第一件事是什么?不是急着拍照,而是初始化SDK。这一步错了,后面全白搭。
我记得第一次用海康的SDK时,忘了调用MV_CC_Initialize(),结果后面所有接口都返回错误码。查了半天文档才发现,原来初始化是必须的。
核心要点:SDK初始化只需要调用一次,通常在程序启动时完成。程序退出前,记得调用反初始化接口释放资源。
来看代码。海康威视的初始化流程是这样的:
// 海康威视SDK初始化
MV_CC_DEVICE_INFO_LIST stDeviceList;
memset(&stDeviceList, 0, sizeof(MV_CC_DEVICE_INFO_LIST));
// 枚举设备
int nRet = MV_CC_EnumDevices(MV_GIGE_DEVICE | MV_USB_DEVICE, &stDeviceList);
if (nRet != MV_OK) {
printf("枚举设备失败,错误码:%d\n", nRet);
return -1;
}
// 创建句柄
void* handle = NULL;
nRet = MV_CC_CreateHandle(&handle, stDeviceList.pDeviceInfo[0]);
if (nRet != MV_OK) {
printf("创建句柄失败\n");
return -1;
}
// 打开设备
nRet = MV_CC_OpenDevice(handle);
if (nRet != MV_OK) {
printf("打开设备失败\n");
return -1;
}
大恒相机的SDK,说实话,接口命名风格不太一样,但逻辑是相通的:
// 大恒相机SDK初始化
IGXDevice* m_pDevice = NULL;
int32_t eDevType = DEVICE_TYPE_GIGE;
// 枚举设备
GX_STATUS emStatus = GXUpdateDeviceList(&m_nDeviceNum, 1000);
if (emStatus != GX_STATUS_SUCCESS || m_nDeviceNum == 0) {
printf("未找到设备\n");
return -1;
}
// 打开设备
emStatus = GXOpenDeviceByIndex(1, &m_pDevice);
if (emStatus != GX_STATUS_SUCCESS) {
printf("打开设备失败\n");
return -1;
}
我的小习惯:初始化时,我会先判断设备数量。如果为0,直接提示用户检查网线或USB线。很多问题其实就出在物理连接上。
4.2 参数设置——曝光、增益、触发模式
相机打开了,接下来就是调参数。这三个参数你必须要搞明白:曝光、增益、触发模式。
4.2.1 曝光时间
曝光时间决定了传感器收集光线的时间。时间越长,图像越亮,但运动物体容易模糊。
我在做流水线检测项目时,产品移动速度很快,曝光时间必须控制在500微秒以内。否则图像全是拖影,根本没法用。
// 海康:设置曝光时间(单位:微秒)
MVCC_FLOATVALUE stExposure;
stExposure.fCurValue = 500.0; // 500微秒
nRet = MV_CC_SetFloatValue(handle, "ExposureTime", stExposure.fCurValue);
// 大恒:设置曝光时间
GX_STATUS emStatus = GXSetFloat(m_pDevice, "ExposureTime", 500.0);
4.2.2 增益
增益说白了就是放大信号。光线不足时,提高增益能让图像变亮。但代价是噪声也会被放大。
你想想看,增益调到16dB以上,图像上的噪点就跟雪花似的。所以我一般建议:优先调曝光,实在不行再加增益。
// 海康:设置增益
MVCC_FLOATVALUE stGain;
stGain.fCurValue = 8.0; // 8dB
nRet = MV_CC_SetFloatValue(handle, "Gain", stGain.fCurValue);
// 大恒:设置增益
emStatus = GXSetFloat(m_pDevice, "Gain", 8.0);
注意:增益不要超过12dB。我曾经为了省一个补光灯,把增益调到18dB,结果图像噪声大到算法误检率飙升。后来老老实实加了光源,增益降到6dB,问题全解决了。
4.2.3 触发模式
触发模式有两种:连续采集和外部触发。连续采集就是相机自己不停地拍,适合实时预览。外部触发则是等一个信号来了才拍,适合流水线场景。
// 海康:设置为连续采集模式
nRet = MV_CC_SetEnumValue(handle, "TriggerMode", MV_TRIGGER_MODE_OFF);
// 海康:设置为外部触发模式
nRet = MV_CC_SetEnumValue(handle, "TriggerMode", MV_TRIGGER_MODE_ON);
nRet = MV_CC_SetEnumValue(handle, "TriggerSource", MV_TRIGGER_SOURCE_LINE0);
// 大恒:设置为连续采集
emStatus = GXSetEnum(m_pDevice, "TriggerMode", GX_TRIGGER_MODE_OFF);
// 大恒:设置为外部触发
emStatus = GXSetEnum(m_pDevice, "TriggerMode", GX_TRIGGER_MODE_ON);
emStatus = GXSetEnum(m_pDevice, "TriggerSource", GX_TRIGGER_SOURCE_LINE0);
4.3 实时预览与保存
参数调好了,接下来就是让图像在屏幕上显示出来,并且能保存到本地。
实时预览的核心逻辑其实就三步:开始采集、循环取图、显示图像。保存的话,就是在取到图像后,调用保存接口。
// 海康:开始采集
nRet = MV_CC_StartGrabbing(handle);
// 循环取图
MV_FRAME_OUT stImageInfo = {0};
nRet = MV_CC_GetImageBuffer(handle, &stImageInfo, 1000);
if (nRet == MV_OK) {
// 在这里显示图像(用OpenCV或Qt等)
// 保存图像
nRet = MV_CC_SaveImageToFile(handle, "capture.bmp", &stImageInfo);
MV_CC_FreeImageBuffer(handle, &stImageInfo);
}
// 停止采集
MV_CC_StopGrabbing(handle);
大恒的流程也类似:
// 大恒:开始采集
emStatus = GXStreamOn(m_pDevice);
// 取图
PGX_FRAME_BUFFER pFrameBuffer = NULL;
emStatus = GXDQBuf(m_pDevice, &pFrameBuffer, 1000);
if (emStatus == GX_STATUS_SUCCESS) {
// 显示图像
// 保存图像
emStatus = GXSaveImageToFile(m_pDevice, "capture.bmp", pFrameBuffer->nImgSize,
pFrameBuffer->imgBuf, IMAGE_FORMAT_BMP);
GXQBuf(m_pDevice, pFrameBuffer); // 释放缓冲区
}
// 停止采集
emStatus = GXStreamOff(m_pDevice);
避坑指南:我曾经在保存图像时,忘了释放缓冲区,结果内存越占越多,程序跑了半小时就崩了。记住:取完图一定要调用释放接口,不管是海康的MV_CC_FreeImageBuffer还是大恒的GXQBuf。
4.4 知识体系总览
下面这张图,把整个相机SDK调用的流程串起来了。你可以把它当作一个检查清单,每次写代码时对照着来:
4.5 完整代码示例
最后,给你一个完整的海康相机采集与保存的代码框架。你可以直接拿去改:
#include <stdio.h>
#include "MvCameraControl.h"
int main() {
// 1. 初始化SDK
MV_CC_Initialize();
// 2. 枚举设备
MV_CC_DEVICE_INFO_LIST stList;
MV_CC_EnumDevices(MV_GIGE_DEVICE | MV_USB_DEVICE, &stList);
if (stList.nDeviceNum == 0) {
printf("未找到相机,请检查连接\n");
return -1;
}
// 3. 创建句柄并打开设备
void* handle = NULL;
MV_CC_CreateHandle(&handle, stList.pDeviceInfo[0]);
MV_CC_OpenDevice(handle);
// 4. 设置参数
MV_CC_SetFloatValue(handle, "ExposureTime", 500.0); // 500微秒
MV_CC_SetFloatValue(handle, "Gain", 8.0); // 8dB
MV_CC_SetEnumValue(handle, "TriggerMode", MV_TRIGGER_MODE_OFF); // 连续采集
// 5. 开始采集
MV_CC_StartGrabbing(handle);
// 6. 取图并保存
MV_FRAME_OUT stImageInfo;
if (MV_CC_GetImageBuffer(handle, &stImageInfo, 1000) == MV_OK) {
MV_CC_SaveImageToFile(handle, "capture.bmp", &stImageInfo);
MV_CC_FreeImageBuffer(handle, &stImageInfo);
printf("图像已保存为 capture.bmp\n");
}
// 7. 停止并释放
MV_CC_StopGrabbing(handle);
MV_CC_CloseDevice(handle);
MV_CC_DestroyHandle(handle);
MV_CC_Finalize();
return 0;
}
嗯,到这里,相机SDK调用的核心内容就讲完了。你只要照着这个流程走一遍,基本不会出大问题。如果遇到报错,先检查物理连接,再看初始化有没有做,最后看参数范围对不对——90%的问题都能解决。
公众号:蓝海资料掘金营,微信deep3321