工业零件裂纹检测实战:YOLO实时检测与部署全流程

📅 发布时间:2026/10/10 16:59:23
工业零件裂纹检测实战:YOLO实时检测与部署全流程
简介面向计算机视觉与工业质检场景的YOLO实时物体检测资源包涵盖齿条、螺栓、螺母及裂缝检测等典型工业任务。包内共2000个文件其中1903个txt标注或配置文件46个c源文件、49个h头文件另含1个cpp与1个md说明文档压缩包整体约199.17MB。txt数量庞大多用于存放类别标签、边界框坐标或训练超参数配合完整C源码可复现检测流程。源码涵盖数据读取、网络构建、层实现与推理逻辑等核心模块有助于深入理解YOLOv1至YOLOv4的演进细节尤其适合对小尺寸工业零件与缺陷进行检测训练和验证。资源结构规整既适合计算机视觉初学者对照源码理解算法原理也便于工程师在此基础上进行模型选型与二次开发。目前已有67人学习是一份从理论到实践都颇具参考价值的实时检测资料。1. 齿条、螺栓、螺母加裂纹检测YOLO实时物体检测为什么是正确起点一条产线上同时要盯齿条齿面、螺栓杆部、螺母端面还得在几十毫秒内把裂纹这种缺陷框出来——这就是标题里那串英文的实际场景yolo real-time object detection 负责零件定位crack detection 负责表面缺陷。齿条、螺栓、螺母跟常见的行人、车辆、猫狗不一样它们是小目标、金属反光背景、缺陷样本稀缺直接把 COCO 80 类的权重拿过来跑肯定是废的得从数据到训练到部署重走一遍。这篇笔记就是按这个路径来拆怎么标裂纹、怎么调训练参数、怎么导出 ONNX 上边缘设备以及我踩过的几个真坑。2. 工业零件数据集齿条、螺栓、螺母与裂纹样本的采集和标注2.1 光照和相机选型决定数据集质量不要先急着标很多人拿到齿条和螺栓的第一反应是打开标注工具开干我建议先花两天把采集环境定下来。YOLO 对光照的敏感度比你想象的高金属零件的反光会让同一批零件在不同角度下看起来像两个类别。常见的做法是环形光源加同轴光齿面用低角度环形光把齿形轮廓打出来螺栓螺母用漫射光避免镜面反射。相机方面不要一上来就上彩色工业相机黑白相机配高分辨率更合适——裂纹在灰度图上对比度往往比彩色图更清晰而且图像体积小一半推理也快。采集时还有一个容易被忽略的点分辨率要留足余量。齿条单齿在画面里可能只有 20×30 像素螺栓头部直径 15 像素这个尺寸在 640×640 的输入下几乎看不见。我一般会把相机架到让最小的目标至少占 32×32 像素实在达不到就靠后面的切片推理补救。另外裂纹样本别只拍水平方向的垂直裂纹、斜向裂纹、贯穿齿根的裂纹都要有不然模型只认一个角度。2.2 用 YOLO 标注格式存裂纹细长目标的三条标注规则YOLO 的标注是 txt 文件每行格式是class_id cx cy w h坐标全部归一化到 01。普通零件按外接矩形框标没问题但裂纹是细长线状目标一条裂纹长 200 像素、宽只有 3 像素直接框的话矩形面积 99% 都是背景训练时损失会被背景主导。我总结的三条规则第一条裂纹必须分段标。把一条长裂纹切成 35 段每段单独一个框段与段之间留 23 像素重叠防止训练时框的抖动把裂纹边缘甩掉。第二条框的方向跟裂纹主轴对齐不要为省事标成水平框长宽比超过 1:10 的框会让模型学到“框里面大部分是背景”推理时置信度普遍偏低。第三条类别命名别用纯数字用rack_good、bolt_good、nut_good、crack这种可读名称后续出报表和排查误检时能少花一半时间。2.3 让几十张裂纹样本撑起一个类过采样和图像增强代码工业场景的常态是有 5000 张合格零件裂纹样本只有 60 张。用原始比例训练模型会直接忽略裂纹类因为负样本占比太高。我的做法是先过采样再增强把裂纹样本复制到与合格品接近的比例比如 1:3然后做数据增强。下面这段脚本是我处理金属零件增强的常用模板import cv2 import numpy as np from pathlib import Path src_dir Path(raw_crack) # 原始裂纹样本目录 out_dir Path(aug_crack) # 增强输出目录 out_dir.mkdir(exist_okTrue) TARGET_COUNT 400 # 增强后裂纹样本目标数量 CLAHE_CLIP 2.0 # CLAHE 对比度限制金属件建议 1.5~3.0 CLAHE_TILE (8, 8) # 分块大小块越小局部对比度越强 def augment_crack(image, bboxes): 对裂纹图像做增强同时按相同变换更新标注框。 h, w image.shape[:2] augs [] # 1. CLAHE 局部对比度增强把裂纹从金属纹理里拉出来 lab cv2.cvtColor(image, cv2.COLOR_BGR2LAB) l_chan lab[:, :, 0] clahe cv2.createCLAHE(CLAHE_CLIP, CLAHE_TILE) l_enh clahe.apply(l_chan) lab[:, :, 0] l_enh image_clahe cv2.cvtColor(lab, cv2.COLOR_LAB2BGR) augs.append((image_clahe, bboxes)) # 2. 高斯模糊模拟低景深迫使模型学形状而不是纹理 image_blur cv2.GaussianBlur(image, (3, 3), 0) augs.append((image_blur, bboxes)) # 3. 旋转 ±15°注意旋转后框的角点要重新算 for angle in (-15, 15): M cv2.getRotationMatrix2D((w / 2, h / 2), angle, 1.0) img_rot cv2.warpAffine(image, M, (w, h), borderModecv2.BORDER_REFLECT) bboxes_rot [] for x, y, bw, bh in bboxes: # 把中心点坐标旋转后还原为 xywh cx, cy x bw / 2, y bh / 2 cx2 m00 * cx m01 * cy M[0, 2] cy2 m10 * cx m11 * cy M[1, 2] bboxes_rot.append((cx2 - bw / 2, cy2 - bh / 2, bw, bh)) augs.append((img_rot, bboxes_rot)) # 4. 加椒盐噪声模拟灰尘颗粒 noise np.random.randint(0, 255, image.shape[:2]) salt noise 20 pepper noise 235 image_noise image.copy() image_noise[salt] (255, 255, 255) image_noise[pepper] (0, 0, 0) augs.append((image_noise, bboxes)) return augs # 循环处理所有原始裂纹图输出增强后的图和 YOLO 标注 crack_id 3 # class_id假设 0rack 1bolt 2nut 3crack for img_path in src_dir.glob(*.jpg): image cv2.imread(str(img_path)) label_path src_dir / (img_path.stem .txt) bboxes [] for line in label_path.read_text().strip().splitlines(): parts line.split() cls_id int(parts[0]) if cls_id crack_id: # 归一化坐标转像素坐标 _, cx, cy, bw, bh map(float, parts) w, h image.shape[1], image.shape[0] bboxes.append((int((cx - bw/2) * w), int((cy - bh/2) * h), int(bw * w), int(bh * h))) for idx, (img_aug, bb_aug) in enumerate(augment_crack(image, bboxes)): out_name f{img_path.stem}_aug{idx}.jpg cv2.imwrite(str(out_dir / out_name), img_aug) with open(out_dir / (out_name.replace(.jpg, .txt)), w) as f: for x, y, bw, bh in bb_aug: cx (x bw / 2) / img_aug.shape[1] cy (y bh / 2) / img_aug.shape[0] f.write(f3 {cx:.6f} {cy:.6f} {bw / img_aug.shape[1]:.6f} {bh / img_aug.shape[0]:.6f}\n)这段脚本的关键在于增强和标注框同步变换。我见过不少翻车案例是图像转了 15°标注框还停留在原位置模型训练时损失直接爆炸。旋转部分的坐标变换最容易写错建议导出一张图人工检查框的位置再批量跑。CLAHE 的参数对金属件影响很大clip 值超过 3.0 会把划痕也增强成裂纹不可逆。3. 训练参数与损失函数YOLOv8 在零件检测上的调参路径3.1 按推理设备选模型参数量不是越大越好很多新人一上来就选 YOLOv8x认为精度最高。但你要先想清楚推理设备在哪产线工控机上跑还是 AGX Orin 这种边缘盒子YOLOv8n 参数量 3.2Myolov8s 是 11.2Mv8m 是 25.9M参数量的差异直接反映到延迟上。我对齿条检测的建议是如果目标在图中超过 32 像素用 v8s 起步如果裂纹和小螺栓占比高用 v8m 配 1024 输入精度收益远大于换成 v8x。选型时还要看输出通道数。YOLOv8 的检测头输出格式是[batch, 4 num_classes, grid_h, grid_w]4 对应 box 的 xywh类数越多通道越宽。你的场景只有 4 个类rack、bolt、nut、crack通道数不算压力但如果你后续加划痕、凹坑、锈蚀等缺陷类别通道会线性增长推理延迟也会跟着涨。另外一个容易忽略的点YOLO 的 Anchor-Free 设计意味着不需要手动设置锚框尺寸。老版本的 YOLOv5 要跑 k-means 聚类出 anchorsv8 改成解耦头后这个步骤省了训练配置里不用再折腾 anchor 相关参数。3.2 训练配方的四个必调参数imgsz、batch、epochs 和增强训练命令看似简单真正影响结果的是四个参数的组合# crack_train.yaml path: ./industrial_parts train: images/train val: images/val nc: 4 names: [rack, bolt, nut, crack]yolo train \ modelyolov8s.pt \ datacrack_train.yaml \ imgsz1024 \ batch16 \ epochs300 \ lr00.01 \ mosaic1.0 \ mixup0.2 \ close_mosaic10imgsz是最关键的一个。齿条齿面这种小目标640 和 1024 的 mAP 差距能有 8 个点以上但显存占用涨了一倍。我一般先看显卡单张 24G 显存用 batch 16 imgsz 1024显存不够就 640 加切片推理兜底。batch别太大也别太小16 到 32 之间太小的话 BN 层统计不稳定裂纹类本来就样本少batch 再小就完全失去了统计意义。epochs至少 300工业数据集中后几百轮才开始稳定不要看 train loss 降了就提前停。增强参数里mosaic1.0和mixup0.2是我调过相对稳的组合。纯金属零件表面纹理相似mosaic 拼图能让模型减少对背景的过拟合。但最后 10 轮一定要close_mosaic否则验证时因为训练和验证分布不一致mAP 会虚高部署到产线就露馅。3.3 从损失曲线判断“裂纹类”是否在收敛YOLOv8 的损失由三部分组成分类损失 BCE、回归损失 DFL 和 CIoU。训练日志里的cls_loss和dfl_loss要分开看不要只盯着box_loss。裂纹这类样本少的类别最典型的信号是cls_loss下降缓慢但dfl_loss在正常下降——这说明模型已经学会框住裂纹的大概位置但分类置信度上不去。遇到这种情况先检查是不是类别权重失衡裂纹样本在 batch 里平均占比不到 5%BCE 损失被其他类主导。解决办法是在数据加载器里对裂纹样本做加权采样或者在损失函数里给裂纹类加权重不用改源码的话最简单的是把裂纹样本在数据目录里多复制几份让采样概率自然提升。# 用这段代码统计训练集各类别占比低于 5% 就要干预 from collections import Counter label_dir Path(industrial_parts/labels/train) counts Counter() for txt in label_dir.glob(*.txt): for line in txt.read_text().strip().splitlines(): counts[int(line.split()[0])] 1 total sum(counts.values()) print({k: v / total for k, v in counts.items()})如果裂纹类占比确实在 5% 以下建议回到第 2 章做过采样而不是在训练代码里塞复杂的损失加权。损失权重调起来很玄学一组参数在一个数据集上有效换到另一种光照下可能完全失效。4. 实时推理与边缘部署ONNX 导出、TensorRT 加速和产线接入4.1 导出 ONNX 并确认输入输出尺寸训练完成后第一步是导出 ONNX目的是脱离 PyTorch 环境、接入生产推理框架。yolo export modelbest.pt formatonnx imgsz1024 opset17导出参数opset17是为了兼容比较新的 TensorRT 版本imgsz必须和训练时一致不要导出时图省事用 640部署时推理 1024 会收到尺寸不匹配的报错。导出后最好用onnxruntime快速验证一下输出。YOLOv8 的 ONNX 输出形状是[1, 84, 1024, 1024]84 4 4 类也就是每个格子输出 4 个框坐标和 4 个类别置信度。很多人栽在预处理上模型训练时用的归一化是[0, 1]还是[0, 255]取决于 ultralytics 版本导出后用一张裂纹图对比 PyTorch 和 ONNX 的输出偏差超过 0.5% 就优先检查输入归一化和通道顺序。4.2 TensorRT 在 AGX Orin 上的精度和延迟平衡ONNX 在 CPU 上跑 1024×1024 大概要 80 到 120 毫秒产线节拍要求通常是 30 到 50 毫秒所以必须上 TensorRT。AGX Orin 的典型转换命令trtexec \ --onnxbest.onnx \ --saveEnginebest_fp16.engine \ --fp16 \ --minShapesinput:1x3x1024x1024 \ --optShapesinput:1x3x1024x1024 \ --maxShapesinput:1x3x1024x1024 \ --workspace4096--fp16这块要注意裂纹是低对比度细线目标FP16 的精度损失有时候会让小裂纹直接消失。我遇到过的案例是 FP16 引擎的 mAP 比 FP32 掉了 3 个点肉眼看不出来但产线漏检率翻倍。做法是先跑 FP16 测召回不合格就退回 FP32或者做 INT8 量化加校准集——INT8 在工业检测上收益大但坑也深建议第二版再碰。TensorRT 推理的 Python 接口不复杂直接读 engine 文件执行import tensorrt as trt import pycuda.driver as cuda import pycuda.autoinit import numpy as np logger trt.Logger(trt.Logger.ERROR) with open(best_fp16.engine, rb) as f: runtime trt.Runtime(logger) engine runtime.deserialize_cuda_engine(f.read()) ctx engine.create_execution_context() # 分配输入输出显存 h_input np.zeros((1, 3, 1024, 1024), dtypenp.float32) h_output np.zeros((1, 84, 4096, 1024), dtypenp.float32) # 1024/32 的网格是 32x32 d_input cuda.mem_alloc(h_input.nbytes) d_output cuda.mem_alloc(h_output.nbytes) cuda.memcpy_htod(d_input, h_input) ctx.execute_v2(bindings[int(d_input), int(d_output)]) cuda.memcpy_dtoh(h_output, d_output)TensorRT 的 binding 顺序和 ONNX 输入输出顺序一致不确定时可打印engine.binding_names确认。另外ctx.execute_v2在同一 context 内重复调用是线程不安全的产线如果有多个相机并行推理每个摄像头单独一个 context 实例。4.3 与 D435i 深度相机配合2D 检测框对齐深度齿条、螺栓检测之外的常见需求是测距定位——机械臂抓取前想知道零件在三维空间的位置。D435i 输出 RGB 流和深度流两路画面的像素不是天然对齐的要用相机内参做对齐。import pyrealsense2 as rs import numpy as np pipeline rs.pipeline() config rs.config() config.enable_stream(rs.stream.depth, 1280, 720, rs.format.z16, 30) config.enable_stream(rs.stream.color, 1280, 720, rs.format.bgr8, 30) profile pipeline.start(config) depth_sensor profile.get_device().first_depth_sensor() depth_scale depth_sensor.get_depth_scale() # 默认 0.001 米 # 关键把深度图像对齐到彩色图的坐标空间 align rs.align(rs.stream.color) frames pipeline.wait_for_frames() aligned_frames align.process(frames) depth_frame aligned_frames.get_depth_frame() color_frame aligned_frames.get_color_frame() depth_image np.asanyarray(depth_frame.get_data()) color_image np.asanyarray(color_frame.get_data())对齐后把 YOLO 检测框的中心点坐标映射到深度图上取该点邻域 5×5 像素的中值作为距离。不要只取单点金属表面在深度图上有噪点单点距离经常跳变。距离值的单位记得乘depth_scaleD435i 默认是毫米。5. 避坑排查裂纹漏检、螺栓误检和实时性不达标的五个真实记录5.1 裂纹漏检率居高不下问题出在标注框现象训练损失正常下降验证 mAP 也有 0.85但产线上 30% 的裂纹完全没有框出来。 原因标注裂纹时框太随意一条裂纹一个水平框框内 95% 是背景模型学到的是“背景模式”而不是裂纹纹理。置信度普遍在 0.2 以下测试阈值一调高就全漏了。 解决回到第 2.2 节的分段标注规则把长裂纹切成至少 3 段每段框长宽比控制在 1:5 以内重新训练后召回率直接拉回 90% 以上。这个教训我吃了两次第一次以为是模型问题换了 v8m、v8x 都没用后来才发现是标注质量拖累。5.2 螺栓螺母全丢因为 imgsz640 对小目标太苛刻现象齿条检测还行螺栓螺母几乎一个都框不出。 原因螺栓头部在 640×640 输入下只有 10×10 像素YOLO 的下采样比是 32这个尺寸落在特征图上还不到一个格子检测头根本看不到。 解决把imgsz提高到 1024同时开启多尺度训练。如果显存紧另一个路子是切分推理——把原图按 512 重叠切片分别推理再合并结果效果等同放大输入但推理时间翻倍。我建议先上 1024 输入还不行再用切片。5.3 金属高光被识别成裂纹误检频发现象无缺陷的螺栓杆部被框成裂纹误检率 15%。 原因高光区域的灰度值梯度大形态上跟裂纹相似尤其是在同轴光照射下抛光面会产生镜面反射形成亮白色条带。 解决物理手段优先换偏振光源或调整光源角度消除反射数据层面给训练集增加高光样本把无缺陷但有反光的零件也拍进来标为背景。误检没法完全清零但把阈值从 0.25 调到 0.4 能显著拉低前提是裂纹的真阳性分数在 0.6 以上。5.4 缺陷类样本太少训练曲线震荡不收敛现象loss 在训练中期反复横跳验证集 mAP 上下波动超过 3 个点。 原因裂纹样本只有几十张一个 epoch 里模型见到的裂纹次数太少梯度不稳定。 解决过采样加增强把裂纹样本提到 400 张以上并降低mixup强度。特别提醒mixup1.0时裂纹样本会被大量混合进背景模型更学不到缺陷特征缺陷类场景控制在 0.10.2 之间。5.5 导出 TensorRT 后掉点FP16 和校准要分开看现象FP16 引擎在验证集上 mAP 下降 5 个点齿条边缘的细小裂纹全部丢失。 原因FP16 的动态范围对低对比度梯度不友好细线目标的前景像素在 FP16 下容易被舍入成背景。 解决对裂纹类单独评估召回率而不是只看整体 mAP。如果在 AGX Orin 上必须用 FP16 才够快就退一步用 FP32 引擎跑 1024 输入延迟可能从 25ms 涨到 60ms但漏检比延迟更要命。产线节拍允许的情况下我宁愿保精度。6. 进阶从检测框到裂纹度量用分割和骨架化算出缺陷长度检测框能告诉你裂纹在哪但产线质检往往需要的是“这条裂纹多长、面积多大、是否超标”。YOLOv8 / YOLOv11 自带实例分割版模型名类似yolov8n-seg.pt输出除了检测框还有每个目标的掩膜。分割版对细长裂纹的效果比检测框好很多因为掩膜可以呈现曲线形态。我现在的做法是两阶段先用检测版快速定位再用分割版对裂纹候选区域做精细掩膜然后把掩膜骨架化计算长度。import cv2 import numpy as np from skimage.morphology import skeletonize def crack_length(mask, pixel_per_mm): 从裂纹掩膜计算真实长度毫米。 mask_bin (mask 0.5).astype(np.uint8) * 255 # 形态学闭运算把断裂的裂纹段连起来 kernel cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)) mask_closed cv2.morphologyEx(mask_bin, cv2.MORPH_CLOSE, kernel) # 骨架化提取单像素曲线 skel skeletonize(mask_closed 0) # 统计骨架像素数量乘以像素间隔即长度 pixels np.sum(skel) length_px pixels * 1.0 length_mm length_px / pixel_per_mm return length_mm骨架化前做闭运算是必要的。真实产线拍到的裂纹常被灰尘或噪点打断直接骨架化会得到十几段碎片长度严重低估。pixel_per_mm从标定板获得D435i 配合特定工作距离下可以固定为一个常数但在产线上如果用变焦镜头每次切换焦距都要重新标定。长度算出来后按标准判定缺陷等级比直接输出“有裂纹/无裂纹”更有价值。把长度和宽度联合做阈值判断长度小于 5mm 且宽度小于 0.5mm 的标记为观察超过就判不合格。最后一件事是验证闭环。不要只信训练集的 mAP要专门留出一条产线连续运行 8 小时统计漏检数和误检数。漏检率高于 0.5% 就回去看标注质量误检率高于 5% 就调阈值或加后处理。这套路子走下来YOLO 在齿条、螺栓、螺母加裂纹检测这个任务上是可以真正顶住产线压力的。我早期吃过标注随意的亏后来所有项目都强制走“标注检查 → 训练 → 产线试跑”三步闭环再没出现过批量漏检。希望帮到你。本文还有配套的精品资源点击获取