系列:C# 视觉量检测工程师成长路线图本篇定位:将视觉算法封装为可交付、可维护、可扩展的工业软件产品目标读者:已掌握视觉算法、需要提升软件工程能力的开发者前置知识:C# 高级特性、设计模式、多线程编程阅读时间:约 20 分钟一、为什么视觉软件需要工程化?很多视觉开发者有这样的经历:算法在 HDevelop 里验证通过了,但集成到 C# 软件后问题不断现场调试时发现一个 bug,改完后引入了三个新 bug客户要求增加一个检测项,发现代码耦合严重,改动牵一发而动全身项目交付后,运维人员看不懂代码,每次维护都要原开发者到场这些问题的根源是:视觉算法能力 ≠ 软件工程能力。工业视觉系统不是实验室 demo,而是需要在产线 7×24 小时稳定运行的软件产品。二、产线级视觉软件的架构设计2.1 分层架构插件化架构的核心思想:核心系统定义接口,具体功能通过插件实现,实现解耦和可扩展。┌─────────────────────────────────────────────────────────────┐ │ 表现层 (Presentation) │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │ │ │ 实时图像显示 │ │ 参数配置界面 │ │ 数据报表/趋势图 │ │ │ │ (WPF/Halcon)│ │ (MVVM) │ │ (LiveCharts) │ │ │ └─────────────┘ └─────────────┘ └─────────────────────┘ │ ├─────────────────────────────────────────────────────────────┤ │ 业务逻辑层 (Business Logic) │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │ │ │ 检测流程控制 │ │ 判定逻辑 │ │ 数据追溯/条码绑定 │ │ │ │ (状态机) │ │ (OK/NG规则) │ │ (MES对接) │ │ │ └─────────────┘ └─────────────┘ └─────────────────────┘ │ ├─────────────────────────────────────────────────────────────┤ │ 算法引擎层 (Algorithm Engine) │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │ │ │ 图像预处理 │ │ 检测算法 │ │ 标定模块 │ │ │ │ (Halcon封装)│ │ (插件化) │ │ (内参/手眼) │ │ │ └─────────────┘ └─────────────┘ └─────────────────────┘ │ ├─────────────────────────────────────────────────────────────┤ │ 硬件抽象层 (Hardware Abstraction) │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │ │ │ 相机采集 │ │ 运动控制 │ │ PLC/光源/IO │ │ │ │ (GigE/USB3) │ │ (Modbus/ADS)│ │ (Modbus/OPC) │ │ │ └─────────────┘ └─────────────┘ └─────────────────────┘ │ ├─────────────────────────────────────────────────────────────┤ │ 基础设施层 (Infrastructure) │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │ │ │ 日志系统 │ │ 配置管理 │ │ 异常报警/远程诊断 │ │ │ │ (NLog/Serilog)│ │ (JSON/XML) │ │ (WebSocket/HTTP) │ │ │ └─────────────┘ └─────────────┘ └─────────────────────┘ │ └─────────────────────────────────────────────────────────────┘2.2 核心设计原则原则说明实践单一职责每个类只做一件事相机采集、图像处理、通信各自独立依赖倒置依赖接口而非实现定义 ICamera、IAlgorithm 接口开闭原则对扩展开放,对修改关闭新检测项通过插件添加接口隔离客户端不依赖不需要的接口细粒度接口设计三、插件化算法引擎3.1 接口定义/// summary/// 视觉算法接口/// /summarypublicinterfaceIVisionAlgorithm{stringName{get;}stringVersion{get;}/// summary/// 初始化算法(加载模型、配置参数)/// /summaryvoidInitialize(AlgorithmConfigconfig);/// summary/// 执行检测/// /summaryAlgorithmResultExecute(HObjectimage,Dictionarystring,objectcontext);/// summary/// 释放资源/// /summaryvoidDispose();}/// summary/// 算法配置/// /summarypublicclassAlgorithmConfig{publicstringAlgorithmName{get;set;}publicstringParameterFilePath{get;set;}publicDictionarystring,objectParameters{get;set;}}/// summary/// 算法执行结果/// /summarypublicclassAlgorithmResult{publicboolSuccess{get;set;}publicstringErrorCode{get;set;}publicstringErrorMessage{get;set;}publicboolIsOK{get;set;}publicHObjectOutputImage{get;set;}publicListMeasurementDataMeasurements{get;set;}publicTimeSpanExecutionTime{get;set;}publicDictionarystring,objectExtraData{get;set;}}publicclassMeasurementData{publicstringName{get;set;}publicdoubleValue{get;set;}publicstringUnit{get;set;}publicdoubleNominal{get;set;}publicdoubleUpperLimit{get;set;}publicdoubleLowerLimit{get;set;}publicboolIsPass{get;set;}}3.2 插件加载器(基于反射)publicclassAlgorithmPluginLoader{privateDictionarystring,TypealgorithmTypes=newDictionarystring,Type();/// summary/// 从 DLL 文件加载算法插件/// /summarypublicvoidLoadPlugins(stringpluginDirectory){if(!Directory.Exists(pluginDirectory))return;vardllFiles=Directory.GetFiles(pluginDirectory,"*.dll");foreach(vardllindllFiles){try{varassembly=Assembly.LoadFrom(dll);vartypes=assembly.GetTypes().Where(t=typeof(IVisionAlgorithm).IsAssignableFrom(t)!t.IsInterface!t.IsAbstract);foreach(vartypeintypes){// 实例化一次获取名称