去年接了个汽车零部件产线的视觉质检项目需求说起来简单8台工业相机同时拍检测结果实时上报MES出了问题能追溯。真干起来才知道相机连上、数据传过去只是第一步后面丢帧、串号、MES接口超时、相机掉线重连、UI卡死……每一个坑都能让产线停半天。这套方案前后在3条产线跑了一年多单工位检测节拍压到200ms以内数据上报成功率稳定在99.99%。今天把整个架构和踩过的坑完整捋一遍给正在做类似项目的朋友一个参考。整体架构四层解耦别把所有逻辑塞一个线程很多人做工业相机对接上来就new一个相机对象回调里直接调算法、调MES接口demo跑得挺欢一上产线就崩。产线环境和实验室最大的区别是任何一个环节都可能出问题而且出问题的时候不能影响其他环节。所以架构上必须分层每一层独立线程、独立容错采集层只负责拿图像不管图像里有什么处理层只负责跑算法不管数据往哪传通信层只负责跟MES打交道不管图像怎么来的。每层之间用队列解耦某一层慢了、挂了其他层还能正常运转最多是队列积压不会整个程序卡死。这个架构说起来不复杂但真正决定稳定性的是每层内部的细节。下面逐层说。采集层SDK回调里什么都别干除了入队相机采集是整个链路的源头源头不稳后面全白搭。产线用的相机基本就那几家——Basler、海康、大恒每家SDK都不一样调用方式、回调机制、内存管理各有各的脾气。所以第一步是把SDK差异屏蔽掉定义一个统一的相机接口上层只面向接口编程后续换相机厂商只需要加一个实现类。接口里最核心的两个方法软触发采集和硬触发回调注册。产线场景一定用硬触发由PLC输出电平信号触发相机拍照采集时机跟产线节拍完全同步。软触发只适合低速离线检测产线跑起来之后软触发的延迟和抖动根本不可控。硬触发的回调函数是重中之重。SDK回调运行在非托管线程里这个线程里除了把图像数据复制一份扔进队列什么都别干——不要调算法、不要写日志、不要更新UI甚至不要做任何可能阻塞的操作。回调返回慢了SDK内部就会丢帧而且你根本感知不到因为SDK不会告诉你我丢了一帧。队列用System.Threading.Channels的有界Channel容量设50帧足够。这里有个关键决策队列满了怎么办我的选择是丢弃旧帧。产线场景下旧帧的检测结果已经没有意义了——产品都流到下一个工位了你才检测上一个的图像除了制造混乱没有任何价值。与其让队列无限增长把内存撑爆不如果断丢旧帧同时记一条warning日志丢帧多了说明处理层性能不够该优化算法了。还有一个容易被忽略的点非托管内存释放。相机SDK返回的图像数据存在非托管内存里你复制到托管对象之后原始缓冲区必须按SDK文档的方式释放否则跑一晚上内存就涨几个G最后程序被系统干掉。这个问题在demo阶段根本发现不了因为demo只跑几分钟上产线连续跑几天才会暴露。相机掉线自动重连也是必备功能。工业现场电磁干扰、网线松动、交换机重启都可能导致相机断连。做法很简单开一个后台线程定时发心跳指令连续3次无响应就判定掉线然后按固定间隔尝试重连重连成功后恢复触发模式。重连期间要把工位状态标记为异常通知MES和操作员不要静默重连导致产线在盲跑。处理层图像、工单、条码三者必须绑死采集层拿到的只是一张图这张图对应哪个工单、哪个批次、哪个产品处理层必须搞清楚。没有业务绑定的图像数据就是一堆废像素MES根本不知道你在检测什么。工单信息在产线启动时从MES拉取包含工单编号、产品型号、批次号、工艺参数、检测标准。工单切换有两种触发方式MES主动推送或者扫码枪扫产品条码后根据条码匹配。不管哪种方式切换工单时必须先停采集、清空队列和待上报数据再加载新工单最后恢复采集。这一条是用血的教训换来的——最早的版本没做清空工单切换后上一个工单的几帧图像混进了新工单导致MES里两个工单的质检数据串号客户质量部门查了整整一天才定位到原因。检测结果要结构化存储不能只传一个OK或NG。至少要包含工单编号、批次号、产品型号、条码、工位编号、检测时间、相机ID、是否合格、缺陷列表、图像存储路径、算法版本号。这些字段里条码是产品的唯一身份标识条码识别失败意味着这张图无法绑定到具体产品。处理逻辑是首次识别失败自动触发重拍最多重试3次3次都失败就判定为异常品记录日志并提示操作员介入。不要条码识别失败就随便编一个或者留空上报那样MES里会出现大量无主数据后续追溯根本没法做。还有一个字段特别关键幂等ID。由工位编号产品条码检测时间戳生成一个唯一字符串每条上报数据带这个ID。MES端收到后先查这个ID有没有处理过处理过就直接返回成功不重复入库。这个机制解决的是网络抖动导致的重复上报问题——请求发出去了MES也处理了但响应包丢了客户端认为超时就重试结果同一条数据上报了两次。没有幂等校验的话MES的合格率统计会被这些重复数据污染。通信层3次重试加本地补偿MES挂了产线也不能停跟MES通信是整个链路里最不可控的一环。你控制不了MES服务器的性能控制不了工厂网络的稳定性甚至控制不了MES维护人员什么时候重启服务。所以通信层的设计原则只有一个MES不可用的时候产线该怎么跑还怎么跑数据先存本地等MES恢复了自动补传。RESTful API是最常用的对接方式。封装一个MesApiClient核心逻辑是上报前先查本地记录这个幂等ID已经上报成功过就直接返回然后尝试发送最多重试3次每次重试间隔按指数退避1秒、2秒、4秒5xx服务端错误和网络异常才重试4xx客户端错误比如参数格式不对、工单不存在不重试直接记错误日志返回失败——因为客户端错误你再重试100次也没用只会给MES服务器制造压力。3次重试全部失败的数据写入本地SQLite补偿队列。后台开一个补传线程每隔30秒轮询一次补偿队列按时间顺序取出数据尝试补传成功就删除本地记录失败就等下一轮。补传要注意顺序必须按产生时间从早到晚传否则MES里可能出现后检测的数据先入库、先检测的数据后到的情况时间线乱了追溯就会出问题。超过7天还补传失败的数据生成告警日志通知人工介入——大概率是MES那边的工单已经关闭或者数据格式有变更自动补传解决不了了。除了上报结果还要支持MES下发指令比如工单切换、工艺参数调整、工位暂停。实现方式有两种轮询和推送。轮询就是定时调MES接口查有没有新指令实现简单、兼容性好大部分场景够用推送是MES通过Webhook或长连接主动推过来实时性高但对MES端有开发要求而且要处理鉴权和安全问题。一般建议轮询为主对实时性要求高的指令再单独做推送。OPC UA是另一种主流对接方式适合工厂已经有SCADA系统、MES支持OPC的场景。OPC UA的优势是工业级稳定性和实时性数据变化时可以订阅通知不用轮询缺点是配置复杂点位多的时候管理起来麻烦而且不同厂商的OPC实现细节有差异联调成本不低。如果工厂基础设施已经是OPC体系就用OPC UA否则RESTful API加本地补偿的方案更轻量、更容易落地。几个差点让项目延期的坑第一个坑是GigE相机高帧率下丢帧。产线提速到30fps之后开始出现规律性丢帧查了半天发现是网卡没开巨帧。默认MTU是1500字节高帧率下每张图要拆成很多个包网络带宽和协议开销扛不住。把网卡巨帧设成9014字节相机端数据包大小跟网卡匹配再把网卡的节能模式和流量控制关掉丢帧问题彻底解决。这个问题纯靠软件优化解决不了必须从网络配置层面入手。第二个坑是SDK调用导致UI线程卡死。最早的版本图省事在按钮点击事件里直接调相机的连接和采集方法一点按钮界面就卡死好几秒。原因是SDK的很多方法是同步阻塞的UI线程被占住之后消息循环停了界面自然不响应。后来把所有SDK调用挪到独立的后台线程UI线程只通过事件更新展示数据问题解决。还有一点要注意部分厂商的SDK有线程亲和性必须在创建相机对象的那个线程里调用SDK方法跨线程调用会直接崩溃这个在SDK文档里通常不会写得很明显踩过坑才知道。第三个坑是图像存储和数据上报的优先级问题。最早的设计是检测完成后先存图像再上报结果结果图像存储偶尔超时磁盘IO抖动、网络存储延迟导致结果上报也跟着延迟MES那边迟迟收不到检测结果。后来调整了优先级检测结果优先上报图像异步存储。结果上报成功就算这条检测完成了图像存失败了后台重试不影响主流程。产线场景下MES及时拿到检测结果比图像存下来更重要——图像可以补产线停了损失补不回来。第四个坑是PLC触发信号抖动。机械继电器接触不良的时候触发信号会在短时间内跳变多次相机连拍好几帧同一产品被重复检测。解决办法是软件防抖同一触发信号在10ms以内只响应第一次后续的直接忽略。这个防抖窗口要根据产线实际节拍调整太短防不住抖动太长可能漏掉真实的连续触发。写在最后工业相机对接MES技术上没有什么特别高深的东西难的是工程细节和容错设计。实验室里跑通demo只需要调几个API产线上稳定运行需要考虑每一个可能出问题的环节相机掉线、网络中断、MES超时、PLC抖动、磁盘IO异常、内存泄漏、UI卡死……任何一个环节没有兜底都可能导致产线停摆。这套方案的核心思路总结起来就是三句话分层解耦让每个模块独立容错数据可追溯每条结果都能绑定到具体的产品和工单异常可补偿MES不可用时数据不丢、产线不停。把这三点做到位工业相机的数据接入就不会成为产线的瓶颈。后续如果要继续扩展可以在这个架构上叠加AI缺陷分类模型把检测结果同步到SCADA和数字孪生平台或者增加历史图像回溯和质检报表功能。基础架构稳了上面加什么都好办。