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调用的流程串起来了。你可以把它当作一个检查清单,每次写代码时对照着来:

相机SDK调用完整流程 1. SDK初始化 枚举设备 → 创建句柄 2. 参数设置 曝光时间 | 增益 | 触发模式 (连续/外部触发) 3. 开始采集 StartGrabbing 4. 循环取图 GetImageBuffer / DQBuf (注意:取图后必须释放缓冲区) 5. 图像处理与保存 显示 → 保存为BMP/JPEG/PNG 6. 停止采集 & 释放资源 ⚠️ 常见问题 • 未初始化就调用接口 • 忘记释放缓冲区 • 触发信号没接好 💡 我的建议 • 先调曝光,后调增益 • 增益不超过12dB • 取图超时设1000ms

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