简介:面向海康工业相机SDK的C#开发示例程序包,为使用C#进行相机二次开发的工程师提供可直接运行的工程模板,重点解决设备初始化、参数配置、触发与图像采集等常见问题。压缩包共29个文件,大小518KB,核心为6个C#源码文件,搭配工程配置文件、动态链接库与可执行程序;其中源码包括主窗体界面与程序入口,便于查阅调用逻辑,同时附带调试符号、日志及界面资源文件,辅助排错和界面还原。已有3500人学习或浏览,适合项目启动前做技术预研或快速验证SDK功能。示例覆盖设备搜索、分辨率与曝光调整、软件触发与硬件触发、单帧及实时采集、图像显示与保存的完整流程;开发者可将其作为基础框架,替换为自己业务逻辑后直接嵌入工业视觉项目,降低从零开发的风险。
1. 海康工业相机SDK C#开发示例程序:从解压到跑通第一帧图
做机器视觉上位机的人,十有八九都跟海康工业相机打过交道。工业相机不像普通USB摄像头,它没有DirectShow驱动那种即插即用的傻瓜体验,靠的是厂商提供的SDK。海康的MVS(Machine Vision Software)软件装好后,会在安装目录里附带一个完整的C#开发示例程序,网上下载的“海康工业相机SDK C#开发示例程序.zip”本质上也源于此。这个包里装的就是你写第一个c#上位机采集程序时的救命模板:从枚举设备、打开相机、开始采集到保存图像,每一步都有现成代码可以抄。这篇笔记就按我实际做项目的顺序,把这个包怎么用、坑在哪、参数怎么调讲透,让新手少走一个月的弯路,也让熟手能直接查漏补缺。
2. 拆开SDK包:目录结构、DLL引用和C#工程的前置条件
2.1 拿到压缩包后先认目录,别急着开VS
从网上下到的“海康工业相机SDK C#开发示例程序.zip”解压后,目录结构和海康官方MVS安装目录基本一致。最常见的布局是一个根目录下有三个核心东西:Development文件夹存放SDK本体,Document文件夹是PDF文档,Runtime文件夹装的是运行环境。对于C#开发而言,真正和你发生关系的是Development\C#目录,里面会有一个MvCameraControl.cs文件,这是海康官方把C++接口封装成C#类的核心胶水层。那个文件大概有一万多行,把相机控制所需的所有接口都Wrapper了一遍,打开看看能学到不少P/Invoke的写法,但日常开发不需要读懂每行,知道怎么调用就行。
2.2 引用三种DLL:管理DLL、原生DLL和C#封装层缺一不可
在Visual Studio里新建一个WinForm项目(.NET Framework 4.6.1或4.7.2比较稳),然后把SDK里这些文件引入工程。先看C#工程引用了什么:
<Reference Include="System" /> <Reference Include="System.Drawing" /> <Reference Include="System.Windows.Forms" />这些都是.NET自带的,不需要额外操作。真正要添加的是SDK的托管DLL和原生DLL的引用关系,在Development\C#\目录下你会看到MvCameraControl.dll和MvCameraControl.xml(XML是注释文档,能让VS智能提示生效)。这个托管DLL必须放进工程的输出目录,同时你还得把原生DLL也带上,因为托管DLL内部通过DllImport调用原生库:
// MvCameraControl.cs 内部的P/Invoke声明,不需要自己写,但要理解 [DllImport("MvCameraControl.dll", EntryPoint = "MV_CC_EnumDevices_NET")] internal static extern int MV_CC_EnumDevices_NET(uint nTLayerType, ref MV_CC_DEVICE_INFO_LIST pstDeviceList);这里有个关键点:SDK包里的C#示例把MvCameraControl.dll放在Debug目录下,但原生DLL放在Runtime\Win64_x64\或Runtime\Win32_x86\里。很多新手翻车就翻在这里——把托管DLL塞进bin,原生DLL没放,程序一跑就报“无法加载DLL或它的依赖项”。解决办法很简单,把对应位数的Runtime目录整个拷到bin\Debug和bin\Release下。我用的是x64,所以通常把Runtime\Win64_x64里的所有文件覆盖到输出目录。
提示:项目属性里的“平台目标”必须是x64(或x86),不要用AnyCPU。MVS的SDK原生库是按位数区分的,AnyCPU在64位系统上默认走x64,但一旦切到x86库就会翻车。
2.3 相机连接状态检查:网口相机必须做这四步
海康工业相机主要有GigE网口和USB3.0两种接口。USB3.0即插即用,插上就能枚举到。网口相机的坑就多了,海康的MVS软件安装后会自动把本地网卡IP改成静态IP并配置好相机IP,但如果你用的是精简版SDK包而不是完整MVS软件,就得手动配。第一步,把相机网口的IP设为静态,比如相机出厂默认是192.168.1.64,你的电脑网卡就要设成192.168.1.10,子网掩码255.255.255.0。第二步,打开cmd输入ping 192.168.1.64确认网络通。第三步,运行你写的程序执行设备枚举。第四步,如果还枚举不到,关掉Windows防火墙再试一次。
2.4 跑通之前的最终检查项
检查完目录和引用,在写代码前把这个清单过一遍:MVS软件装没装(SDK依赖它的驱动);相机固件和SDK版本是否兼容(这个看Document目录里的版本说明PDF);Visual Studio的C#环境是否正常;x64/x86位数是否匹配。我见过一个项目,代码全对但相机半小时就断一次流,最后发现是USB3.0线用了根劣质延长线,供电不足。工业相机宁可线粗一点、短一点,别在这上面省。
3. 用C#写最小采集程序:从枚举设备到保存一帧图
3.1 初始化SDK和枚举设备的固定写法
海康的C# SDK使用方式是典型的“先枚举、再创建、后操作”。枚举设备的API返回一个设备列表,你要从中找到类型匹配的设备。下面这段是每个采集程序都有的开头,直接抄就行:
using MvCamCtrl.NET; using System; using System.Windows.Forms; public class CameraOperator { private MyCamera camera = new MyCamera(); // 海康C#封装类 private MyCamera.MV_CC_DEVICE_INFO_LIST deviceList = new MyCamera.MV_CC_DEVICE_INFO_LIST(); public bool InitCamera() { int nRet = MyCamera.MV_CC_EnumDevices_NET(MyCamera.MV_GIGE_DEVICE | MyCamera.MV_USB_DEVICE, ref deviceList); if (nRet != 0) { MessageBox.Show("设备枚举失败,错误码: " + nRet); return false; } uint deviceCount = deviceList.nDeviceNum; if (deviceCount == 0) { MessageBox.Show("没有找到海康工业相机,请检查网络连接或USB线缆"); return false; } // 取第一个设备信息 MyCamera.MV_CC_DEVICE_INFO deviceInfo = (MyCamera.MV_CC_DEVICE_INFO)System.Runtime.InteropServices.Marshal.PtrToStructure( deviceList.pDeviceInfo[0], typeof(MyCamera.MV_CC_DEVICE_INFO)); nRet = camera.MV_CC_CreateDevice_NET(ref deviceList.pDeviceInfo[0]); if (nRet != 0) { MessageBox.Show("创建设备失败,错误码: " + nRet); return false; } return true; } }这段代码的核心逻辑是三层:第一层MV_CC_EnumDevices_NET枚举总线上所有海康相机,枚举的传输层类型用MV_GIGE_DEVICE | MV_USB_DEVICE表示同时搜网口和USB设备;第二层从设备列表取第一个设备信息,用Marshal.PtrToStructure把非托管内存里的结构体转成托管对象;第三层MV_CC_CreateDevice_NET在SDK内部创建这个设备的句柄,之后所有操作都靠camera这个对象来发。注意错误码不是Win32错误码,是海康SDK自定义的MV_E_*系列返回码,0代表成功。
3.2 打开相机、设置采集模式并启动抓流
设备创建好后,接下来就是“打开→配置→开始采集”三步曲。工业相机默认触发模式可能是“外部触发”,如果不开软件触发或连续采集模式,你会发现调MV_CC_StartGrabbing_NET后不出图。这个坑我踩过,所以代码里明确把触发源设成软件触发:
public bool StartGrabbing() { // 打开相机,使用默认配置 int nRet = camera.MV_CC_OpenDevice_NET(); if (nRet != 0) { MessageBox.Show("打开设备失败,错误码: " + nRet); return false; } // 设置触发模式为关闭(即连续采集模式) MyCamera.MVCC_ENUMVALUE triggerMode = new MyCamera.MVCC_ENUMVALUE(); triggerMode.nCurValue = (uint)MyCamera.MV_CAM_TRIGGER_MODE.MV_TRIGGER_MODE_OFF; nRet = camera.MV_CC_SetEnumValue_NET("TriggerMode", triggerMode.nCurValue); if (nRet != 0) { MessageBox.Show("设置触发模式失败,错误码: " + nRet); return false; } // 设置采集帧率上限为60fps(视相机型号而定) nRet = camera.MV_CC_SetFrameRate_NET(60.0f); if (nRet != 0) { // 并不是所有型号都支持任意帧率,这个失败可以忽略 Console.WriteLine("设置帧率失败,错误码: " + nRet); } // 开始采集 nRet = camera.MV_CC_StartGrabbing_NET(); if (nRet != 0) { MessageBox.Show("开始采集失败,错误码: " + nRet); return false; } return true; }这段代码里两个参数是关键:TriggerMode的枚举值含义是,OFF代表自由运行模式,相机自己按帧率出图;ON代表等待外部信号或软件指令。做视觉检测项目时,如果PLC给信号相机才拍一张,那就用MV_TRIGGER_MODE_ON,而单纯调相机时先用OFF跑通链路。MV_CC_SetFrameRate_NET是软件层限帧,不是相机硬件的传感器帧率,如果设置的帧率超过相机实际能力,SDK会返回错误码或实际帧率不达标,所以失败时别惊慌,打印出来继续跑就行。
3.3 取流回调:别用轮询,用回调拿图像数据
这里就是“示例程序”最值钱的部分。海康SDK取流有两种方式:一种是在独立线程里调MV_CC_GetImageBuffer_NET轮询取图,另一种是注册回调函数,SDK每采到一帧就调你注册的函数。做上位机界面时千万别在主线程里干等,界面会卡死。我用回调方式,注册的委托在SDK内部线程触发,拿到图像后丢给UI线程刷新:
// 图像回调委托 private void GrabImageCallback(IntPtr pData, ref MyCamera.MV_FRAME_OUT_INFO pFrameInfo, IntPtr pUser) { if (pData == IntPtr.Zero) return; // pData指向的是SDK内部缓冲区的灰度数据或BGR数据 int nWidth = (int)pFrameInfo.nWidth; int nHeight = (int)pFrameInfo.nHeight; int nPixelType = (int)pFrameInfo.enPixelType; int nPayloadSize = (int)pFrameInfo.nFrameLen; // 构建Bitmap:这里假设是Mono8灰度图 if (nPixelType == (int)MyCamera.MV_PIXEL_TYPE.MV_PIXEL_TYPE_MONO8) { byte[] buffer = new byte[nPayloadSize]; System.Runtime.InteropServices.Marshal.Copy(pData, buffer, 0, nPayloadSize); // 跨线程更新UI(示例用的BeginInvoke) this.BeginInvoke(new Action(() => { pictureBox1.Image = ByteArrayToBitmap(buffer, nWidth, nHeight); })); } } private Bitmap ByteArrayToBitmap(byte[] rawData, int width, int height) { Bitmap bmp = new Bitmap(width, height, System.Drawing.Imaging.PixelFormat.Format8bppIndexed); // 设置灰度调色板 System.Drawing.Imaging.ColorPalette palette = bmp.Palette; for (int i = 0; i < 256; i++) palette.Entries[i] = Color.FromArgb(i, i, i); bmp.Palette = palette; // 拷贝像素数据 var rect = new Rectangle(0, 0, width, height); var bmpData = bmp.LockBits(rect, System.Drawing.Imaging.ImageLockMode.WriteOnly, bmp.PixelFormat); System.Runtime.InteropServices.Marshal.Copy(rawData, 0, bmpData.Scan0, rawData.Length); bmp.UnlockBits(bmpData); return bmp; }图像数据的坑集中在像素格式上。海康相机默认输出可能是Mono8(灰度8bit)、BayerRG8(彩色相机原始数据),甚至YUV422。Mono8转Bitmap最省事,直接按像素拷贝再加灰度调色板。如果是彩色的BayerRG8,你直接拷贝出来是花的,必须用MV_CC_ConvertPixelType_NET转成RGB888。下面代码是彩色转换的标准姿势:
// 彩色相机必须先做像素格式转换再显示 MyCamera.MV_PIXEL_CONVERT_PARAM convertParam = new MyCamera.MV_PIXEL_CONVERT_PARAM(); convertParam.pSrcData = pData; convertParam.nSrcDataLen = (uint)nPayloadSize; convertParam.enSrcPixelType = pFrameInfo.enPixelType; convertParam.enDstPixelType = MyCamera.MV_PIXEL_TYPE.MV_PIXEL_TYPE_RGB8; convertParam.nWidth = nWidth; convertParam.nHeight = nHeight; byte[] rgbBuffer = new byte[nWidth * nHeight * 3]; convertParam.pDstData = System.Runtime.InteropServices.Marshal.UnsafeAddrOfPinnedArrayElement(rgbBuffer, 0); convertParam.nDstDataLen = (uint)rgbBuffer.Length; int nRet = camera.MV_CC_ConvertPixelType_NET(ref convertParam); if (nRet == 0) { // rgbBuffer现在是标准的RGB数据,转成24位Bitmap Bitmap bmp = new Bitmap(nWidth, nHeight, System.Drawing.Imaging.PixelFormat.Format24bppRgb); // 之后照上面LockBits拷贝即可 }3.4 停止采集和销毁句柄:顺序颠倒就蓝屏或死锁
采集程序结束前,资源释放的顺序有铁律:先MV_CC_StopGrabbing_NET,然后MV_CC_CloseDevice_NET,最后MV_CC_DestroyDevice_NET。这个顺序不能乱,否则SDK内部线程还挂着,资源就被释放了,轻则内存泄漏,重则程序奔溃。关闭窗体时的完整代码:
private void Form1_FormClosing(object sender, FormClosingEventArgs e) { camera.MV_CC_StopGrabbing_NET(); camera.MV_CC_CloseDevice_NET(); camera.MV_CC_DestroyDevice_NET(); }这里有个血泪经验:如果程序里开了多个线程访问SDK接口,关闭前必须先把业务线程停掉。SDK内部接口不是所有都线程安全的,比如你一边在UI线程调MV_CC_SetEnumValue_NET改曝光,一边在回调里取帧分析,偶尔就会出现死锁。我一般会在关闭前先置一个volatile bool标志,让工作线程先退出再执行上面的释放序列。
4. 从示例到项目:图像参数调节和图像格式转换的必踩细节
4.1 曝光、增益、白平衡用什么接口调
工业现场光照变化剧烈,不能靠相机自动曝光一棵树上吊死。海康SDK提供三类参数接口:整数型、浮点型、枚举型。曝光时间在SDK里是ExposureTime(浮点,单位微秒),增益是Gain(浮点,单位dB),白平衡是BalanceWhiteAuto(枚举)。一般我用的调参套路是先把自动曝光关掉,再手动一步步调曝光:
// 关闭自动曝光,手动控制 camera.MV_CC_SetEnumValue_NET("ExposureAuto", (uint)MyCamera.MV_CAM_EXPOSURE_AUTO.MV_EXPOSURE_AUTO_OFF); // 设置手动曝光时间:5000微秒 = 5ms float exposureTime = 5000.0f; nRet = camera.MV_CC_SetFloatValue_NET("ExposureTime", exposureTime); if (nRet != 0) { Console.WriteLine("设置曝光失败: " + nRet); } // 增益:亮度还不够就加增益,一般不超过16dB,太高噪声感人 float gain = 8.0f; nRet = camera.MV_CC_SetFloatValue_NET("Gain", gain); if (nRet != 0) { Console.WriteLine("设置增益失败: " + nRet); }注意ExposureAuto是枚举,ExposureTime是浮点,接口不能混用。你拿SetFloatValue去设枚举值,SDK直接返回错误码MV_E_CALLORDER什么的。另外曝光单位是微秒,很多人设0.5以为是0.5秒,实际是0.5微秒,拍出来一团黑就开始怀疑相机坏了。
4.2 像素格式和图像大小:为什么你拿到的数据是花的
海康相机的像素格式五花八门,C#示例里经常能看到MV_FRAME_OUT_INFO里的enPixelType字段。工业面阵相机常见的输出是Mono8、Mono12、BayerRG8、BayerGB8、YUV422。Mono12是16bit存储的,但高位只有12bit有效,显示前必须做移位或缩放,否则图像灰蒙蒙且偏暗。我的经验是:如果相机有Mono8模式,采集就直接用Mono8,省去转换步骤;如果必须用高位数采集做算法分析,显示时再转成8bit。
图像分辨率方面,海康相机有Width、Height参数,可以通过MV_CC_SetIntValue_NET设置,但有个隐藏门槛:Width必须是4的倍数。这是SDK内部行对齐要求的,设了个奇数宽度可能报错。设置ROI的代码和可能报错的场景如下:
// 设置ROI之前先查最大值 MyCamera.MVCC_INTVALUE intValue = new MyCamera.MVCC_INTVALUE(); camera.MV_CC_GetIntValue_NET("WidthMax", ref intValue); int maxWidth = (int)intValue.nCurValue; // 设置宽度,注意必须是4的倍数 int roiWidth = 1280; // 假设相机支持 camera.MV_CC_SetIntValue_NET("Width", roiWidth);4.3 软触发模式:怎么做到PLC一来信号就拍一张
很多视觉定位项目不需要连续视频流,PLC给一个信号,相机拍一帧,算法算一下,结果发回去。这种场景要把相机设成软触发模式。C#里触发的动作就是一个API调用:
// 设置触发源为软件触发 camera.MV_CC_SetEnumValue_NET("TriggerSource", (uint)MyCamera.MV_CAM_TRIGGER_SOURCE.MV_TRIGGER_SOURCE_SOFTWARE); // 设置触发模式为ON camera.MV_CC_SetEnumValue_NET("TriggerMode", (uint)MyCamera.MV_CAM_TRIGGER_MODE.MV_TRIGGER_MODE_ON); // 等PLC信号到了,执行软触发 camera.MV_CC_SetCommandValue_NET("TriggerSoftware");这里有个容易被忽略的点:MV_CC_SetCommandValue_NET("TriggerSoftware")是一个“即发即弃”的指令,触发成功后SDK回调里才有一帧新图。如果触发前相机还没准备好,或者上一帧还没取走,这帧会丢失。所以软触发模式下的业务循环是:触发 → 等待回调 → 在回调里取图分析 → 分析完再触发下一帧。不要在主线程里死等,等会等到怀疑人生。
4.4 帧率限制和带宽:为什么GigE相机跑不到标称帧率
GigE相机标称帧率是在理想带宽下算出来的。200万像素、Mono8的数据量是200万字节每帧,千兆网的理论带宽125MB/s,算下来极限也就60帧左右。如果你在相机端开了超大分辨率,又在PC端跑了个占带宽的杀毒软件,实际能拿到的帧率就会打折。排查帧率上不去的思路是:看MVS软件里当前帧率多少,如果MVS也上不去就是相机配置或带宽问题,如果MVS能上去而你程序上不去,就是代码里回调里处理图像耗时长,SDK内部缓冲区满了就丢帧。给回调里的图像处理加个耗时统计,你会发现很多帧率问题根本不是SDK的问题,是处理逻辑没优化。
5. 海康相机C#开发的避坑手册:从解压到部署的常见翻车现场
5.1 报错“无法加载DLL 'MvCameraControl.dll'或其依赖项”
现象:程序启动瞬间抛DllNotFoundException。原因:两种可能。一种是托管DLL和原生DLL放错位置,原生DLL缺失;另一种是运行时加载路径不对,比如用相对路径时当前目录不是你想象的那个bin目录。解决:把Runtime\Win64_x64\里所有文件Copy到exe同目录。还在报错,就用Dependency Walker或dumpbin /dependents看MvCameraControl.dll到底依赖哪些原生库,逐一对照是否缺失。另外检查平台目标是否是x86跑到x64系统这种离谱配置。
5.2 枚举不到相机,但MVS软件能搜到
现象:海康MVS客户端能看到相机,自己写的C#程序枚举到0个设备。原因:90%是传输层类型没配对。代码里只枚举了MV_GIGE_DEVICE,但相机是USB3.0的;或者反过来。还有10%是权限问题,比如以管理员权限跑MVS装好驱动,但你的程序是普通权限跑的。解决:枚举类型直接写MyCamera.MV_GIGE_DEVICE | MyCamera.MV_USB_DEVICE两个都搜。还不行就把调试器附加到进程看返回码,MV_E_ACCESS_DENIED就是权限问题。你的程序右键“以管理员身份运行”测试一下。
5.3 相机打开成功但StartGrabbing返回错误码21
现象:MV_CC_StartGrabbing_NET返回MV_E_CALLORDER(错误码21),意思是接口调用顺序有误。原因:相机没打开就开抓流,或者相机已经被别的进程独占打开了。海康相机单次只允许一个进程句柄,MVS软件开着就会把你的程序顶掉。解决:确认先调了MV_CC_OpenDevice_NET;确认MVS客户端没开着;检查相机网线连接的吧,有些型号断线后SDK不知道,直到调用时才报错。
5.4 图像颜色不对:偏绿、偏红或者花屏
现象:彩色相机拍出来的图是花的或者色调不对。原因:Bayer数据没转换成RGB,直接当Bitmap显示;或者白平衡没设置。BayerRG8的“RG”含义就是第一行第一列是R,第二行第一列是G,转RGB的时候用错了转换接口就不对。解决:采集到Bayer数据后一律走MV_CC_ConvertPixelType_NET转RGB8;白平衡用BalanceWhiteAuto设为Continuous或Once,让相机自动校正一次再关掉。
5.5 程序释放相机时死锁或蓝屏
现象:关窗体时程序卡死,或者偶尔蓝屏(很少见但有人遇到过)。原因:回调函数里正在处理图像,同时主线程又在释放SDK资源,SDK内部锁冲突。解决:释放顺序严格按照“先停业务线程→StopGrabbing→CloseDevice→DestroyDevice”。在回调里用锁保护共享数据,别让UI线程直接碰相机对象。关闭程序前先Environment.Exit(0)粗暴跳过回调后续操作,也是一种保命法,但事后必须找到根因。
6. 进阶:多线程取流下的回调锁机制与图像复用
最后写一个C#取流时容易忽略但极其关键的进阶细节:相机回调触发频率和你图像处理速度不匹配时的应对。很多项目开始能跑,图像处理逻辑越加越复杂,回调里占用的时间越来越长,帧率就开始莫名下降。我遇到过一个典型的场景:回调里做Blob分析,一帧处理50ms,相机持续以60帧的速率往回调里灌,SDK内部缓冲区全满,后面帧直接被丢弃,实际出图效率远低于标称。
正确的做法是回调里只做两件事:把图像数据Copy出来(Marshal.Copy耗时极小),然后丢到线程安全的队列里,工作线程从队列取帧做处理。队列用ConcurrentQueue<T>或锁保护的Queue,并且要限制最大队列长度,超过就丢最旧的一帧,保证处理的是最新数据。核心代码如下:
private ConcurrentQueue<byte[]> frameQueue = new ConcurrentQueue<byte[]>(); private const int MaxQueueLength = 3; // 只保留最新3帧 private void GrabImageCallback(IntPtr pData, ref MyCamera.MV_FRAME_OUT_INFO pFrameInfo, IntPtr pUser) { byte[] buffer = new byte[pFrameInfo.nFrameLen]; Marshal.Copy(pData, buffer, 0, (int)pFrameInfo.nFrameLen); // 只留最新帧,丢旧帧 if (frameQueue.Count >= MaxQueueLength) { byte[] discarded; frameQueue.TryDequeue(out discarded); } frameQueue.Enqueue(buffer); } private void ProcessingThread() { while (isRunning) { byte[] frame; if (frameQueue.TryDequeue(out frame)) { ProcessFrame(frame); // 耗时操作放这里 } else { Thread.Sleep(5); // 队列空就睡5ms,别空转 } } }这个模式的价值在于把“取流”和“处理”解耦,图像处理再慢也不至于卡死SDK的取流线程。实际项目里我还会在工作线程里对图像做JPEG压缩后存本地,配合Stopwatch统计平均处理耗时,比在回调里处理靠谱得多。
另一个进阶点是相机的像素格式在示例程序里基本都是Mono8或BayerRG8,但如果你的算法库(比如Halcon或OpenCV)需要Bgr24或者Rgb24,转换格式的时机要放在工作线程而不是回调线程——转换本身也有几百微秒开销,放在工作线程里能保持取流和转换互不影响。我之前踩过一次坑,在回调里加了一个像素格式转换,结果帧率从30帧掉到了20帧,而转换时间实际上只有不到5毫秒——原因就是回调线程被转换阻塞后,SDK内部缓冲在等待取走的时间拉长了整个链路。后来把转换挪到工作线程,帧率立刻恢复。这算是SDK开发里一个典型的“玄学”优化坑,本质上是线程阻塞时序问题,说穿了不值钱,但不点破能卡一下午。
今天聊的这套流程,从解压SDK包到跑通回调采集,参考的就是MVS目录里那份C#示例。拿过去改改,配上你自己的算法,就是一套能用的视觉检测原型。做工业项目时我习惯留着那份示例原始代码当“后悔药”,改坏了随时拉回来,希望帮到你。
本文还有配套的精品资源,点击获取