YOLOv8焊缝质量检测实战:从数据标注到模型部署
简介面向焊缝质量检测与YOLOv8目标检测学习者的完整资源包不仅给出已经训练好的焊缝好坏检测模型还附带训练与验证阶段生成的PR曲线、loss曲线等评估结果可直接用于模型推理、结果复现或在此基础上继续微调。数据集内容为jpg格式的钢材焊缝图片标注同时提供xml与txt两种格式分别对应LabelImg标注软件的原始导出与YOLO训练所需格式方便对照学习标注规范、自行扩展数据或做格式转换练习。压缩包共2000个文件以txt标注文件、md说明文档、jpg图片、py训练脚本、yaml配置文件为主并包含pt权重文件整体155.52MB目录结构与训练工程相匹配便于按需查找。配套博文包含检测效果和数据集说明已有224人浏览学习适合正在做缺陷检测项目或刚开始接触YOLOv8训练的开发者参考使用。1. 焊缝质量检测为什么选YOLOv8而不是传统视觉拿到「YOLOv8焊缝质量好坏检测」这个标题一线做视觉的人第一反应通常是又是把通用目标检测框架套到工业场景的项目。但焊缝质检恰恰是传统机器视觉最容易翻车的领域之一——焊缝区域有强烈反光、飞溅、氧化皮颜色变化加上工件表面光照不均用阈值分割加形态学做特征提取的方案换个工件、换个角度就得重新调参。而YOLOv8这类端到端检测模型只需要把“好焊”和“坏焊”用框标出来训练完直接输出类别和位置生产线上要的就是这个。标题里的ultralytics-main-yolov8-sts-hanfeng-data.zip从命名看就是一套 ultralytics 工程目录加焊缝数据集sts-hanfeng-data。常见做法是用 YOLOv8 训练自己的数据集把“气孔、夹渣、未熔合、咬边、裂纹”这些典型缺陷识别出来或者至少区分“合格/不合格”。这套方案适合谁适合刚接手产线视觉项目、手里有几百张焊缝图、想快速验证可行性的工程师也适合做毕业设计需要完整落地链路的同学。本文不假定你有那份打包好的原始工程而是按最标准的 YOLOv8 数据准备、训练、评估、部署流程把这个标题背后的活干完。2. 把焊缝数据整理成YOLOv8能吃的格式标注、目录与数据集划分2.1 先想清楚检测目标质量好坏是几分类问题做焊缝质量检测第一步不是急着标注而是跟工艺人员把检测目标敲定。这是因为“焊缝质量好坏”在视觉上不是一个单一特征常见的数据集有是两类设定二分类好焊 / 坏焊。坏焊是整体缺陷框框住缺陷区域类别只写成 one class。适合流水线快速分拣模型压力小。多分类按缺陷类型分比如porosity气孔、slag夹渣、lack_of_fusion未熔合、undercut咬边、crack裂纹。适合事后分析缺陷成因但样本如果不够很容易出现类别不平衡训练难度直线上升。我一般建议先做二分类跑通整个链路确认指标能看再决定要不要拆细类别。标题里sts-hanfeng-data如果是从实际项目导出的数据多半已经包含了缺陷框拿到手先做一件事解压后挨个打开图片确认标注框是紧贴缺陷还是整条焊缝框起来。框得太松会让模型学到的特征里混进大量背景表现为 P 值低、误检多。先明确类别定义再继续走流程。2.2 用Labelme标注焊缝缺陷并转换成YOLO txt格式如果数据是直接从相机采集的原始图片没有标注常见标注工具是 Labelme。它输出的是 JSON 文件YOLO 训练需要的是每个图片对应一个.txt每一行格式是class_id x_center y_center width height注意 YOLO 格式里的x_center y_center width height是相对图片宽高的比例值取值 0~1不是像素坐标。下面这段 Labelme JSON 转 YOLO 的脚本是我在焊缝项目里反复用过的版本直接用shapes里的多边形外包矩形import json import os from pathlib import Path def labelme_to_yolo(json_path, out_path, class_list): with open(json_path, encodingutf-8) as f: data json.load(f) w data[imageWidth] h data[imageHeight] lines [] for shape in data[shapes]: label shape[label] if label not in class_list: continue class_id class_list.index(label) points shape[points] xs [p[0] for p in points] ys [p[1] for p in points] x_min, x_max min(xs), max(xs) y_min, y_max min(ys), max(ys) box_w x_max - x_min box_h y_max - y_min # 小于 3 像素的框直接丢掉焊缝标注常见误操作是画成点 if box_w 3 or box_h 3: continue x_center (x_min x_max) / 2 / w y_center (y_min y_max) / 2 / h bw box_w / w bh box_h / h lines.append(f{class_id} {x_center:.6f} {y_center:.6f} {bw:.6f} {bh:.6f}) if lines: out_file out_path / (Path(json_path).stem .txt) with open(out_file, w) as f: f.write(\n.join(lines))脚本里的过滤条件值得注意box_w 3 or box_h 3直接跳过因为工业相机下小于 3 像素的缺陷框大概率是误标注即便真是缺陷YOLO 也很难从缩小的特征图上找回这么小的目标强行保留只会让 loss 抖动。转换完成后我会写一个反向校验脚本把 txt 转回图框画在图上随机抽查 5% 的图这一步能抓出imageWidth和实际图片尺寸不匹配导致的错位问题属于白盒验证后面避坑章会再提。2.3 目录结构与train/val划分脚本ultralytics 默认按以下目录结构读取数据dataset/ images/ train/ 001.jpg 002.jpg val/ 101.jpg labels/ train/ 001.txt 002.txt val/ 101.txt图片和 txt 必须同名.jpg和.txt一一对应训练时会根据图片路径自动去labels找同名 txt。手写一个划分脚本时最忌直接用随机打乱因为焊缝数据往往连续采集同一块板的正反两面相邻图片几乎一样随机划分会让验证集里混进训练集近亲指标虚高部署到新工件立刻现原形。按“文件名单号做分组、按组划分”更可靠比如文件名带batch001、batch002就按批次划分import random import shutil from pathlib import Path src_img Path(all_images) src_lbl Path(all_labels) dst Path(dataset) imgs sorted(src_img.glob(*.jpg)) random.Random(42).shuffle(imgs) split int(len(imgs) * 0.85) # 85% 训练 for phase in [train, val]: (dst / images / phase).mkdir(parentsTrue, exist_okTrue) (dst / labels / phase).mkdir(parentsTrue, exist_okTrue) for i, img_path in enumerate(imgs): phase train if i split else val lbl_path src_lbl / (img_path.stem .txt) if not lbl_path.exists(): print(f缺少标注: {img_path.name}) continue shutil.copy(img_path, dst / images / phase / img_path.name) shutil.copy(lbl_path, dst / labels / phase / lbl_path.name)注意脚本里的random.Random(42).shuffle固定了随机种子目的是保证每次跑划分结果一致后面调模型时不会因为数据集变了而不知道是数据问题还是模型问题。split取 85% 还是 80%要看总样本量——焊缝缺陷样本几百张时 85% 训练是合理的低于 300 张建议 80%并把更多图留给验证否则置信区间太宽很难判断一次改动是变好还是变坏。划分完打印一下每个类别的框数量和原分布对比偏差超过 10% 就要检查是不是批次分组没做好。3. 在ultralytics工程里跑通训练最少命令与关键参数3.1 环境安装与预训练权重下载CPU和GPU两种路YOLOv8 的训练入口就是ultralytics这个 Python 包。生产环境常见两种公司有 N 卡直接用pip install ultralytics如果只有 CPU 机器想先验证流程也不是不行只是慢可以装 CPU 版 PyTorch 再装 ultralytics。热词里大量出现“ubuntu20.04搭建yolov8环境cpu版本”这里给出一套能在 Ubuntu 20.04 下跑通的最小命令# 创建虚拟环境避免把系统 Python 搞乱 python3 -m venv yolov8env source yolov8env/bin/activate # CPU 版 PyTorch约 200MBGPU 机器不要用这个 pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu # 安装 ultralytics会自动带 opencv、numpy 等依赖 pip install ultralytics如果你有 N 卡直接把--index-url那行去掉就行PyTorch 默认装 CUDA 版。装完跑yolo predict modelyolov8n.pt sourcebus.jpg验证环境能输出框就算通。这里不贴外链下载地址但注意一个坑预训练权重yolov8n.pt、yolov8s.pt是训练时自动下载到用户目录下的如果公司内网不能联网第一次训练会卡在下载阶段提前在能上网的机器把pt文件拷到~/.config/Ultralytics/对应目录或者直接放到工程目录下通过modelyolov8s.pt指定路径即可。3.2 编写data.yaml与选择模型规模ultralytics 训练时需要一份data.yaml三个核心字段训练图片路径、验证图片路径、类别列表。焊缝数据集的sts-hanfeng-data解压后如果自带标注和划分这份 yaml 通常是报方已经配好的如果是自己从零组织按下面这份写path: /home/user/dataset # 数据集根目录建议用绝对路径 train: images/train # 相对 path 的图片目录 val: images/val # 类别名要和训练 txt 里的 class_id 顺序完全一致 names: 0: good_weld 1: bad_weldnames的索引是硬对应顺序换错模型训得再好也是拿类别 1 的损失去训类别 0val 曲线看着正常但实际预测全反。如果你从 zip 里拿到的类别是porosity、slag这种多分类也要在 names 里按同样顺序写好。模型规模的选择上标题里没提工业现场算力按经验焊缝缺陷普遍是小目标优先选yolov8s起步而不是yolov8nn 模型的特征图分辨率低对细裂纹不友好除非是部署到 RK3588 这类边缘盒子且帧率要求高。yolov8m是精度和速度的甜点训练时间约是 s 的两倍数据量超过 1000 张可以试。3.3 训练命令、参数含义与中断恢复跑训练建议用命令行而不是写训练脚本命令行可以每次改动只动参数不用反复改文件# 进入工程目录激活虚拟环境后执行 yolo train \ datadata.yaml \ modelyolov8s.pt \ epochs100 \ imgsz640 \ batch16 \ device0 \ workers4 \ projectruns/weld \ nameexp_bad_weld \ patience20参数说明imgsz是输入分辨率焊缝检测我建议不要低于 640如果缺陷是毫米级细长条直接上 960 或 1280代价是显存翻倍batch16看显存调12GB 显卡跑 s 模型 640 分辨率batch 16 快要占满显存不够报 OOM 就减半device0表示用第一张卡CPU 机器改成devicecpupatience20是早停轮数验证集指标连续 20 轮不涨就停省时间。训练中断是个几乎必然遇到的事不用慌。ultralytics 会自动保存last.pt和best.pt中断后继续训练用resumeTrueyolo train resume modelruns/weld/exp_bad_weld/weights/last.pt这里有个老手常用的习惯第一轮训练参数不要拉满先用 20 个 epoch 和数据子集确认 loss 在正常下降再启动正式训练。否则等你睡一觉醒来发现 loss 是 nan或者 images/labels 路径写错跑了一夜空训练这锅只能自己背。训练过程中把runs/weld/exp_bad_weld/下的results.csv实时打开看里面有每个 epoch 的 box_loss、cls_loss、mAP50、mAP50-95 等字段Excel 打开按 F5 刷新就能滚动看这比看控制台日志直观。4. 焊缝缺陷检测的避坑与排查数据、过拟合和漏检4.1 标签错位导致loss不降或val曲线不动现象训练 loss 从 2.3 降到 1.5 后就卡住val 的 mAP50 一直在 0.1 上下抖动怎么调学习率都没用。排查时打开一张 val 图用yolo predict modelbest.pt source某张val图画框发现预测框和真实缺陷位置完全不在一个地方甚至类别张冠李戴。原因最常见是数据集划分时 images 和 labels 的对应关系错位。比如按文件名 glob 排序后发现1.jpg配的是10.txt等ultralytics 不检查同名内容的语义是否匹配它只认路径同名。还有一种隐藏情况——标注时从 Labelme 导出的点数坐标是对的但转换脚本里imageWidth读的是 JSON 头里的数值和实际解码后的图片宽高不一致比如旋转过的图导致框右下角偏移。解决强制做一次“标注回读”检查。写一个临时脚本把训练集随机挑 50 张读 txt 坐标画框保存到check/目录人眼过一遍。不要凭感觉“应该没问题”这一步是血泪经验换来的我踩过一次整个数据集因 Exif 旋转信息导致框偏移白训三天。回读脚本很短import cv2 from pathlib import Path img_dir Path(dataset/images/val) lbl_dir Path(dataset/labels/val) for img_path in img_dir.glob(*.jpg): lbl_path lbl_dir / (img_path.stem .txt) if not lbl_path.exists(): continue img cv2.imread(str(img_path)) h, w img.shape[:2] for line in lbl_path.read_text().splitlines(): parts line.split() if len(parts) ! 5: continue cx, cy, bw, bh map(float, parts[1:]) x1 int((cx - bw / 2) * w) y1 int((cy - bh / 2) * h) x2 int((cx bw / 2) * w) y2 int((cy bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 0, 255), 2) out Path(check) / (img_path.stem _check.jpg) out.parent.mkdir(exist_okTrue) cv2.imwrite(str(out), img)4.2 小缺陷漏检输入分辨率与切图推理现象模型 mAP50 练到了 0.85看着不错但实际跑产线视频时长度只有 20 像素的小裂纹经常漏掉大块气孔都能框出来细长未熔合一次都抓不到。原因焊缝缺陷在很多数据集中最典型的形态是细长条占整张图不到 2%。YOLOv8 是 anchor-free 检测下采样倍数决定了最小可检测尺寸输入 640 时特征图最小一层是 20x20对应每格代表 32 像素太细的缺陷在特征图上只剩半格响应被激活阈值压掉了。解决优先把imgsz从 640 提到 960 或 1280缺陷像素尺寸不变相对特征图占比变大这是最简单有效的改动。如果显存不够就改成“切图检测”——把原图按 50% 重叠切块分别送入模型再合并结果前提是推理代码里要做坐标映射回原图。另一个常见做法是换更大模型yolov8m或yolov8l它们特征图通道更多对小目标更宽容但部署成本上升。还有一招是把训练数据里的缺陷图做随机裁剪增强让模型多见识局部放大后的形态不过这部分属于改数据流新手先试前两种。4.3 焊缝反光和飞溅造成大量误检现象badcase 里一堆框打在焊缝边缘的飞溅颗粒或镜面反光处模型把高亮区域当成了未熔合。原因真实焊缝图的光照条件和你训练集里的光照不一致或者训练集中反光样本太少。视觉检测模型本质是学纹理和对比度反光区的灰度变化与某些缺陷确实高度相似模型分不清物理成因只知道像素模式像。解决从源头收集坏样本把产线打光角度下出现误检的图补进训练集。但这要等部署后才暴露属于后验问题。预防做法是训练时对亮度做强数据增强hsv_h0.02、hsv_s0.5、hsv_v0.4。ultralytics 训练命令里这几个参数默认是hsv_h0.015, hsv_s0.7, hsv_v0.4可以在你的场景里加大亮度扰动。另外数据采集时尽量与最终部署用同一套光源别再搞“实验室一盏灯、产线一盏灯”的国产化套话调光在工业视觉里永远比调参优先。4.4 类别不平衡好焊太多坏焊太少现象训练集中 good_weld 框 5000 个bad_weld 框 200 个。模型把所有焊点都判为好焊mAP50 也能到 0.9因为准确率被多数类撑起来了但产线上坏焊漏检率是 100%这模型等于废了。原因YOLO 的损失函数里类别权重是平均的少数类贡献的梯度被多数类淹没。常见错误是只看总 mAP50不看每个类别的 PR 曲线。解决三类办法可以组合。第一过采样少数类每个 epoch 让程序随机多读几遍坏焊样本在data.yaml的mosaic增强打开时YOLO 训练会基于 mosaic 自动组合图少数类出现概率会提升第二给少数类更大的 loss 权重ultralytics 在data.yaml里可以配Loss_weights自定义类别损失系数但改起来不如过采样直接第三硬核办法是数据扩编把有缺陷的图做旋转、左右翻转、随机裁剪扩到 1000这是工业项目最笨但最稳定的路。4.5 训练中loss变成nan的常见原因现象训练跑了十几轮控制台突然报loss: nan之后每个 epoch loss 都是 nan模型输出全部为空。原因出现 nan 在 YOLOv8 里最常见的原因不是学习率过大而是数据里有异常标注。比如某个 txt 里坐标出现-0.5或者宽度是 0或者在类别数与 yaml 不对应时cls_loss 会算出 inf。另外 batch 里包含一张损坏的 JPEG解码出来是全黑图但标注框巨大也会让 loss 炸掉。解决首先停训练写一个遍历所有 txt 的脚本检查每行坐标是否在0~1范围内宽度高度是否大于 0再检查是否有图片打不开python -c from PIL import Image from pathlib import Path for p in Path(dataset/images/train).glob(*.jpg): try: Image.open(p).load() except Exception as e: print(p, e) 这两项检查通常能找到元凶。如果全通过再把学习率从默认的lr00.01降到0.001重跑排除优化器问题。这一套下来 99% 的项目能复活。5. 不只盯着mAP用混淆矩阵、PR曲线和badcase验证焊缝模型5.1 从results.csv画损失函数曲线训练完成后runs/weld/exp_bad_weld/results.csv是评估的第一份证据。我一般不会只截图发给工艺同事而是用 matplotlib 画成曲线图确认三件事训练 loss 最终是否收敛val 的 box_loss 和 cls_loss 是否同步下降mAP50 有没有在后期突然下降的过拟合迹象。import pandas as pd import matplotlib.pyplot as plt df pd.read_csv(runs/weld/exp_bad_weld/results.csv) # 注意 ultralytics 的列名带空格取列前先 strip df.columns df.columns.str.strip() fig, ax plt.subplots(2, 2, figsize(12, 8)) ax[0, 0].plot(df[train/box_loss], labeltrain box_loss) ax[0, 0].plot(df[val/box_loss], labelval box_loss) ax[0, 1].plot(df[metrics/mAP50(B)], labelmAP50) ax[0, 1].plot(df[metrics/mAP50-95(B)], labelmAP50-95) ax[1, 0].plot(df[train/cls_loss], labeltrain cls_loss) ax[1, 0].plot(df[val/cls_loss], labelval cls_loss) ax[1, 1].plot(df[val/box_loss], labelval box_loss) for a in ax.flat: a.legend() a.set_xlabel(epoch) plt.tight_layout() plt.savefig(training_curves.png, dpi150)如果看到验证 loss 在下降但 mAP50 停滞问题多半在“定位还可以但分类置信度不够”如果训练 loss 很低但验证 loss 高就是过拟合回数据量和增强。画完后别急着调参把这版模型用过一遍再改。5.2 生成混淆矩阵与PR曲线看缺陷识别能力ultralytics 在训练结束时会自动在runs/weld/exp_bad_weld/下生成confusion_matrix.png和PR_curve.png这两个图比 mAP 数字诚实得多。混淆矩阵里最需要看的是“坏焊被判成好焊”的格子那是漏检还有背景被误判成坏焊的格子那是过杀。焊缝场景漏检比过杀严重因为漏检的焊点进下道工序过杀只是返工。PR 曲线看每个类别的召回率拐点。曲线靠近右上角说明类别可分性好如果曲线掉得陡说明模型只能识别部分形态的缺陷。此时我的做法是回到 badcase 图里把召回率低的那一类图挑出来看是不是都缺了同一种形态比如全是长条裂纹然后定向补数据而不是盲目加 epoch。5.3 用val和predict跑badcase按置信度筛选训练完不要光看曲线用验证集图跑一次推理把失败案例挑出来。ultralytics 的 val 模式会输出每张图的预测结果但不会自动告诉你哪张预测错了。写一个快速脚本按置信度排序把分数低于阈值的预测框和所有漏检的原图挑出来from ultralytics import YOLO model YOLO(runs/weld/exp_bad_weld/weights/best.pt) results model.val(datadata.yaml, conf0.25, iou0.6, save_jsonFalse) for r in results: boxes r.boxes if boxes is None: continue for box in boxes: conf box.conf.item() cls int(box.cls.item()) if conf 0.6: # 低分预测框通常是错误候选 print(f{r.path}: class {cls} conf {conf:.2f})注意model.val返回的results是验证集整体结果逐图遍历时r.path给出原图路径。低分框集中出现在反光区或焊缝边缘说明模型定位不稳定下一步不是调阈值而是去看看是不是验证集里没有这类难例、训练时没见够。这个过程是黑匣子变白的过程别嫌麻烦模型部署后产线给到的坏样本永远比你训练集丰富提前学会看失败后面兜得住。6. 部署前的一个实用技巧导出ONNX并找对置信度阈值训练完best.pt只是 PyTorch 权重产线用 C 或 RK3588 部署时一般先导出 ONNX。ultralytics 直接支持yolo export modelruns/weld/exp_bad_weld/weights/best.pt formatonnx imgsz640 opset12导出后拿到best.onnx用onnxruntime验证输出尺寸YOLOv8 的输出是一个 1xNx(5class_num) 的张量N 是预测框候选数。这里我吃过亏在 OpenCV 的 DNN 模块里加载 ONNX 后输出的是 4 维不处理就直接遍历会越界。更稳的做法是直接用onnxruntime跑一遍确认输出和 PyTorch 的 predict 结果一致再写 C 后处理。阈值设置别拍脑袋。先用验证集跑一个不同conf下的漏检率和误检率表yolo val modelbest.pt datadata.yaml conf0.3 iou0.5 yolo val modelbest.pt datadata.yaml conf0.5 iou0.5 yolo val modelbest.pt datadata.yaml conf0.7 iou0.5比较三次输出里的F1和每个类别的recall。焊缝场景我通常挑召回率最高、误检率增加不明显的最低阈值比如 0.35 而不是默认的 0.25。这个“找阈值”动作经常被忽略但它能直接把漏检率降一半还没动模型结构。最后一件事部署到产线后每天收集新的误检漏检图一旦超过几十张就增量训练一次——YOLOv8 支持在旧权重上续训这就是你的后悔药。这是我从人行道走到产线的习惯希望帮到你。本文还有配套的精品资源点击获取