简介:这是一份面向计算机视觉初学者与进阶开发者的YOLO实时物体检测资源包,聚焦齿条、螺栓、螺母及裂缝检测等典型工业质检场景。资源完整呈现YOLO的核心设计思想,包括S×S网格划分、边界框坐标与置信度预测、条件类别概率计算,以及不同版本在速度与精度间的平衡策略,适合用来理解目标检测原理、对比YOLOv1至v4的改进点,并在实际项目中直接借鉴。压缩包共2000个文件,以txt辅助数据/说明、C语言源码(.c)和头文件(.h)为主,另有少量cpp与md文档,整体大小约199MB。文件结构对应Darknet框架的模块化实现,包含数据解析、网络层、区域检测等关键模块,便于按函数或主题查阅。目前已有67人学习下载,是一份适合系统阅读与二次开发的参考资料。借助这些源码和说明文档,读者可以掌握YOLO在工业质检中的部署思路,并据此调整网络结构或训练自己的检测模型。 一条装配线,齿条送料、螺栓锁紧、螺母拧入,最后还要肉眼扫一眼裂纹。人工盯久了就会疲劳,漏检一两个不算事,出了批次问题就是大事。传统视觉方案我可以做,边缘检测加形态学,但光照一变、零件一叠,阈值就要重调,这就是典型的翻车现场。YOLO实时物体检测是另一条路:把rack、bolt、nut、crack当作四类目标,用标注好的图片训练一个回归模型,类别和位置一次给出来,推理速度在边缘设备上也能跑实时。这篇文章写给正在做工业质检、自动分拣或者想用机器视觉替代人眼的工程师,我把数据、训练、部署和踩坑都过一遍。
2. 数据准备:从采集、标注到VOC转YOLO格式
2.1 采集策略:多角度多光照,每类到底要多少张
先说数据上的教训。齿条这类目标有个麻烦:齿部是重复纹理,螺栓和螺母又小又反光,光照角度不对,铝件表面全是高光,标注软件里连轮廓都看不清。我一般分三条线采集:零件平放俯视、侧视45度、试验台强光下暗背景。每条线各拍一批,齿条每类至少600张,螺栓螺母各1000张起步,crack裂纹样本能拿到多少先拿多少。为什么差这么多?因为齿条是大目标,一张图里可能就一两个,特征好学到;螺栓螺母在产线画面里往往只有几十个像素,属于小目标,数据少了模型根本记不住。
真实的裂纹样本哪怕只有150张,也比合成出来的2000张有用。合成裂纹的纹理和真实金属断裂面差距太大,模型学了之后会把机加工刀痕误判成裂纹,后续误检率会非常难看。所以裂纹样本能拍就尽量拍,不要图省事去生成。采集现场记得固定曝光参数,工业相机的自动增益会让同一零件的亮度在早晚班次都不一样,后期统一很痛苦。
2.2 类别设计:裂纹算单独一类还是做成属性
我在项目里把crack定为独立类别,而不是螺栓或齿条的附属性。YOLO是单标签检测,一个框只能有一个类别;如果裂纹画成齿条的子类,部署时就要加二次分类逻辑,实时性就打折扣。独立类别的代价是模型要把"rack本身"和"rack上的裂纹"分开学,这正好符合产线需求:目标框和缺陷框同时输出,下游机械臂扫一眼框的类别就能决定抓取还是剔除。
标注边界有几个坑。第一,裂纹框要紧贴裂纹区域,不要包住整个齿条,否则模型学到的是"齿条等于裂纹"。第二,齿条的齿槽在灰度图里很像裂纹,标注时一定要看彩色原图,按纹理走向判断,齿槽是规则的平行线,裂纹是曲折的断裂线。第三,螺栓头部的六角凹槽很容易被标成裂纹,我早期标注规范里没写清楚,返工了两天才改完。这些细节要在标注规范文档里写明白,多人标注时尤其重要。
2.3 VOC转YOLO格式:归一化坐标与类别索引
市面上标注工具导出常见的是VOC格式XML,而YOLO训练要的是txt加归一化坐标。这一步需要转换脚本,我一般写一个通用的scripts/voc_to_yolo.py:
import os import xml.etree.ElementTree as ET def convert(voc_dir, label_dir, class_names): os.makedirs(label_dir, exist_ok=True) for file in os.listdir(voc_dir): if not file.endswith('.xml'): continue tree = ET.parse(os.path.join(voc_dir, file)) root = tree.getroot() img_w = int(root.find('size/width').text) img_h = int(root.find('size/height').text) txt_path = os.path.join(label_dir, file.replace('.xml', '.txt')) with open(txt_path, 'w') as out: for obj in root.findall('object'): name = obj.find('name').text if name not in class_names: continue cls_id = class_names.index(name) box = obj.find('bndbox') x_min = float(box.find('xmin').text) y_min = float(box.find('ymin').text) x_max = float(box.find('xmax').text) y_max = float(box.find('ymax').text) # YOLO格式要求:中心点x、中心点y、宽、高,全部除以图片宽高归一化 x_center = (x_min + x_max) / 2 / img_w y_center = (y_min + y_max) / 2 / img_h w = (x_max - x_min) / img_w h = (y_max - y_min) / img_h # 防止标注越界,工业相机偶尔拍到目标贴边,xml里会出现负坐标 x_center = min(max(x_center, 0.0), 1.0) y_center = min(max(y_center, 0.0), 1.0) w = min(max(w, 0.0), 1.0) h = min(max(h, 0.0), 1.0) out.write(f"{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}\n") class_names = ['rack', 'bolt', 'nut', 'crack'] convert('dataset/annotations_xml', 'dataset/labels', class_names)脚本逻辑:遍历XML,取size标签里的宽高,把框坐标从像素转成0到1之间的中心点坐标和宽高。class_names的顺序就是训练时的类别编号,两边顺序必须一致,我见过有人改了类别列表顺序没重新转换,训练出来的模型类别全错位。脚本里做了越界保护,工业相机偶尔拍到目标贴边,XML里会出现负坐标或者超过宽高的数,不处理会在训练时报警告甚至丢图。转换完抽20%的txt和原图用可视化工具把框画回去,确认类别编号没串位,这一步半小时能省后面一个星期的排查时间。
2.4 数据划分与增强:让裂纹样本撑过训练
我按8:1:1把数据分成train、val、test。划分时以文件名前缀为分组单位,同一个零件ID的所有图片只进一个集合,不能直接random打乱。同零件训练和验证都出现过,模型相当于开卷考试,mAP虚高,部署到新批次零件上立刻现原形。
增强分两档。通用档用YOLO自带的mosaic、平移、翻转就够了,螺栓螺母这类小目标靠mosaic把小零件拼到大图里,模型能学到更丰富的上下文。裂纹样本单独做第二档增强:亮度扰动、高斯噪声、随机旋转15度以内。旋转角度不要太大,齿条有方向性,转90度变成竖齿条,和产线实际摆放不符,强行转会让模型学到错误的方向特征。裂纹样本从80张扩到300张,我靠的就是这组增强组合。
提示:划分数据时不要用shuffle直接切,先按文件名前缀分组,再随机选组。否则同一零件的训练图和验证图互相泄漏,mAP虚高到没有参考价值。
3. 训练:Anaconda环境配置到损失函数调参
3.1 Anaconda环境配置:yolov8的依赖边界
先讲环境配置,这是新手最容易卡住的地方。推荐用Anaconda建独立环境,避免系统Python被后续其他项目污染。我实际用过的组合如下:
conda create -n yolo python=3.10 -y conda activate yolo pip install torch==2.1.0 torchvision==0.16.0 --index-url https://download.pytorch.org/whl/cu121 pip install ultralytics版本选择有讲究。Python用3.10而不是3.12,因为torch的CUDA预编译包在3.10下最稳。torch指定2.1.0是为了和cu121匹配,如果机器是CUDA 11.8,把cu121改成cu118。装ultralytics时它会自动带opencv-python、numpy这些依赖。YOLOv8本身对依赖不苛刻,但numpy版本太新会和opencv冲突,报_ARRAY_API not found,我第一次配环境就踩过,解决方式是pip install numpy==1.24.4。装好后跑一句yolo predict source=https://ultralytics.com/images/bus.jpg,能出结果说明环境通了。
3.2 YOLO损失函数:box、cls、dfl三个部分各自管什么
YOLOv8的损失由三块构成:box回归损失、类别损失、DFL分布损失。box损失管预测框和真实框的偏差,类别损失管"这个框里是齿条还是螺栓",DFL损失比较特殊,它把边界框的坐标预测建模成离散分布,对小目标和小偏移更敏感。螺栓螺母在640分辨率下只有几十个像素,DFL能让预测框的坐标精度明显提升,这是YOLOv8比旧版YOLOv5在小零件上更好调的原因之一。
训练日志里会打印box_loss、cls_loss、dfl_loss三个值。我一般不看单次数值,只看它们是否随epoch下行。如果box_loss降到0.02附近不再动,说明定位精度到瓶颈,先查标注框是否贴边;如果cls_loss反复躁动,大概率是类别不平衡,crack样本太少时最常见。损失函数调参的实质是观察这三条曲线的走向,它们比mAP更能提前暴露问题。
3.3 训练命令与三组关键参数
数据准备好之后,训练命令本身不长,把参数含义吃透更重要:
yolo train \ data=/home/industrial/dataset/dataset.yaml \ model=yolov8s.pt \ epochs=200 \ batch=16 \ imgsz=640 \ lr0=0.01 \ mosaic=1.0 \ close_mosaic=10参数说明:model=yolov8s.pt表示用YOLOv8s的COCO预训练权重做迁移学习。工业零件和COCO物体差异大,我试过换m模型,精度没有明显提升,推理速度却掉了一半,所以s是性价比起点。epochs=200不要照抄,看val曲线提前停止。batch=16取决于显存,8G显卡跑16没问题,不够就降到8。imgsz=640训练和推理保持一致,部署时不要随意改分辨率,模型对输入尺寸敏感。close_mosaic=10表示最后10个epoch关闭mosaic增强,让模型在接近真实分布的样本上收尾,直接训到结束容易出现val震荡。
这里说下dataset.yaml的结构。class_names的顺序必须和转换脚本一致,否则类别错位:
path: /home/industrial/dataset train: images/train val: images/val names: 0: rack 1: bolt 2: nut 3: crack3.4 迁移学习:预训练权重要不要换
用COCO预训练权重在工业零件上属于标准做法。齿条、螺栓、螺母虽然COCO里没有,但预训练模型已经学到边缘、纹理、形状的通用表达,从头训练需要的数据量是三倍以上,而且收敛很慢。我见过同事坚持从scratch训练,300个epoch还没收敛到预训练权重80个epoch的水平,时间成本完全划不来。
预训练权重的边界在于:如果你的产品是半透明塑料件或者纯黑橡胶件,COCO学到的形状特征差异太大,这时候用model=yolov8s.yaml不带.pt从零开始,数据量要准备到每类2000张以上。判断标准很简单:先用预训练权重少量epoch跑一遍,如果loss完全不下降,再考虑从零训练。
4. 实时推理部署:置信度门限与边缘设备选型
4.1 最小推理脚本与测试图片
训练结束后,best.pt保存在runs/detect/train/weights/下。推理脚本如下:
from ultralytics import YOLO model = YOLO('runs/detect/train/weights/best.pt') results = model.predict( source='test_samples/001.jpg', conf=0.45, iou=0.5, imgsz=640, save=True, project='runs/detect', name='predict_test' ) for r in results: for box in r.boxes: cls_id = int(box.cls) conf = float(box.conf) x1, y1, x2, y2 = [round(v) for v in box.xyxy[0].tolist()] print(cls_id, conf, (x1, y1, x2, y2))model.predict接受图片路径、摄像头索引或视频流地址。conf是置信度门限,低于这个值的框直接丢弃;iou是NMS的IoU阈值,两个框重叠超过这个比例就只保留得分高的那只。save=True会把带框的图存下来,我平时做快速验证,拿一张yolo测试图片跑这个命令,换模型权重时对比输出框的差别,比看指标更直观。
4.2 置信度门限和NMS:误检率与召回率怎么权衡
置信度门限是最常被问的参数。YOLOv8默认值是0.25,但工业场景我一般从0.4起步。产线上漏检一个裂纹的代价远大于误报一次,那就把门限调低换取召回;如果误检率高、下游剔除机构频繁动作,往上调门限压误检。这个参数只影响阈值以下的框是否保留,不会重新计算得分,所以不需要重新训练。
门限定0.4还是0.6,取决于验证集上的置信度分布。我在部署前会跑一次全量验证集,把每个类别的预测框按置信度从高到低排序,观察误检框集中在哪个区间。如果0.5以上全是正确框、0.4以下混入大量背景框,门限就定在0.5。iou保持0.5,螺栓螺母挨得近,iou太高会把相邻两个目标合并成一个框;太低又会输出大量重复框。0.5是实测折中,一般不用动。
4.3 边缘设备选型:Jetson、RK3588还是树莓派
实时推理的设备选择要看帧率和功耗。我按两个维度切:产线固定机位用NVIDIA Jetson Orin Nano,8G显存跑YOLOv8s能到40FPS左右;移动巡检设备用RK3588,配合RKNN推理约20FPS;树莓派4B只能跑YOLOv8n,适合概念验证,不适合量产交付。关键在部署格式:PyTorch模型不能直接跑在边缘设备上,Jetson要导出TensorRT引擎,RK3588要导出RKNN格式。
导出ONNX是中间步骤,一般用ultralytics自带命令:
yolo export model=runs/detect/train/weights/best.pt format=onnx imgsz=640再在目标设备上把ONNX转成TensorRT或RKNN。转换时imgsz要和训练一致,动态尺寸在某些硬件上会退化成低精度路径,推理结果出现偏移很难排查。
注意:导出的ONNX本身不针对具体硬件优化。Jetson上一定要用TensorRT重新编译引擎,直接跑ONNX相当于让GPU干CPU的活,FPS差两倍以上。
4.4 性能验证:不要只看mAP
训练完我最先看的不是mAP,而是验证集上每个类别的AP。齿条、螺栓、螺母、裂纹的AP要分开记录。mAP50达到0.95不代表裂纹类表现好,因为裂纹样本少,被齿条的大样本稀释了。我在项目里要求裂纹类AP50不低于0.85,否则不上线。FPS用真实摄像头实测,model.predict(source=0, stream=True)跑一分钟数帧数,不要信软件自带的benchmark,那跑的是虚拟设备。
5. 实战避坑:边缘部署误检率高、小目标漏检等五个高频问题
5.1 边缘部署误检率居高不下
现象:模型在自己电脑上测得好好的,换到产线工控机上跑,画面里全是误检,尤其在强光下把背景反光识别成螺栓。
原因:训练集和部署环境的光照分布不一致,模型学到了"高光区域等于金属表面"的伪特征。工控机摄像头默认的自动白平衡和增益也和采集时不同,画面色温一变,模型就发慌。
解决:把工控机摄像头固定曝光和手动白平衡,采集一批现场光线下的图片加入训练集,模型重训两轮。这个坑我栽过,后来总结成一句话:训练集必须来自部署的同一台相机,至少要加一批现场光线样本。
5.2 齿条小目标漏检
现象:齿条的齿部小目标、螺栓在远距离画面中只有20像素宽,训练时val的AP不错,实时画面上连续漏检。
原因:小目标在640分辨率下信息太少,模型难以提取特征。YOLOv8在高层特征图上检测小目标,但输入分辨率不够,小目标的框内像素只有个位数,特征根本立不住。
解决:把imgsz从640提到960或1280,小目标AP会有明显提升,代价是推理速度下降,用TensorRT加速补回来。还有一个技巧:对螺栓螺母做裁剪放大,在大图上检测到疑似区域后,把该区域裁出来缩放,用同一个模型再检一次,相当于二次确认。
5.3 loss一直降但val mAP不动
现象:训练到第80个epoch,box_loss持续下降,val的mAP50卡在0.85不动。
原因:过拟合开始发生,模型记住了训练集的噪声标注。通常是某一批图片标注框偏移了2到3个像素,损失还能降,但验证集上的泛化不再提升。我把这类情况叫作模型把错题本背会了。
解决:先降低学习率并开启余弦退火,看mAP有没有松动;然后排查标注质量,把验证集里预测置信度低但GT存在的图抽出来,逐张看标注框是否贴合目标。十次有八次是标注问题,不是模型问题。
5.4 部署后FPS不达标
现象:Jetson上跑ONNX的YOLOv8s只有10FPS,离产线要求的15FPS差一截。
原因:ONNX在Jetson上没有利用TensorRT的硬件加速,FP16也没有开启,GPU都在用FP32跑卷积,浪费了大半算力。
解决:用TensorRT转FP16引擎,FPS能翻倍以上。如果还不够,把batch固定为1,关闭动态形状,或者把imgsz降到480,前提是重新验证小目标AP。我之前有个项目为了省显存把动态形状关了,后来换产线型号必须重新导出,所以多花十分钟测试是值得的。
5.5 裂纹样本太少导致训练崩盘
现象:crack类别只有80张图,训练到一半cls_loss突然飙升,推理时把齿条上的阴影全当裂纹,误检率惨不忍睹。
原因:类别极度不平衡,模型在梯度更新时被大样本类别主导,裂纹类几乎学不到特征。80张的类别等于在黑匣子里碰运气。
解决:先推理,对齿条类别的预测框做裁剪,人工筛选出其中真正的裂纹图补充标注;同时对已有裂纹样本做离线增强,每张图生成10个变体。我当时的经验是,裂纹样本补到300张之后,AP50才真正稳定下来,少于200张都是玄学,调参也救不回来。
6. 进阶:把检测结果接进产线逻辑的验证方法
6.1 用框的类别和坐标做分级
模型输出已经包含类别和坐标。对齿条,我可以根据裂纹框是否与齿条框重叠,输出"齿条合格"或"齿条裂纹";对螺栓,统计画面中螺栓框的数量和位置,判断是否漏装。这一步不需要改模型,只需在部署逻辑里写一个规则层。裂纹的置信度还可以换算成缺陷严重等级:0.8以上直接剔除,0.5到0.8进人工复检。
6.2 灰度验证与抽帧回放
上线前我会做一场灰度:产线停机时录制一段真实运行视频,用部署模型逐帧推理,把每一帧的框、置信度、时间戳存成JSON日志,再和人工标注的抽样帧对比。重点不是准确率数字,而是看误检是否集中在某类光照条件下。如果发现下午三点阳光侧照时误检率升高,就把该时段的图像补进训练集,这种定位方法比盲目调门限有效得多。
最后说一个我自己的教训:所有调参都要留档。conf、iou、imgsz、增强开关在哪个数据集上取得什么指标,写成文本文件丢进项目仓库。三个月后模型因为新增产品种类要重训时,这份记录比记忆可靠得多。这也是我能一次次快速复现YOLO物体检测项目的原因,希望帮到你。
本文还有配套的精品资源,点击获取