历经11年从事工业上位机开发工作, 我目睹许许多多工程师于AI视觉这条道路上出现走错路的状况。
网上百分之九十的YOLO教程, 都是关于生态方面的, 而工业现场百分之九十的上位机, 都是基于C# /WPF进行开发的, 于是便出现了一个极其尴尬的状况, 所做的原型在实验室运行得挺不错, 然而一到现场, 要集成到现有的系统时, 就出现了各种各样的问题, 比如打包困难、部署遇到难题、维护存在难点、与PLC通信颇具艰难, 终归项目在“最后一公里”受阻, 活生生地被拖垮了。
不少人由此衍生出了一种误解, 即: 运用C#去开展YOLO工业视觉, 其门槛相当高, 这要求具备深厚的AI功底以及C++底层能力。
就在今天, 我会凭借自己诸多落地项目所积累的实战经验来告知你, 当下的C#与YOLO相结合, 其门槛着实并非很高。身为一名普通的C#上位机工程师, 只要掌握基本的面向对象编程, 在一周之内, 便能够制作出一个可以在产线上运行的工业级视觉检测系统。
一、先搞清楚:为什么工业界最终选择了 C#+YOLO?
在决定技术路线之前, 我们得先明确工业场景的核心诉求, 核心诉求是稳定要优先, 部署需简单, 维护成本要低, 还要与现有系统无缝地集成。
我尝试过市面上差不多所有的技术组合的情况, 踩过数不清的坑, 最终在几十个历经验证的项目当中, C#上位机加上/11, 再加上ONNX, 是当下综合总体表现最为良好的方案。
表格
很多人会说:" 做 YOLO 不是更简单吗?"
确实, 写个demo是简单的, 然而工业现场并非实验室。你去试着把程序打包成一个单文件exe , 接着部署到没有网络、也没有环境的工控机上。你再试着让一个只会C#的现场维护工程师去调试代码。你又去试试让程序连续跑7×24小时而不出现内存泄漏。
这些问题,在 C# 生态里根本就不是问题。
二、C#+YOLO 工业视觉系统架构
许多人一旦听闻AI视觉便会觉得繁杂, 实际上将整个系统进行拆解后极为简单, 存在五个核心模块。
第 1 层为图像采集层, 这一层要对接工业相机, 像海康、大华、巴斯勒等品牌的相机, 由其来负责图像的硬触发或者软触发采集, 紧接着是预处理层,该层在采集好图像后, 要开展缩放、归一化、ROI 裁剪等操作, 随后进入推理层, 推理层需加载 YOLO 模型, 以此执行推理计算, 并输出检测结果, 之后是业务处理层, 业务处理层要针对检测结果做解析、判断以及统计, 进而生成控制信号, 最后是输出层, 输出层要用与 PLC 通信的方式来控制剔除机构, 同时还要生成质检报表, 并且把数据上传到 MES 系统。
你瞧, 是不是除了推理层属于新的事物, 其他的那四个模块, 不就是在我们平常做上位机这件事的时候, 天天都要去写的内容呀?
三、5 分钟快速上手:写出你的第一个 YOLO 检测程序
我能够十分肯定地担保, 只要你具备会去运用的能力, 在5分钟的限定时间之内, 就将可以成功运行通过第一个YOLO检测程序。
步骤 1:创建项目并安装依赖
开启, 处于二零二二年这个时段, 8 的控制台类型项目(假设是上位机的情形, 构建出WPF或者别的相关项目), 紧接着借助NuGet这一方式来实施 , 三个包:
Install-Package Microsoft.ML.OnnxRuntime.Gpu Install-Package OpenCvSharp4 Install-Package OpenCvSharp4.runtime.win就是这三个包, 不存在其他任何依赖, 不需要进行安装,不需要安装CUDA(当然要是有GPU那就更好了), 不需要任何复杂的环境配置。
步骤 2:准备 YOLO 模型
前往YOLO的官方网站, 获取下载一个预先训练好的模型, 接着把它导出成为ONNX格式。
# 这一步只需要在你的开发电脑上做一次 from ultralytics import YOLO model = YOLO("yolo11s.pt") model.export(format="onnx", opset=12, dynamic=False)有一个情况是, 当导出操作达成完成状态之后 , 你能够获得一个.onnx格式的文件 , 然后要把这个文件进行复制 , 复制的目的地是你的C#项目的输出目录的下面。
步骤 3:编写检测代码
核心代码不到 50 行:
using Microsoft.ML.OnnxRuntime; using Microsoft.ML.OnnxRuntime.Tensors; using OpenCvSharp; // 初始化推理会话 var options = new SessionOptions(); options.GraphOptimizationLevel = GraphOptimizationLevel.ORT_ENABLE_ALL; // 自动选择GPU或CPU try { options.AppendExecutionProvider_Cuda(0); Console.WriteLine("使用GPU加速"); } catch { options.AppendExecutionProvider_CPU(); Console.WriteLine("使用CPU推理"); } using var session = new InferenceSession("yolo11s.onnx", options); // 读取图片 using var img = Cv2.ImRead("test.jpg"); var resized = img.Resize(new Size(640, 640)); // 图像预处理 var input = new DenseTensor(new[] { 1, 3, 640, 640 }); for (int y = 0; y < 640; y++) { for (int x = 0; x < 640; x++) { input[0, 0, y, x] = resized.At(y, x)[2] / 255f; input[0, 1, y, x] = resized.At(y, x)[1] / 255f; input[0, 2, y, x] = resized.At(y, x)[0] / 255f; } } // 执行推理 var inputs = new List { NamedOnnxValue.CreateFromTensor("images", input) }; using var outputs = session.Run(inputs); var output = outputs.First().AsTensor(); // 解析结果 var results = new List(); for (int i = 0; i < output.Dimensions[1]; i++) { float confidence = output[0, i, 4]; if (confidence < 0.5f) continue; float x = output[0, i, 0]; float y = output[0, i, 1]; float w = output[0, i, 2]; float h = output[0, i, 3]; int classId = 0; float maxClassConf = 0; for (int c = 0; c < output.Dimensions[2] - 5; c++) { float classConf = output[0, i, 5 + c]; if (classConf > maxClassConf) { maxClassConf = classConf; classId = c; } } results.Add(new Detection { ClassId = classId, Confidence = confidence * maxClassConf, X1 = (x - w / 2) * img.Width / 640, Y1 = (y - h / 2) * img.Height / 640, X2 = (x + w / 2) * img.Width / 640, Y2 = (y + h / 2) * img.Height / 640 }); } // 绘制结果 foreach (var det in results) { Cv2.Rectangle(img, new Point((int)det.X1, (int)det.Y1), new Point((int)det.X2, (int)det.Y2), Scalar.Red, 2); Cv2.PutText(img, #34;{classNames[det.ClassId]} {det.Confidence:F2}", new Point((int)det.X1, (int)det.Y1 - 10), HersheyFonts.HersheySimplex, 0.5, Scalar.Red, 2); } Cv2.ImShow("Result", img); Cv2.WaitKey(0); class Detection { public int ClassId { get; set; } public float Confidence { get; set; } public float X1 { get; set; } public float Y1 { get; set; } public float X2 { get; set; } public float Y2 { get; set; } } string[] classNames = new string[] { "person", "bicycle", "car", /* ... 其他类别 */ };运行一下,你就会看到检测结果了。是不是比你想象的简单得多?
四、工业级优化:让你的系统稳定跑 7×24 小时
上面呈现的代码仅仅是一个演示示例, 若要应用于工业现场环境, 那么还得去开展一些至关重要的优化工作。而这些优化内容是我历经无数次挫折与失败后总结归纳出来的, 能够直接照搬使用。
1. 性能进行优化, 要使得推理速度能够飞速提升起来, 稳定性予以优化, 要杜绝内存出现泄漏以及崩溃情况, 工业场景开展适配, 要解决现场实际存在的问题, 真实工业案例展示, 我运用C#加上YOLO在一周时间内就搞定的项目。
我去年为天津的一家汽车零部件工厂制作了一个座椅滑轨缺陷检测系统, 此检测系统是运用C#进行开发的, 整个从开发直至验收的过程仅仅耗费了一周时间。
生产线上的需求是, 要对座椅滑轨表面进行检测, 检测的内容包括划痕、凹坑以及缺料这三种缺陷, 生产线运行的速度为每秒1.2米, 同时还要求误检的比率要小于0.1%, 并且漏检的比率等于0。
系统架构:
最终效果:
要是针对这个项目采用 + 来操作, 单单是授权方面的费用就得达到好几万块钱, 并且开发周期往少里说也得有一个月。然而要是运用 C#+YOLO 的方式, 不但授权是免费的, 而且开发速度相较于之前还加快了好几倍呢。
六、那些年之内, 我曾经踩过的坑, 你不用再去踩了, 其中坑1是, 推理所得到的结果, 跟端并不一致。
原因:图像预处理方式不同(归一化、通道顺序、数据类型)。
解决办法: 做到严格依规与C#的预处理代码对齐, 进行逐像素的对比。我曾在将BGR转换为RGB之际弄反了, 致使检测精度降低了30%, 查找了足足两天时间才把问题找出来。
坑 2:内存泄漏,程序跑几个小时就崩溃
Mat对象没能予以及时地释放, ONNX会话未能进行正确地销毁, 这便是原因。
办法是: 全部并非托管的资源达成接口, 运用using语句。要记住: 于C#之中, 只要是跟并非托管代码产生交互作用的对象, 均需要手动去解除占用。
坑 3:工业现场光照变化大,检测精度下降
原因:训练数据没有覆盖所有光照条件。
解决办法是, 去采集处于各异光照状况下的图像, 以此来增添数据增强。于相机一端设置自动曝光以及自动增益, 又或者采用环形光源还有背光光源。
坑 4:产线速度快,剔除位置不准确
原因:信号传输延迟、气缸响应时间不一致。
解决办法是, 精确地去计算延迟的时间, 运用编码器实现同步, 于PLC那个地方达成精准的延时, 而非在上位机那边进行延时操作。
七、最后说几句真心话
好多人认为AI视觉极为高大上, 有着颇高的门槛。实则并非如此。当下的工具链已然相当成熟, YOLO将模型训练以及推理的复杂度降至最低, ONNX把跨平台部署的问题给解决掉了, C#把工业上位机开发的效率提升至最高。
身为一名平常的 C# 上位机工程师, 无需晓得啥神经网络原理, 不必会编写 CUDA 内核, 只要会去调用 API, 会对图像予以处理, 会跟 PLC 展开通信, 便能够打造出工业级的视觉检测系统。
当然了, 我并非是在讲不好, 它极为适合用于开展算法研究以及进行原型验证, 然而在工业落地此种场景之下, C#加上YOLO绝对是当下最为出色的选择。
假设你正从事工业自动化方面的工作, 有着为自身设备增添AI视觉能力的想法, 那么就不要再迟疑了, 去尝试一下C#+YOLO这样一套方案吧。请相信我, 日后你会回过头来对我心怀感激的。