使用海康威视 SDK 实现软触发拍照(C语言完整示例 + 中文注释)
🌟 前言
在机器视觉项目中,我们经常需要通过 软件触发(Software Trigger) 来控制工业相机在特定时刻拍摄图像。相比连续采集(Continuous Mode),触发模式(Trigger Mode) 更加精准、节能,适用于流水线检测、运动同步等场景。
本文基于 海康威视(Hikvision)官方 MVS SDK,使用 C 语言实现一个完整的 软触发拍照程序,并提供:
✅ 完整可运行代码
✅ 中文详细注释
✅ 所有打印信息中文化
✅ 常见问题解析(如只拍一张就停)
⚠️ 本文代码在 Linux 下测试通过,Windows 用户需调整线程和头文件路径。
📦 程序功能概述
本程序实现以下功能:
枚举所有连接的海康相机
用户选择目标相机
设置为 软触发模式(Software Trigger)
启动取流
创建工作线程,每收到一帧图像就发送一次软触发
图像回调函数中打印图像信息
按回车键退出程序🔁 实现“拍一张 → 处理 → 再拍一张”的典型控制逻辑。
💻 完整代码(含中文注释)
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
#include "MvCameraControl.h"// 全局标志:是否已获取图像帧(用于控制触发节奏)
bool g_bIsGetImage = true;// 全局标志:是否退出程序
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); // 给线程留出退出时间
}/*** 打印设备信息(IP、型号、用户自定义名称)* 支持 GigE 和 USB 相机*/
bool PrintDeviceInfo(MV_CC_DEVICE_INFO* pstMVDevInfo)
{if (NULL == pstMVDevInfo){printf("设备信息指针为空!\n");return false;}if (pstMVDevInfo->nTLayerType == MV_GIGE_DEVICE){// 解析当前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){printf("设备型号名称: %s\n", pstMVDevInfo->SpecialInfo.stUsb3VInfo.chModelName);printf("用户自定义名称: %s\n\n", pstMVDevInfo->SpecialInfo.stUsb3VInfo.chUserDefinedName);}else{printf("不支持的设备类型。\n");}return true;
}/*** 图像数据回调函数* 当相机捕获到一帧图像时,SDK 会自动调用此函数*/
void __stdcall ImageCallBackEx(unsigned char* pData, MV_FRAME_OUT_INFO_EX* pFrameInfo, void* pUser)
{if (pFrameInfo){printf("成功获取一帧图像,宽度[%d],高度[%d],帧编号[%d]\n",pFrameInfo->nExtendWidth, pFrameInfo->nExtendHeight, pFrameInfo->nFrameNum);// 模拟图像处理耗时(可选)// sleep(1);g_bIsGetImage = true; // 标记:已收到新图像,可以再次触发}
}/*** 工作线程:循环发送软触发命令* 只有在上一帧图像被处理后才发送下一次触发*/
static void* WorkThread(void* pUser)
{while (1){if (g_bExit){break;}if (true == g_bIsGetImage){// 发送软触发命令,告诉相机“现在拍一张照片”int nRet = MV_CC_SetCommandValue(pUser, "TriggerSoftware");/*int nRet = MV_CC_SetCommandValue(pUser, "TriggerSoftware");这行代码的意思是:“SDK,请告诉相机:现在拍一张照片!”🟢 执行完这行代码后:你的程序不会等待相机拍照而是立即返回,继续循环相机开始拍照(需要几毫秒到几十毫秒)*/if (MV_OK != nRet){printf("软触发失败,错误码[0x%x]\n", nRet);}else{printf("软触发成功,已发送拍照指令");// 去掉此处 sleep,避免阻塞触发逻辑printf(" [软触发成功,当前g_bIsGetImage=%d]\n", g_bIsGetImage);g_bIsGetImage = false; // 重置标志,等待下一帧图像到达}}else{printf("[等待图像返回中,g_bIsGetImage=%d]\n", g_bIsGetImage);sleep(1); // 避免空转占用CPUcontinue;}}return NULL;
}int main()
{int nRet = MV_OK;void* handle = NULL; // 相机句柄do{// 初始化 SDKnRet = MV_CC_Initialize();if (MV_OK != nRet){printf("初始化SDK失败!错误码 [0x%x]\n", nRet);break;}// 设备信息列表MV_CC_DEVICE_INFO_LIST stDeviceList;memset(&stDeviceList, 0, sizeof(MV_CC_DEVICE_INFO_LIST));// 枚举连接的相机设备nRet = MV_CC_EnumDevices(MV_GIGE_DEVICE | MV_USB_DEVICE, &stDeviceList);if (MV_OK != nRet){printf("枚举设备失败!错误码 [0x%x]\n", nRet);break;}if (stDeviceList.nDeviceNum > 0){for (int i = 0; i < stDeviceList.nDeviceNum; i++){printf("[设备 %d]:\n", i);MV_CC_DEVICE_INFO* pDeviceInfo = stDeviceList.pDeviceInfo[i];if (NULL == pDeviceInfo){break;}PrintDeviceInfo(pDeviceInfo);}}else{printf("未找到任何相机设备!\n");break;}// 用户选择相机索引printf("请输入要打开的相机编号: ");unsigned int nIndex = 0;scanf("%d", &nIndex);if (nIndex >= stDeviceList.nDeviceNum){printf("输入的编号无效!\n");break;}// 创建设备句柄nRet = MV_CC_CreateHandle(&handle, stDeviceList.pDeviceInfo[nIndex]);if (MV_OK != nRet){printf("创建设备句柄失败!错误码 [0x%x]\n", nRet);break;}// 打开设备nRet = MV_CC_OpenDevice(handle);if (MV_OK != nRet){printf("打开设备失败!错误码 [0x%x]\n", nRet);break;}// 如果是 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("警告:获取最优数据包大小失败,错误码 [0x%x]!\n", nPacketSize);}}// 关闭帧率控制(使用触发模式时通常关闭)nRet = MV_CC_SetBoolValue(handle, "AcquisitionFrameRateEnable", false);if (MV_OK != nRet){printf("设置帧率使能失败!错误码 [0x%x]\n", nRet);break;}// 设置触发模式为“开启”nRet = MV_CC_SetEnumValue(handle, "TriggerMode", 1); // 1 = Onif (MV_OK != nRet){printf("设置触发模式失败!错误码 [0x%x]\n", nRet);break;}// 设置触发源为“软件触发”nRet = MV_CC_SetEnumValue(handle, "TriggerSource", MV_TRIGGER_SOURCE_SOFTWARE);if (MV_OK != nRet){printf("设置触发源失败!错误码 [0x%x]\n", nRet);break;}// 打印当前采集模式(Continuous=3, MultiFrame=2, SingleFrame=1)MVCC_ENUMVALUE stEnum = {0};nRet = MV_CC_GetEnumValue(handle, "AcquisitionMode", &stEnum);if (MV_OK == nRet){printf("当前采集模式 AcquisitionMode = %d", stEnum.nCurValue);if (stEnum.nCurValue == 1) printf(" (单帧模式)");else if (stEnum.nCurValue == 2) printf(" (多帧模式)");else if (stEnum.nCurValue == 3) printf(" (连续模式)");printf("\n");}else{printf("获取采集模式失败,错误码 = 0x%x\n", nRet);printf("可能该相机不支持此参数,或已被弃用\n");}// 注册图像数据回调函数nRet = MV_CC_RegisterImageCallBackEx(handle, ImageCallBackEx, handle);if (MV_OK != nRet){printf("注册图像回调函数失败!错误码 [0x%x]\n", nRet);break;}// 开始取流(启动图像采集)nRet = MV_CC_StartGrabbing(handle);if (MV_OK != nRet){printf("开始取流失败!错误码 [0x%x]\n", nRet);break;}// 创建工作线程用于发送软触发pthread_t nThreadID;nRet = pthread_create(&nThreadID, NULL, WorkThread, handle);if (nRet != 0){printf("创建线程失败,返回值 = %d\n", nRet);break;}// 等待用户按回车退出PressEnterToExit();// 停止取流nRet = MV_CC_StopGrabbing(handle);if (MV_OK != nRet){printf("停止取流失败!错误码 [0x%x]\n", nRet);break;}// 关闭设备nRet = MV_CC_CloseDevice(handle);if (MV_OK != nRet){printf("关闭设备失败!错误码 [0x%x]\n", nRet);break;}// 销毁句柄nRet = MV_CC_DestroyHandle(handle);if (MV_OK != nRet){printf("销毁句柄失败!错误码 [0x%x]\n", nRet);break;}handle = NULL;} while (0);// 异常退出时确保句柄被销毁if (handle != NULL){MV_CC_DestroyHandle(handle);handle = NULL;}// 反初始化SDKMV_CC_Finalize();printf("程序退出。\n");return 0;
}
📊 运行效果
[设备 0]:
设备型号: MV-CA060-10GC
当前IP: 192.168.1.100
用户名称: Camera_01请输入相机编号: 0
📊 当前采集模式 = 1 (单帧)
✅ 成功获取图像,宽[4024],高[3036],帧号[1]
🟢 软触发成功,已发送拍照指令 [g_bIsGetImage=1]
✅ 成功获取图像,宽[4024],高[3036],帧号[2]
...
请按回车键退出程序...
🎯 总结
本文提供了一个 稳定、可扩展的软触发相机控制框架,适用于:
工业检测
机器人抓取
触发同步
图像采集控制
你只需要在此基础上:
添加图像保存(BMP/PNG)
集成 OpenCV 处理
使用队列缓存图像
支持多相机
即可构建完整的视觉系统。