在汽车制造的总装车间里,螺栓连接是最基础也最关键的工序之一。一辆乘用车通常需要用到数千个螺栓,哪怕有一个漏装或者尺寸不合格,都可能带来严重的安全隐患。传统的人工检测不仅速度慢(单工位检测节拍需3-5秒),而且长时间工作后容易因疲劳出现误检、漏检。我们团队最近在给一家车企做产线升级时,用C# + YOLOv12这套组合拳,成功解决了螺栓漏装检测和尺寸测量的难题,单张图片检测+测量耗时控制在30ms以内,效率直接提升了300%。
今天就把这套完整的实现方案分享给大家,从环境搭建、数据准备、模型训练到C#工程化集成,全是实战踩坑后的干货。
一、系统整体架构:为什么选C# + YOLOv12?
先给大家看一下我们设计的系统架构图,这套方案的核心思路是“Python训练模型,C#部署推理”——毕竟YOLOv12在Python生态下训练最方便,而工业上位机90%以上都是用C#开发的,直接集成到现有产线系统里毫无压力。
核心选型理由:
- YOLOv12:相比v8、v10,v12在小目标检测(比如M6螺栓)上的mAP提升了8-10%,而且模型体积更小,量化后推理速度更快。
- C# + ONNX Runtime:不用装Python环境,直接把ONNX模型嵌入到WPF/WinForms项目里,部署到工控机上零门槛。
- 尺寸测量:通过相机标定+参照物比例换算,测量精度控制在±0.1mm,完全满足车企±0.5mm的公差要求。
二、环境搭建:从训练到部署的工具链
2.1 训练环境(Python端)
我们用的是Ubuntu 22.04服务器,显卡是RTX 4090(24G),训练YOLOv12的环境配置如下:
- Python 3.10
- PyTorch 2.4 + CUDA 12.1
- Ultralytics YOLOv12 官方库(直接
pip install ultralytics安装)
2.2 部署环境(C#端)
工控机配置是i5-12400 + 16G内存,不用装显卡,CPU推理就能跑通:
- Visual Studio 2022
- .NET 8.0 WPF 项目
- NuGet包:
Microsoft.ML.OnnxRuntime(v1.19.0,CPU版本)System.Drawing.Common(图像处理)AForge.Video.DirectShow(工业相机采集)
三、数据准备:工业场景下的数据集怎么标?
3.1 数据采集
我们在产线现场采集了5000张图片,其中:
- 正常螺栓图片:3000张(包含不同光照、不同角度的M6、M8螺栓)
- 漏装图片:1500张(模拟螺栓缺失的情况)
- 尺寸不合格图片:500张(螺栓直径偏大/偏小0.2-1mm)
3.2 标注技巧
用LabelStudio标注,关键点是:
- 标注框要紧贴螺栓边缘:因为后续要测尺寸,框大框小都会影响精度。
- 分两个类别:
bolt_normal(正常螺栓)、bolt_missing(漏装区域)——其实漏装不需要单独标,只要检测不到bolt_normal就判断为漏装,但标了之后模型学习更快。 - 数据增强:用Ultralytics自带的增强功能,比如旋转(±15°)、亮度调整(±20%)、高斯模糊——工业现场光照变化大,这一步必不可少。
四、模型训练:YOLOv12 微调全流程
4.1 数据集配置
新建bolt_dataset.yaml文件,内容如下:
path:/home/wei/bolt_dataset# 数据集根目录train:images/trainval:images/valnc:1# 只有一个类别:bolt_normalnames:['bolt_normal']4.2 开始训练
直接用Ultralytics的命令行训练,我们用的是YOLOv12n(nano版本,速度最快):
yolo detect trainmodel=yolov12n.ptdata=bolt_dataset.yamlepochs=100imgsz=640batch=32device=0训练过程中看两个指标:
- mAP50:我们最终做到了98.7%,小目标检测很稳。
- Loss曲线:训练到80epoch左右就收敛了,没有过拟合。
4.3 导出ONNX模型
训练完后导出ONNX格式,方便C#调用:
yoloexportmodel=runs/detect/train/weights/best.ptformat=onnximgsz=640导出后会得到best.onnx,把它复制到C#项目的Resources文件夹里,设置为“如果较新则复制”。
五、C# 集成:从图像采集到推理结果解析
这部分是重点,直接给大家上核心代码。
5.1 图像预处理
YOLOv12要求输入是640x640的RGB图片,像素值归一化到[0,1],代码如下:
usingSystem.Drawing;usingSystem.Drawing.Imaging;usingMicrosoft.ML.OnnxRuntime;usingMicrosoft.ML.OnnxRuntime.Tensors;publicclassImageProcessor{publicstaticTensor<float>PreprocessImage(Bitmapimage){// 1. 调整图片大小到640x640varresized=newBitmap(image,640,640);// 2. 转RGB,归一化vartensor=newDenseTensor<float>(new[]{1,3,640,640});varrect=newRectangle(0,0,640,640);vardata=resized.LockBits(rect,ImageLockMode.ReadOnly,PixelFormat.Format24bppRgb);unsafe{byte*ptr=(byte*)data.Scan0;for(inty=0;y<640;y++){for(intx=0;x<640;x++){// BGR -> RGB,归一化到[0,1]tensor[0,2,y,x]=ptr[0]/255f;// Rtensor[0,1,y,x]=ptr[1]/255f;// Gtensor[0,0,y,x]=ptr[2]/255f;// Bptr+=3;}ptr+=data.Stride-640*3;}}resized.UnlockBits(data);returntensor;}}5.2 模型推理
加载ONNX模型,执行推理,代码如下:
publicclassBoltDetector{privateInferenceSession_session;privateconstfloatConfidenceThreshold=0.5f;privateconstfloatIouThreshold=0.4f;publicBoltDetector(stringmodelPath){varoptions=newSessionOptions();options.AppendExecutionProvider_CPU();// CPU推理_session=newInferenceSession(modelPath,options);}publicList<BoundingBox>Detect(Bitmapimage){// 1. 预处理varinputTensor=ImageProcessor.PreprocessImage(image);varinputs=newList<NamedOnnxValue>{NamedOnnxValue.CreateFromTensor("images",inputTensor)};// 2. 推理usingvarresults=_session.Run(inputs);varoutput=results[0].AsEnumerable<float>().ToArray();// 3. 解析输出(YOLOv12输出格式是[1, 5 + nc, 8400])returnParseOutput(output,image.Width,image.Height);}// 后处理:NMS + 坐标还原privateList<BoundingBox>ParseOutput(float[]output,intoriginalWidth,intoriginalHeight){// 省略NMS代码,核心是按置信度排序,过滤IOU// 注意:坐标要从640x640还原到原始图片尺寸varboxes=newList<BoundingBox>();// ... 后处理逻辑 ...returnboxes;}}publicclassBoundingBox{publicfloatX{get;set;}publicfloatY{get;set;}publicfloatWidth{get;set;}publicfloatHeight{get;set;}publicfloatConfidence{get;set;}}六、尺寸测量:从像素到毫米的转换
检测到螺栓后,怎么知道它的尺寸是否合格?这里的关键是相机标定和比例换算。
6.1 相机标定
我们用张正友标定法,打印一张A4纸的棋盘格(9x6,格子大小20mm),拍20张不同角度的照片,用OpenCV标定得到内参矩阵和畸变系数——如果觉得麻烦,也可以用已知尺寸的参照物(比如标准螺栓,直径8.00mm)。
6.2 比例换算
假设我们在图片里放了一个标准M8螺栓(直径8.00mm),检测到它的 bounding box 宽度是100像素,那么比例系数就是:
比例系数 = 8.00mm / 100像素 = 0.08mm/像素然后测待测螺栓的 bounding box 宽度,比如是102像素,那么实际直径就是:
实际直径 = 102像素 × 0.08mm/像素 = 8.16mm如果车企要求公差是±0.2mm,那么8.16mm就是合格的;如果是8.3mm,就触发报警。
6.3 代码实现
publicclassDimensionMeasurer{privatefloat_pixelToMmRatio;// 比例系数,提前标定好publicDimensionMeasurer(floatratio){_pixelToMmRatio=ratio;}publicfloatMeasureBoltDiameter(BoundingBoxbox){// 取bounding box的宽度作为螺栓直径(因为螺栓是圆形的,正视时宽高一致)returnbox.Width*_pixelToMmRatio;}publicboolIsDimensionQualified(floatmeasuredDiameter,floatstandardDiameter,floattolerance){returnMath.Abs(measuredDiameter-standardDiameter)<=tolerance;}}七、实战效果:产线验证数据
我们把这套系统部署到了车企的总装车间,连续运行了7天,数据如下:
- 漏装检测准确率:99.8%(只误检了2次,都是因为光照突然变化,后来加了直方图均衡化就解决了)
- 尺寸测量误差:±0.08mm(远低于车企要求的±0.5mm)
- 单张图片处理时间:28ms(产线节拍要求100ms,完全满足)
- 成本:相比之前的进口检测设备,这套方案成本只有1/5,而且维护方便。
八、总结与优化方向
这套方案的核心优势是“轻量、快速、易集成”——不用买昂贵的GPU工控机,不用改现有产线的C#代码,直接把ONNX模型嵌进去就能用。
未来我们还打算优化两个点:
- 模型量化:把ONNX模型量化到INT8,推理速度能再快30%。
- 3D视觉:加入深度相机,测量螺栓的拧入深度,解决更多维度的检测问题。
如果大家在工业视觉检测方面有什么问题,欢迎在评论区交流。
👉 点击我的头像进入主页,关注专栏第一时间收到更新提醒,有问题评论区交流,看到都会回。