上位机视觉通信的整体架构:采集层 → 处理层 → 应用层
前三篇讲了通信本质、设备对话、串口 vs Socket。这一篇把它们组装成一个完整的机器视觉上位机系统——从相机拍照,到算法处理,到界面显示和业务决策。
一、先看全局:一张架构图
┌───────────────────────────────────────────────────────────┐ │ 应用层(Application) │ │ 界面显示 │ 业务逻辑 │ 数据存储 │ 报警 │ 与MES/PLC通信 │ └───────────────────────────▲───────────────────────────────┘ │ 结果 / 指令 ┌───────────────────────────┴───────────────────────────────┐ │ 处理层(Processing) │ │ 图像预处理 │ 算法检测 │ 测量 │ 识别 │ 结果判定 │ └───────────────────────────▲───────────────────────────────┘ │ 图像数据 ┌───────────────────────────┴───────────────────────────────┐ │ 采集层(Acquisition) │ │ 工业相机 │ 光源 │ 触发信号 │ 图像采集卡 │ 相机SDK │ └───────────────────────────▲───────────────────────────────┘ │ 光 → 电信号 被检测物体核心思想:数据自下而上流动,指令自上而下传递。
二、采集层:把"光"变成"图像"
1. 采集层要解决什么?
| 问题 | 说明 |
|---|---|
| 拍什么 | 相机选型(面阵/线阵、分辨率、帧率) |
| 怎么拍 | 光源、镜头、曝光、触发方式 |
| 何时拍 | 硬触发(光电传感器)/ 软触发(软件命令) |
| 传给谁 | 通过 SDK / 采集卡把图像交给处理层 |
2. 采集层的"通信"发生在这里
采集层内部其实有三次通信:
① PLC/传感器 ──触发信号──► 相机 (硬触发,通常是 IO 电平) ② 相机 ──图像数据──► 上位机 (GigE Vision / USB3 Vision / Camera Link / CoaXPress) ③ 上位机 ──控制命令──► 相机 (设置曝光、增益、触发模式)3. C# 里的采集层代码骨架
以GigE Vision相机(如海康、Basler)为例:
publicinterfaceICamera{voidOpen();voidSetTriggerMode(TriggerModemode);ImageDataGrab(inttimeoutMs);voidClose();}// 海康相机示例(伪代码,实际用 MvCameraControl.NET)publicclassHikCamera:ICamera{privateIntPtr_handle;publicvoidOpen(){// 枚举设备 → 创建句柄 → 打开MV_CC_EnumDevices(...,refdeviceList);MV_CC_CreateHandle(ref_handle,refdeviceInfo);MV_CC_OpenDevice(_handle);}publicvoidSetTriggerMode(TriggerModemode){// 硬触发 or 软触发MV_CC_SetEnumValue(_handle,"TriggerMode",mode==TriggerMode.Hard?1u:0u);MV_CC_SetEnumValue(_handle,"TriggerSource",mode==TriggerMode.Hard?0u:7u);}publicImageDataGrab(inttimeoutMs){// 取一帧图像MV_FRAME_OUT_INFO_EXframeInfo=newMV_FRAME_OUT_INFO_EX();byte[]buffer=newbyte[width*height*channels];MV_CC_GetOneFrameTimeout(_handle,buffer,buffer.Length,refframeInfo,timeoutMs);returnnewImageData(buffer,frameInfo.nWidth,frameInfo.nHeight);}publicvoidClose(){MV_CC_CloseDevice(_handle);MV_CC_DestroyHandle(_handle);}}4. 采集层的工程要点
- 触发方式:硬触发比软触发确定性高(生产线节拍要求)
- 丢帧处理:缓存队列、超时重取
- 多相机同步:硬件同步信号 or PTP(IEEE 1588)
- 断线重连:网口相机断网后要能自动恢复
三、处理层:把"图像"变成"结果"
1. 处理层要解决什么?
原始图像 ↓ 预处理 灰度化、滤波、二值化、ROI裁剪 ↓ 特征提取 边缘、角点、轮廓、模板 ↓ 检测/测量/识别 缺陷检测、尺寸测量、OCR、定位、分类 ↓ 结果判定 OK / NG + 数据2. 处理层也是"通信密集区"
处理层内部各模块之间也要通信:
┌──────────┐ 图像 ┌──────────┐ 特征 ┌──────────┐ │ 预处理 │ ───────► │ 算法引擎 │ ─────► │ 判定逻辑 │ └──────────┘ └──────────┘ └──────────┘ ▲ │ │ 结果回调 │ └──────────────────────────────────────────┘关键设计:处理层必须流水线化,否则相机等算法,节拍跟不上。
3. C# 里的处理层骨架
publicclassProcessingPipeline{privatereadonlyBlockingCollection<ImageData>_inputQueue=new(10);privatereadonlyBlockingCollection<VisionResult>_outputQueue=new(10);publicvoidStart(intworkerCount=2){// 多个处理线程并发处理for(inti=0;i<workerCount;i++){Task.Run(()=>ProcessLoop());}}privatevoidProcessLoop(){foreach(varimagein_inputQueue.GetConsumingEnumerable()){varresult=Process(image);_outputQueue.Add(result);}}privateVisionResultProcess(ImageDataimage){// 1. 预处理usingvarmat=ImageConverter.ToMat(image);Cv2.CvtColor(mat,mat,ColorConversionCodes.BGR2GRAY);Cv2.GaussianBlur(mat,mat,newSize(5,5),0);// 2. 检测(示例:找轮廓)Cv2.Threshold(mat,mat,100,255,ThresholdTypes.Binary);Cv2.FindContours(mat,outvarcontours,out_,RetrievalModes.External,ContourApproximationModes.ApproxSimple);// 3. 判定boolisOk=contours.Length==1;doublearea=contours.Length>0?Cv2.ContourArea(contours[0]):0;returnnewVisionResult{IsOk=isOk,Area=area,Timestamp=DateTime.Now,ImageId=image.Id};}publicvoidEnqueue(ImageDataimage)=>_inputQueue.Add(image);publicVisionResultDequeue(inttimeoutMs)=>_outputQueue.TryTake(outvarr,timeoutMs)?r:null;}4. 处理层的工程要点
- 算法与采集解耦:用队列缓冲,避免阻塞相机
- 多线程/多核:OpenCV +
Parallel或 GPU(CUDA) - 结果可追溯:每张图、每个结果都要有 ID
- 参数可配置:算法参数不能写死,要能下发
四、应用层:把"结果"变成"价值"
1. 应用层要解决什么?
| 功能 | 说明 |
|---|---|
| 界面显示 | 实时图像、结果、统计 |
| 业务逻辑 | OK/NG 分流、报警、计数 |
| 数据存储 | 图像存盘、结果入库 |
| 对外通信 | 给 PLC 发结果、给 MES 传数据 |
| 用户管理 | 权限、配方、日志 |
2. 应用层是"对外通信的总出口"
┌──────────────┐ │ 应用层 │ └──┬───┬───┬───┘ │ │ │ ┌──────────────┘ │ └──────────────┐ ▼ ▼ ▼ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ PLC │ │ MES │ │ 数据库 │ │(Modbus) │ │(HTTP/DB)│ │(SQL) │ └─────────┘ └─────────┘ └─────────┘3. C# 里的应用层骨架
publicclassVisionApplication{privatereadonlyICamera_camera;privatereadonlyProcessingPipeline_pipeline;privatereadonlyIPlcClient_plc;// 给 PLC 发结果privatereadonlyIMesClient_mes;// 给 MES 传数据privatereadonlyIResultRepository_repo;// 存数据库publicasyncTaskRunAsync(CancellationTokenct){_pipeline.Start();while(!ct.IsCancellationRequested){// 1. 采集varimage=_camera.Grab(1000);if(image==null)continue;// 2. 送入处理_pipeline.Enqueue(image);// 3. 取结果varresult=_pipeline.Dequeue(500);if(result==null)continue;// 4. 显示(UI 线程)OnResultReady?.Invoke(result);// 5. 业务动作if(result.IsOk){await_plc.WriteResultAsync(true);await_repo.SaveAsync(result);}else{await_plc.WriteResultAsync(false);// 触发剔除await_mes.ReportNgAsync(result);// 上报 MESawait_repo.SaveAsync(result);}}}publiceventAction<VisionResult>OnResultReady;}4. 应用层的工程要点
- UI 不阻塞:图像显示用双缓冲,算法在后台线程
- 通信超时:给 PLC/MES 的调用都要有超时和重试
- 数据一致性:图像存盘和数据库记录要对应
- 可配置:配方、阈值、通信参数都要能改
五、三层之间的"通信契约"
三层之间不是随便传数据,而是有明确的契约:
采集层 → 处理层
publicclassImageData{publiclongId{get;set;}// 唯一标识publicbyte[]Pixels{get;set;}// 像素数据publicintWidth{get;set;}publicintHeight{get;set;}publicintChannels{get;set;}publicDateTimeCaptureTime{get;set;}publicstringCameraId{get;set;}// 多相机时区分}处理层 → 应用层
publicclassVisionResult{publiclongImageId{get;set;}// 关联图像publicboolIsOk{get;set;}publicdouble[]Measurements{get;set;}publicstringDefectType{get;set;}publicTimeSpanProcessingTime{get;set;}publicDateTimeTimestamp{get;set;}}应用层 → 外部设备
| 目标 | 协议 | 内容 |
|---|---|---|
| PLC | Modbus TCP / 西门子 S7 | OK/NG 信号、坐标 |
| MES | HTTP / OPC UA / 数据库 | 检测记录、统计 |
| 报警灯 | IO / 串口 | 三色灯控制 |
| 数据库 | SQL | 图像路径 + 结果 |
契约稳定的价值:任何一层换了实现,其他层不受影响。
六、完整数据流:一次检测的全过程
① 光电传感器检测到工件到位 │ ▼ ② 触发信号 → 相机(硬触发) │ ▼ ③ 相机曝光、成像 │ ▼ ④ 图像通过 GigE 传到上位机 │ ▼ ⑤ 采集层封装 ImageData,入队 │ ▼ ⑥ 处理线程取出,预处理 + 算法 │ ▼ ⑦ 生成 VisionResult,出队 │ ▼ ⑧ 应用层:显示 + 存盘 + 发 PLC │ ▼ ⑨ PLC 控制执行机构(剔除/放行) │ ▼ ⑩ MES 记录,数据库归档每一层都在"通信",只是通信的对象不同。
七、工程中的典型难点
| 难点 | 现象 | 解决 |
|---|---|---|
| 节拍跟不上 | 相机等算法 | 流水线 + 多线程 + GPU |
| 丢帧 | 图像不连续 | 增大缓存、提高优先级 |
| 触发不同步 | 拍早/拍晚 | 硬触发 + 延时补偿 |
| 通信超时 | PLC 无响应 | 超时重试 + 心跳 |
| 内存爆 | 图像没释放 | using/ 手动 Dispose |
| UI 卡顿 | 界面无响应 | 后台线程 + 双缓冲 |
| 断线 | 相机/PLC 掉线 | 自动重连 + 状态提示 |
| 数据不一致 | 图库不匹配 | 事务 + 唯一 ID |
八、C# 技术选型建议
| 层 | 常用技术 |
|---|---|
| 采集层 | 相机厂商 SDK(海康 MVS、Basler pylon)、System.IO.Ports(串口触发) |
| 处理层 | OpenCvSharp、HalconDotNet、EmguCV、Cognex VisionPro |
| 应用层 UI | WPF / WinForms / Avalonia |
| 应用层通信 | System.Net.Sockets、NModbus、S7.Net、OPC UA SDK |
| 数据存储 | SQLite / SQL Server / MySQL + Dapper/EF Core |
| 并发 | Task、Channel<T>、BlockingCollection<T> |
九、总结:三句话记住整体架构
- 采集层:把光变成图像,解决"看得见"。
- 处理层:把图像变成结果,解决"看得懂"。
- 应用层:把结果变成价值,解决"用得上"。
记忆口诀:
采集层里相机忙,触发同步不能忘 处理层中算法强,流水多线程跟上 应用层对外通信广,PLC MES 数据库都要连上 三层契约要稳定,解耦设计寿命长十、下一步建议
- 画图:用 Visio / draw.io 画出你项目的三层架构
- 定义契约:明确每层之间的数据类
- 写骨架:把上面三个 C# 类补齐、跑通
- 加通信:接入真实相机 SDK + Modbus TCP 给 PLC
- 压测:连续跑 8 小时,看丢帧、内存、节拍
做完这五步,你就有了一个可以上生产线的视觉上位机框架。