视觉检测项目里,上位机接相机跟接 PLC 是两套完全不同的思维:PLC 是"轮询寄存器",相机是"流式回调 + 非托管内存"。我们用海康 MVS SDK(C# 封装 MyCamera.MV_CC_* 系列接口)做过多个 GigE 视觉工位,踩坑高度集中:找不到设备、触发不拍照、偶发丢帧、图像内存踩坏。这篇按接入顺序把五步生命周期和四类高频问题讲清楚,思路同样适用于巴斯勒 Pylon、华睿等 GenICam 兼容 SDK。
一、上线前:先把网络层的坑排掉
GigE 相机一半的"软件问题"其实是网络配置问题。固定动作清单:
- 相机配静态 IP,与工控机网卡同网段,绝不依赖 DHCP——产线没有 IT 网管在旁边,IP 一变设备就"失踪";多相机时每个网卡一个独立网段;
- 网卡开 Jumbo Frame(巨帧,9014 字节),相机端 PacketSize 同步调到 8192 以上。默认 1500 MTU 下一张 500 万像素图要拆几千个包,CPU 和丢包率都难看;
- 网卡设为千兆全双工、关闭节能(设备管理器里"允许计算机关闭此设备节电"必须取消,节能模式会在采图间隙降链路速率);
- 相机独占网卡:相机流量不要和 PLC/办公网混在一张网卡上,更不要走普通交换机级联,视觉工位用工业交换机或直连。
二、SDK 五步生命周期:顺序不能乱
- 枚举设备:
MV_CC_EnumDevices按 GigE 层枚举,优先用序列号(SN)选相机而不是 IP——换网口 IP 会变,SN 永远不变; - 创建句柄并打开:
MV_CC_CreateDevice→MV_CC_OpenDevice,打开失败先查 IP 和防火墙(SDK 用约定端口通信,工控机防火墙要么放行 MVS 程序要么直接关); - 配参数:触发模式、曝光、增益、像素格式、PacketSize,全部在 StartGrabbing 之前设好;
- 注册回调并开始取流:
MV_CC_RegisterImageCallBackEx注册取流回调,再MV_CC_StartGrabbing; - 停机逆序释放:StopGrabbing → CloseDevice → DestroyDevice,一步不能省,尤其最后 DestroyDevice——句柄泄漏会让第二次打开设备直接失败。
var dev = deviceList.pDeviceInfo[0]; // 按SN过滤后的目标相机 var cam = new MyCamera(); MyCamera.MV_CC_CreateDevice_NET(ref dev, ref cam); cam.MV_CC_OpenDevice_NET(); cam.MV_CC_SetEnumValueByString_NET("TriggerMode", "On"); // 触发模式 cam.MV_CC_SetEnumValueByString_NET("TriggerSource", "Line0"); // 硬触发源 cam.MV_CC_SetIntValueEx_NET("GevSCPSPacketSize", 8192); // 巨帧 cam.MV_CC_RegisterImageCallBackEx_NET(OnFrame, this); cam.MV_CC_StartGrabbing_NET();
三、三种取图模式怎么选
| 模式 | 配置 | 适用场景 | 注意点 |
|---|---|---|---|
| 连续采集 | TriggerMode=Off | 实时预览、人工目检工位 | 回调按帧频持续来,处理慢了直接丢帧,预览取最新帧即可 |
| 软触发 | TriggerSource=Software | 上位机自己控制节拍,如扫码后拍一张 | TriggerSoftware 命令发出后等帧到达,要做超时等待(典型500ms) |
| 硬触发 | TriggerSource=Line0 | 与工位动作同步(气缸到位、编码器位置) | 触发延迟/抖动用 TriggerDelay 微调到曝光时刻 |
在线检测工位一律用硬触发:PLC 控制工件到位后给相机一个 24V 脉冲(经 IO 模块转相机光耦输入),拍照时刻与机械位置的一致性不依赖软件调度,上位机卡顿也不影响触发。
四、取流回调:三条铁律
回调函数运行在 SDK 的取流线程上,不是 UI 线程,里面的写法决定系统稳不稳:
- 回调里只做"拷贝+投递",不做处理。视觉算法、存盘、数据库都是耗时操作,放回调里会堵住取流线程导致丢帧。收到帧立刻 CopyImage 到自己管理的缓冲,丢进队列由工作线程处理;
- SDK 的帧内存只在回调期间有效。回调返回后那块非托管内存可能被下一帧覆盖,直接引用指针/拿 IntPtr 出线程用必踩内存,必须
MV_CC_CopyImage_NET完整拷贝; - 异常绝不能逃出回调。回调里抛未处理异常可能直接终结取流线程,所有逻辑 try/catch 包住,异常转日志和报警。
五、丢帧怎么查:SDK 已经把账记好了
不要凭感觉猜丢没丢。SDK 的流统计节点直接给数:GevStat_Payload_Count(收到帧)、GevStat_Packet_Lost_Count(丢包数)、FrameLostCount(取流丢帧数)。每分钟读一次记日志,丢包数 > 0 就是网络层问题,按这个顺序查:
- PacketSize 超过实际链路 MTU(网卡忘开巨帧)——占丢包原因的一半以上,表现为小包正常、大图必丢;
- 取流回调处理太慢,SDK 内部缓冲(默认抓包缓冲池)被填满——调大
GevSCPD包间延迟给 CPU 喘息,或把回调逻辑再瘦身; - 网卡中断被高负载 CPU 占满:相机独占网卡、关掉无关网卡的流量,必要时在网卡高级设置里调大接收缓冲(Receive Buffers 到 2048);
- 线缆/水晶头问题:GigE 用超五类以上工业线,弯折处损坏表现为随机丢包且与节拍无关。
MV_CC_ConvertPixelType_NET 在非托管侧转,别在 C# 里逐像素手写——又慢又容易字节序出错。转换目标缓冲按目标格式大小自己分配并 Dispose,这是相机程序的第二大内存泄漏点(第一是句柄未销毁)。六、和 PLC 的拍照握手:双向确认才算闭环
硬触发只是"PLC 让相机拍照",检测结果还要回去。我们的握手用四个布尔量做状态机:PLC 触发位 → 相机拍照 → 上位机忙位置 1 → 检测完成写结果位(OK/NG)并清忙位 → PLC 读到结果后清触发位。任何一步都有超时判定:触发后 1 秒无帧报"相机未响应",结果超过节拍时间未被 PLC 取走报"握手超时"。再加一个软触发按钮用于调试模式,工程师手动出图调算法参数,不和产线状态冲突。
最后给一个部署层面的经验:相机程序在工位机上一定配断线自愈——相机重上电、网线被碰松,SDK 回调会收到断流状态,程序要自动 StopGrabbing、重新枚举打开、恢复参数和取流,恢复过程写日志报警。产线工位的视觉程序,重启相机这件事不能靠操作工找工程师。
