上个月接了个汽配厂的活,他们的发动机密封垫产线之前全靠人工目检,一天下来工人眼睛花,漏检率还高。老板要求搞个“眼睛+大脑+手脚”的系统:用相机当眼睛,YOLO当大脑,PLC当手脚,检测到缺陷直接分拣。花了三周时间调通,今天把这套C#上位机+YOLO+PLC的联动方案拆解开,从架构到代码,全是能直接落地的干货。
一、系统整体架构:稳定压倒一切
先给大家看这套系统的核心架构,没有花里胡哨的设计,全是工业现场验证过的可靠组合:
为什么这么选?给大家算笔账:
- C#上位机:厂里老系统都是WinForms,技术栈统一,维护成本低;
- YOLOv8n:速度快(640x640分辨率推理<50ms),精度够,导出ONNX后能直接在C#里跑,不用装Python环境;
- Modbus TCP:不用额外装OPC UA服务器,PLC侧编程简单,连车间电工都能看懂;
- 西门子S7-1200:稳定,抗干扰,汽配厂车间里的电磁干扰根本不是事。
二、核心模块实现:每一行代码都为7x24小时运行设计
1. YOLO视觉检测:从模型到C#调用的全流程
第一步:模型准备
我用LabelImg标注了5000张密封垫缺陷图(划痕、变形、气泡各占1/3),训练YOLOv8n,重点是导出ONNX格式时要加上--opset 12参数,否则C#里的ONNX Runtime会报错。
第二步:C#调用ONNX Runtime
这里有两个坑必须提醒大家:一是图像预处理必须和训练时完全一致(Resize到640x640、RGB通道、归一化到0-1);二是一定要用using语句释放资源,否则产线跑一晚上内存就爆了。
usingMicrosoft.ML.OnnxRuntime;usingMicrosoft.ML.OnnxRuntime.Tensors;usingOpenCvSharp;publicclassYoloDetector{privatereadonlyInferenceSession_session;privatereadonlystring[]_classNames={"划痕","变形","气泡"};publicYoloDetector(stringmodelPath){varsessionOptions=newSessionOptions();sessionOptions.AppendExecutionProvider_CPU(0);// 用CPU推理,工控机没显卡也能跑_session=newInferenceSession(modelPath,sessionOptions);}publicList<DetectionResult>Detect(Matimage){// 1. 图像预处理:Resize、转RGB、归一化varresized=image.Resize(newSize(640,640));varrgb=newMat();Cv2.CvtColor(resized,rgb,ColorConversionCodes.BGR2RGB);varinputTensor=ConvertMatToTensor(rgb);// 2. 推理varinputs=newList<NamedOnnxValue>{NamedOnnxValue.CreateFromTensor("images",inputTensor)};usingvarresults=_session.Run(inputs);// 3. 后处理:NMS去重、解析检测框returnPostProcess(results,0.5f,0.4f);// 置信度0.5,NMS阈值0.4}privateDenseTensor<float>ConvertMatToTensor(Matmat){vartensor=newDenseTensor<float>(new[]{1,3,640,640});vardata=mat.GetData<byte>();for(inti=0;i<640;i++){for(intj=0;j<640;j++){tensor[0,0,i,j]=data[(i*640+j)*3+0]/255f;// Rtensor[0,1,i,j]=data[(i*640+j)*3+1]/255f;// Gtensor[0,2,i,j]=data[(i*640+j)*3+2]/255f;// B}}returntensor;}// PostProcess方法省略,核心是NMS和坐标转换}2. C#与PLC通信:加心跳、重连,一个都不能少
通信我选了工业界用烂的HslCommunication库,稳定、文档全。但工业现场最容易出问题的就是通信,所以我加了两个关键机制:心跳包和自动重连。
usingHslCommunication.ModBus;usingSystem.Threading;publicclassPlcController{privatereadonlyModbusTcpNet_plc;privateThread_heartBeatThread;privatevolatilebool_isRunning;publicPlcController(stringip,intport=502){_plc=newModbusTcpNet(ip,port);_plc.ConnectTimeOut=2000;// 连接超时2秒}publicboolConnect(){varresult=_plc.ConnectServer();if(!result.IsSuccess){Console.WriteLine($"PLC连接失败:{result.Message}");returnfalse;}// 启动心跳线程_isRunning=true;_heartBeatThread=newThread(HeartBeatLoop){IsBackground=true};_heartBeatThread.Start();returntrue;}privatevoidHeartBeatLoop(){while(_isRunning){Thread.Sleep(5000);// 每5秒心跳一次varresult=_plc.ReadInt16("M100");// 读一个无关的寄存器做心跳if(!result.IsSuccess){Console.WriteLine("PLC心跳失败,尝试重连...");_plc.ConnectServer();}}}publicboolSendDefectSignal(boolhasDefect){// 写入M100:1=有缺陷,0=正常,重试3次for(inti=0;i<3;i++){varresult=_plc.Write("M100",(short)(hasDefect?1:0));if(result.IsSuccess)returntrue;Thread.Sleep(100);}Console.WriteLine("写入PLC失败,已重试3次");returnfalse;}publicvoidDisconnect(){_isRunning=false;_heartBeatThread?.Join();_plc.ConnectClose();}}3. PLC控制逻辑:简单到电工都能维护
PLC侧我用西门子TIA Portal写的,逻辑非常简单:当M100为1时,Q0.0输出触发分拣气缸,延迟2秒后复位。给大家看时序图,一目了然:
三、实战效果:三个月收回成本,老板笑开了花
这套系统上线后,我每周都去汽配厂跟进数据,给大家看真实的效果:
- 人工成本:从6个目检工减到1个(只负责补料和设备巡检),每月省3万;
- 漏检率:从5%降到0.1%,客户投诉几乎为零;
- 产线节拍:从每分钟30件提到40件,产能提升33%。
唯一的小插曲是车间里的电磁干扰,一开始相机偶尔丢帧,后来给相机的GigE线加了两个磁环,问题就解决了。
四、常见问题与优化建议
1. 推理速度不够快怎么办?
如果产线节拍要求更高(比如每分钟60件),可以试试这两个方法:
- 模型量化:用ONNX Runtime把FP32模型转成INT8,速度能提30%,精度损失不到1%;
- OpenVINO加速:如果工控机是Intel的CPU,用OpenVINO执行Provider,推理速度能再提20%。
2. 通信丢包怎么解决?
除了我代码里的心跳和重连,还可以加两个机制:
- 请求确认:PLC收到信号后,写一个寄存器给上位机确认,没收到确认就重发;
- 报警机制:连续5次通信失败,上位机弹出报警窗口,同时触发PLC的声光报警。
3. 相机触发时机怎么选?
尽量用硬件触发(比如PLC输出一个24V信号给相机),不要用软件定时触发。硬件触发能保证每次拍照都在产品的同一位置,检测精度更高。
最后说两句
工业现场的多设备联动,从来不是拼技术有多高深,而是拼稳定、可靠、易维护。C#上位机+YOLO+PLC这套组合,既能利用YOLO的强大视觉能力,又能发挥PLC的控制稳定性,而且对传统工控人非常友好——不用学Python,不用搞复杂的框架,就能把视觉检测落地到产线。
如果大家在实际项目中遇到类似问题,欢迎一起探讨。