一、前言
在机器视觉、自动化检测、智能制造等领域,工业相机是获取图像数据的核心设备。海康威视作为国内领先的机器视觉厂商,其工业相机产品线丰富,广泛应用于各类工业场景。
本文将带你从零开始,使用 海康MVS SDK(Machine Vision Software Development Kit),通过 C/C++语言 实现对海康工业相机的控制,重点演示如何配置 软件触发模式 并完成图像采集。
✅ 本文特点:全中文输出日志详细中文注释结构清晰,适合初学者基于真实SDK接口编写
二、开发环境准备
-
硬件准备
海康威视工业相机(GigE / USB3.0 均可)
网线(GigE)或 USB3.0 线(USB)
相机已正确连接并上电 -
软件准备
操作系统:Linux(Ubuntu/CentOS)或 Windows
安装 MVS客户端软件(含SDK开发包)
开发工具:GCC / G++ / Visual Studio
头文件:MvCameraControl.h
库文件:libMvCameraControl.so(Linux)或 MvCameraControl.dll/.lib(Windows)
三、核心功能流程
我们实现的功能流程如下:
初始化SDK → 枚举设备 → 选择相机 → 打开设备 → 配置参数 → 启动取流 → 软件触发采集 → 停止取流 → 关闭设备 → 反初始化
本文重点讲解 软件触发模式 的配置与使用。
四、什么是“软件触发”?
工业相机通常有以下几种工作模式:
模式 说明
连续采集 相机持续输出图像
触发采集 只有收到“触发信号”才采集一帧
而“软件触发”是触发模式中的一种,指通过 调用SDK函数发送命令 来触发相机采集一帧图像,无需外部硬件信号,非常适合调试和控制场景。
🔧 触发流程:设置 TriggerMode = On设置 TriggerSource = Software调用 MV_CC_SetCommandValue("TriggerSoftware") 发送触发命令调用 MV_CC_GetImageBuffer() 获取图像
五、完整代码实现(含中文注释)
以下是完整的 C++ 示例代码,实现了上述功能。
/*整个流程大概为:
// 1. 先开启触发模式
camera.SetEnumValue("TriggerMode", 1); // 1 = On// 2. 设置触发源为软件触发
int mode = 5; // Software
camera.SetEnumValue("TriggerSource", mode);// 3. 发送一次软件触发命令
camera.SetCommandValue("TriggerSoftware");// 4. (可选)关闭触发
// camera.SetEnumValue("TriggerMode", 0); // 0 = Off
*/
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
#include "MvCameraControl.h"// 全局变量:用于控制程序退出
bool g_bExit = false;// 提示用户按回车键退出程序
void PressEnterToExit(void)
{int c;// 清空输入缓冲区中的换行符或残留字符while ((c = getchar()) != '\n' && c != EOF);fprintf(stderr, "\n请按回车键退出程序。\n");// 等待用户真正按下回车while (getchar() != '\n');g_bExit = true; // 设置退出标志sleep(1); // 给线程留出退出时间
}/*** 打印设备基本信息(根据设备类型不同,信息结构不同)* @param pstMVDevInfo: 设备信息结构体指针* @return 成功返回 true,失败返回 false*/
bool PrintDeviceInfo(MV_CC_DEVICE_INFO* pstMVDevInfo)
{if (NULL == pstMVDevInfo){printf("错误:设备信息指针为空!\n");return false;}// 根据设备传输层类型(GigE、USB、CameraLink 等)打印不同信息if (pstMVDevInfo->nTLayerType == MV_GIGE_DEVICE){// GigE 相机:打印IP、型号、用户自定义名int nIp1 = ((pstMVDevInfo->SpecialInfo.stGigEInfo.nCurrentIp & 0xff000000) >> 24);int nIp2 = ((pstMVDevInfo->SpecialInfo.stGigEInfo.nCurrentIp & 0x00ff0000) >> 16);int nIp3 = ((pstMVDevInfo->SpecialInfo.stGigEInfo.nCurrentIp & 0x0000ff00) >> 8);int nIp4 = (pstMVDevInfo->SpecialInfo.stGigEInfo.nCurrentIp & 0x000000ff);printf("设备型号名称: %s\n", pstMVDevInfo->SpecialInfo.stGigEInfo.chModelName);printf("当前IP地址: %d.%d.%d.%d\n", nIp1, nIp2, nIp3, nIp4);printf("用户自定义名称: %s\n\n", pstMVDevInfo->SpecialInfo.stGigEInfo.chUserDefinedName);}else if (pstMVDevInfo->nTLayerType == MV_USB_DEVICE){// USB 相机:打印型号和用户自定义名printf("设备型号名称: %s\n", pstMVDevInfo->SpecialInfo.stUsb3VInfo.chModelName);printf("用户自定义名称: %s\n\n", pstMVDevInfo->SpecialInfo.stUsb3VInfo.chUserDefinedName);}else if (pstMVDevInfo->nTLayerType == MV_GENTL_GIGE_DEVICE){// GenTL GigE 设备printf("用户自定义名称: %s\n", pstMVDevInfo->SpecialInfo.stGigEInfo.chUserDefinedName);printf("序列号: %s\n", pstMVDevInfo->SpecialInfo.stGigEInfo.chSerialNumber);printf("型号名称: %s\n\n", pstMVDevInfo->SpecialInfo.stGigEInfo.chModelName);}else if (pstMVDevInfo->nTLayerType == MV_GENTL_CAMERALINK_DEVICE){// CameraLink 设备printf("用户自定义名称: %s\n", pstMVDevInfo->SpecialInfo.stCMLInfo.chUserDefinedName);printf("序列号: %s\n", pstMVDevInfo->SpecialInfo.stCMLInfo.chSerialNumber);printf("型号名称: %s\n\n", pstMVDevInfo->SpecialInfo.stCMLInfo.chModelName);}else if (pstMVDevInfo->nTLayerType == MV_GENTL_CXP_DEVICE){// CoaXPress 设备printf("用户自定义名称: %s\n", pstMVDevInfo->SpecialInfo.stCXPInfo.chUserDefinedName);printf("序列号: %s\n", pstMVDevInfo->SpecialInfo.stCXPInfo.chSerialNumber);printf("型号名称: %s\n\n", pstMVDevInfo->SpecialInfo.stCXPInfo.chModelName);}else if (pstMVDevInfo->nTLayerType == MV_GENTL_XOF_DEVICE){// XoF 设备(如光纤)printf("用户自定义名称: %s\n", pstMVDevInfo->SpecialInfo.stXoFInfo.chUserDefinedName);printf("序列号: %s\n", pstMVDevInfo->SpecialInfo.stXoFInfo.chSerialNumber);printf("型号名称: %s\n\n", pstMVDevInfo->SpecialInfo.stXoFInfo.chModelName);}else{printf("不支持的设备类型!\n");}return true;
}/*** 工作线程函数:用于持续获取图像帧* 在触发模式下,每发送一次软件触发,就尝试获取一帧图像* @param pUser: 传入的相机句柄(void* 类型)* @return 线程返回值(此处为 NULL)*/
static void* WorkThread(void* pUser)
{int nRet = MV_OK;MV_FRAME_OUT stImageInfo = {0}; // 存储图像数据和信息的结构体memset(&stImageInfo, 0, sizeof(MV_FRAME_OUT));while(1){// 1. 发送软件触发命令,通知相机采集一帧图像nRet = MV_CC_SetCommandValue(pUser, "TriggerSoftware");if (MV_OK != nRet){printf("发送软件触发命令失败!错误码: [0x%x]\n", nRet);}// 2. 获取图像缓冲区(等待最多1000ms)nRet = MV_CC_GetImageBuffer(pUser, &stImageInfo, 1000);if (nRet == MV_OK){// 成功获取图像printf("成功获取一帧图像:宽度[%d],高度[%d],帧号[%d]\n",stImageInfo.stFrameInfo.nWidth,stImageInfo.stFrameInfo.nHeight,stImageInfo.stFrameInfo.nFrameNum);// 使用完图像缓冲区后必须释放,否则内存泄漏MV_CC_FreeImageBuffer(pUser, &stImageInfo);}else{printf("获取图像失败!错误码: [0x%x]\n", nRet);}// 检查是否收到退出信号if (g_bExit){break;}}return 0;
}/*** 主函数:实现海康相机的初始化、配置、触发采集、停止和释放资源*/
int main()
{int nRet = MV_OK;void* handle = NULL; // 相机设备句柄do {// 1. 初始化 SDK(必须首先调用)nRet = MV_CC_Initialize();if (MV_OK != nRet){printf("初始化SDK失败!错误码: [0x%x]\n", nRet);break;}printf("SDK初始化成功。\n");// 2. 定义设备列表结构体并清零MV_CC_DEVICE_INFO_LIST stDeviceList;memset(&stDeviceList, 0, sizeof(MV_CC_DEVICE_INFO_LIST));// 3. 枚举当前连接的所有支持的相机设备nRet = MV_CC_EnumDevices(MV_GIGE_DEVICE | MV_USB_DEVICE | MV_GENTL_CAMERALINK_DEVICE | MV_GENTL_CXP_DEVICE | MV_GENTL_XOF_DEVICE, &stDeviceList);if (MV_OK != nRet){printf("枚举设备失败!错误码: [0x%x]\n", nRet);break;}// 4. 检查是否找到设备if (stDeviceList.nDeviceNum > 0){printf("共发现 %d 台设备:\n", stDeviceList.nDeviceNum);for (int i = 0; i < stDeviceList.nDeviceNum; i++){printf("[设备 %d]:\n", i);MV_CC_DEVICE_INFO* pDeviceInfo = stDeviceList.pDeviceInfo[i];if (NULL == pDeviceInfo){printf("设备信息为空,跳过。\n");continue;}PrintDeviceInfo(pDeviceInfo); // 打印每台设备的详细信息}}else{printf("未发现任何相机设备!\n");break;}// 5. 提示用户选择要操作的相机printf("请输入要打开的相机序号: ");unsigned int nIndex = 0;scanf("%d", &nIndex);if (nIndex >= stDeviceList.nDeviceNum){printf("输入的序号无效!\n");break;}// 6. 根据用户选择创建设备句柄nRet = MV_CC_CreateHandle(&handle, stDeviceList.pDeviceInfo[nIndex]);if (MV_OK != nRet){printf("创建设备句柄失败!错误码: [0x%x]\n", nRet);break;}printf("设备句柄创建成功。\n");// 7. 打开设备(建立通信)nRet = MV_CC_OpenDevice(handle);if (MV_OK != nRet){printf("打开设备失败!错误码: [0x%x]\n", nRet);break;}printf("设备打开成功。\n");// 8. (仅对GigE相机)设置最优网络包大小,提升传输效率if (stDeviceList.pDeviceInfo[nIndex]->nTLayerType == MV_GIGE_DEVICE){int nPacketSize = MV_CC_GetOptimalPacketSize(handle);if (nPacketSize > 0){nRet = MV_CC_SetIntValueEx(handle, "GevSCPSPacketSize", nPacketSize);if (nRet != MV_OK){printf("警告:设置网络包大小失败!错误码: [0x%x]\n", nRet);}else{printf("已设置最优网络包大小为: %d 字节。\n", nPacketSize);}}else{printf("警告:获取最优网络包大小失败!返回值: [0x%x]\n", nPacketSize);}}// 9. 关闭采集帧率控制(使用默认帧率)nRet = MV_CC_SetBoolValue(handle, "AcquisitionFrameRateEnable", false);if (MV_OK != nRet){printf("设置采集帧率使能失败!错误码: [0x%x]\n", nRet);break;}// 10. 设置为触发模式:开启nRet = MV_CC_SetEnumValue(handle, "TriggerMode", 1); // 1 表示开启if (MV_OK != nRet){printf("设置触发模式失败!错误码: [0x%x]\n", nRet);break;}printf("已设置为触发模式(开启)。\n");// 11. 设置触发源为“软件触发”nRet = MV_CC_SetEnumValue(handle, "TriggerSource", MV_TRIGGER_SOURCE_SOFTWARE);if (MV_OK != nRet){printf("设置触发源为软件触发失败!错误码: [0x%x]\n", nRet);break;}printf("已设置触发源为:软件触发。\n");// 12. 开始取流(启动图像采集)nRet = MV_CC_StartGrabbing(handle);if (MV_OK != nRet){printf("启动取流失败!错误码: [0x%x]\n", nRet);break;}printf("开始取流...\n");// 13. 创建工作线程,用于在后台获取图像pthread_t nThreadID;nRet = pthread_create(&nThreadID, NULL, WorkThread, handle);if (nRet != 0){printf("创建工作线程失败!返回值: %d\n", nRet);break;}printf("工作线程已启动。\n");// 14. 等待用户按回车键退出PressEnterToExit();// 15. 停止取流nRet = MV_CC_StopGrabbing(handle);if (MV_OK != nRet){printf("停止取流失败!错误码: [0x%x]\n", nRet);break;}printf("已停止取流。\n");// 16. 关闭设备nRet = MV_CC_CloseDevice(handle);if (MV_OK != nRet){printf("关闭设备失败!错误码: [0x%x]\n", nRet);break;}printf("设备已关闭。\n");// 17. 销毁设备句柄nRet = MV_CC_DestroyHandle(handle);if (MV_OK != nRet){printf("销毁设备句柄失败!错误码: [0x%x]\n", nRet);break;}handle = NULL;printf("设备句柄已销毁。\n");} while (0); // 使用 do-while(0) 实现错误时 break 跳出// 异常退出时确保资源释放if (handle != NULL){MV_CC_DestroyHandle(handle);handle = NULL;printf("异常退出,已销毁设备句柄。\n");}// 18. 反初始化 SDK(释放全局资源)MV_CC_Finalize();printf("SDK已反初始化,程序退出。\n");return 0;
}
Demo: Trigger_Image.cppg++ -g -o Trigger_Image Trigger_Image.cpp -I../../../../../include -Wl,-rpath=$(MVCAM_COMMON_RUNENV)/64 -L$(MVCAM_COMMON_RUNENV)/64 -lMvCameraControl -lpthreadclean:rm Trigger_Image -rf
✅ SDK初始化成功。
发现 1 台设备:
[设备 0]:
设备型号: MV-CA060-10GC
IP地址: 192.168.1.100
自定义名: Camera_01请输入设备编号: 0
✅ 设备打开成功。
✅ 网络包大小设置为: 1500
✅ 已设置为软件触发模式。
✅ 开始取流,发送软件触发采集图像...✅ 获取图像成功:宽[2448] 高[2048] 帧号[1]
✅ 获取图像成功:宽[2448] 高[2048] 帧号[2]
✅ 获取图像成功:宽[2448] 高[2048] 帧号[3]请按回车键退出程序。✅ 程序退出。
结语
本文通过一个完整示例,展示了如何使用海康MVS SDK 实现 软件触发图像采集,代码结构清晰,注释详尽,适合初学者快速上手。
掌握这套流程后,你可以进一步实现 硬件触发、多相机同步、图像处理流水线 等高级功能。