一、前言

在机器视觉、自动化检测、智能制造等领域,工业相机是获取图像数据的核心设备。海康威视作为国内领先的机器视觉厂商,其工业相机产品线丰富,广泛应用于各类工业场景。

本文将带你从零开始,使用 海康MVS SDK(Machine Vision Software Development Kit),通过 C/C++语言 实现对海康工业相机的控制,重点演示如何配置 软件触发模式 并完成图像采集。

✅ 本文特点:全中文输出日志详细中文注释结构清晰,适合初学者基于真实SDK接口编写

二、开发环境准备

  1. 硬件准备

    海康威视工业相机(GigE / USB3.0 均可)
    网线(GigE)或 USB3.0 线(USB)
    相机已正确连接并上电

  2. 软件准备

    操作系统: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 实现 软件触发图像采集,代码结构清晰,注释详尽,适合初学者快速上手。

掌握这套流程后,你可以进一步实现 硬件触发、多相机同步、图像处理流水线 等高级功能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/98033.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/98033.shtml
英文地址,请注明出处:http://en.pswp.cn/diannao/98033.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Modbus RTU 协议介绍

Modbus RTU 协议介绍 异步串行传输方式&#xff0c;采用二进制格式&#xff0c;适用于串行通讯&#xff08;如RS-485&#xff09;&#xff0c;效率高&#xff0c;是工业现场的主流选择。 主站是Master&#xff0c;从站是Slave。 Modbus RTU 协议格式 帧结构 地址码&#xf…

TCP/IP函数——sendmsg

sendmsg() 是 POSIX 标准中一个高级套接字发送函数,属于系统调用(由操作系统内核实现),定义在 <sys/socket.h> 头文件中。它的核心特点是支持复杂消息结构,不仅能发送常规数据,还能附加控制信息(如辅助数据、IP 选项等),适用于 TCP、UDP 等多种协议,功能比 sen…

运动控制中的插值运动(插补运动):原理、实现与应用

在自动化设备中,从起点到终点的精准轨迹控制是核心需求。当目标轨迹是直线、圆弧或复杂曲线时,仅通过离散的目标点无法实现平滑运动,这就需要插值运动(Interpolation Motion)技术 —— 通过控制算法在已知路径点之间计算出连续的中间点,使运动部件沿预定轨迹平滑移动。本…

GMT——用于人形全身控制的通用运动跟踪:两阶段师生训练框架下,全身基于单一策略,且自适应采样、MoE架构

前言 如此文《KungfuBot——基于物理约束和自适应运动追踪的人形全身控制PBHC&#xff0c;用于学习打拳或跳舞(即RL下的动作模仿和运控)》的开头所说 如此&#xff0c;便关注到最新出来的三个工作 第一个是GMT: General Motion Tracking for Humanoid Whole-Body Control第二个…

matlab版本粒子群算法(PSO)在路径规划中的应用

基于粒子群优化&#xff08;PSO&#xff09;算法的路径规划 MATLAB代码实现 1. 初始化环境和参数 % 初始化环境参数 mapSize [10, 10]; % 地图大小 startPoint [1, 1]; % 起点 endPoint [9, 9]; % 终点 obstacles [3, 3; 5, 5; 7, 7]; % 障碍物位置% PSO参数 numParticles …

Go语言面试:传值与传引用的区别及选择指南

在Go语言中&#xff0c;函数参数的传递方式有两种&#xff1a;传值&#xff08;pass-by-value&#xff09;和传引用&#xff08;pass-by-reference&#xff09;。理解这两种方式的区别及其适用场景&#xff0c;是成为Go语言开发高手的必备技能。本文将深入探讨Go语言中传值与传…

数据无言,网关有声 耐达讯自动化RS485转Profinet让千年液位数据“开口说话”

在能源行业的数字化转型浪潮中&#xff0c;你是否曾面临这样的困境&#xff1a; 现场大量采用RS485接口的液位计&#xff0c;数据孤立如信息孤岛&#xff0c;无法接入Profinet高速网络&#xff1f; 模拟信号传输距离受限&#xff0c;抗干扰能力弱&#xff0c;导致液位测量误差…

出口退税新政大提速:企业如何抓住政策红利,提升最高13%纯利?

近年来&#xff0c;出口退税政策的优化与升级&#xff0c;正在成为外贸企业提升资金周转率和利润率的关键。国家税务总局发布的 2022年第9号公告&#xff08;简称“9号公告”&#xff09;落地执行已两年&#xff0c;外贸行业普遍感受到退税速度显著加快&#xff0c;平均退税周期…

使用pytorch创建/训练/推理OCR模型

一、任务描述 从手写数字图像中自动识别出对应的数字&#xff08;0-9&#xff09;” 的问题&#xff0c;属于单标签图像分类任务&#xff08;每张图像仅对应一个类别&#xff0c;即 0-9 中的一个数字&#xff09; 1、任务的核心定义&#xff1a;输入与输出 输入&#xff1a;28…

新启航开启深孔测量新纪元:激光频率梳技术攻克光学遮挡,达 130mm 深度 2μm 精度

摘要&#xff1a;本文聚焦于深孔测量领域&#xff0c;介绍了一种创新的激光频率梳技术。该技术成功攻克传统测量中的光学遮挡难题&#xff0c;在深孔测量深度达 130mm 时&#xff0c;可实现 2μm 的高精度测量&#xff0c;为深孔测量开启了新的发展篇章。关键词&#xff1a;激光…

GEO优化推荐:AI搜索新纪元下的品牌内容权威构建

引言&#xff1a;AI搜索引擎崛起与GEO策略的战略重心转移2025年&#xff0c;以ChatGPT、百度文心一言、DeepSeek为代表的AI搜索引擎已深入成为公众信息获取的核心渠道。这标志着品牌营销策略的重心&#xff0c;正从传统的搜索引擎优化&#xff08;SEO&#xff09;加速向生成式引…

uniapp的上拉加载H5和小程序

小程序配置{"path": "list/course-list","style": {"navigationBarTitleText": "课程列表","enablePullDownRefresh": true,"onReachBottomDistance": 150}}上拉拉触底钩子onReachBottom() {var that …

【和春笋一起学C++】(四十)抽象数据类型

抽象数据类型&#xff08;abstract data type, ADT&#xff09;以通用的方式描述数据类型。C中类的概念非常适合于ADT方法。例如&#xff0c;C程序通过堆栈来管理自动变量&#xff0c;堆栈可由对它执行的操作来描述。可创建空堆栈&#xff1b;可将数据项添加到堆顶&#xff08;…

大文件断点续传解决方案:基于Vue 2与Spring Boot的完整实现

大文件断点续传解决方案:基于Vue 2与Spring Boot的完整实现 在现代Web应用中,大文件上传是一个常见但具有挑战性的需求。传统的文件上传方式在面对网络不稳定、大文件传输时往往表现不佳。本文将详细介绍如何实现一个支持断点续传的大文件上传功能,结合Vue 2前端和Spring Bo…

LeNet-5:手写数字识别经典CNN

配套讲解视频&#xff0c;点击下方名片获取20 世纪 90 年代&#xff0c;计算机已经能识别文本&#xff0c;但图片识别很困难。比如银行支票的手写数字识别&#xff0c;传统方法需要人工设计规则&#xff0c;费时费力且精度不高。 于是&#xff0c;Yann LeCun 及其团队提出了 Le…

如何在 C# 中将文本转换为 Word 以及将 Word 转换为文本

在现代软件开发中&#xff0c;处理文档内容是一个非常常见的需求。无论是生成报告、存储日志&#xff0c;还是处理用户输入&#xff0c;开发者都可能需要在纯文本与 Word 文档之间进行转换。有时需要将文本转换为 Word&#xff0c;以便生成结构化的 .docx 文件&#xff0c;使内…

Open SWE:重构代码协作的智能范式——从规划到PR的全流程自动化革命

在软件开发的演进史上,工具链的每一次革新都深刻重塑着开发者的工作方式。LangChain AI推出的Open SWE,作为首个开源的异步编程代理,正在重新定义代码协作的边界——它不再仅仅是代码生成工具,而是构建了从代码库分析、方案规划、代码实现到拉取请求创建的端到端自动化工作…

【ARDUINO】通过ESP8266控制电机【待测试】

需求 通过Wi-Fi控制Arduino驱动的3V直流电机。这个方案使用外部6V或9V电源&#xff0c;ESP8266作为Wi-Fi模块&#xff0c;Arduino作为主控制器&#xff0c;L298N作为电机驱动器。 手机/电脑 (Wi-Fi客户端) | | (Wi-Fi) | ESP8266 (Wi-Fi模块, AT指令模式) | | (串口通信) | A…

cuda编程笔记(18)-- 使用im2col + GEMM 实现卷积

我们之前介绍了cudnn调用api直接实现卷积&#xff0c;本文我们探究手动实现。对于直接使用for循环在cpu上的实现方法&#xff0c;就不过多介绍&#xff0c;只要了解卷积的原理&#xff0c;就很容易实现。im2col 的核心思想im2col image to column把输入 feature map 的每个卷积…

Loopback for Mac:一键打造虚拟音频矩阵,实现跨应用音频自由流转

虚拟音频设备创建 模拟物理设备&#xff1a;Loopback允许用户在Mac上创建虚拟音频设备&#xff0c;这些设备可被系统及其他应用程序识别为真实硬件&#xff0c;实现音频的虚拟化传输。多源聚合&#xff1a;支持将麦克风、应用程序&#xff08;如Skype、Zoom、GarageBand、Logic…