简介:面向计算机相关专业毕设与课程设计场景,基于YOLOv8实现化工管道焊缝缺陷检测,包含可视化界面、完整数据集与部署教程,下载后按README说明即可运行。代码经测试通过,可输出核心指标曲线、混淆矩阵、F1分数曲线、精确率-召回率曲线、验证集预测结果与标签分布图,适合答辩展示,也便于在此基础上改进检测逻辑或扩展缺陷类型。资源包共97个文件,以Python源码(70个py)、训练权重(4个pt)、配置文件与说明文档(xml/txt)为主,另含界面图标和演示视频,整体24.21MB。界面交互、模型训练、视频检测等模块一应俱全,目录结构简洁,便于按需查看。已有56人浏览学习,适合初次接触目标检测的学生用于快速上手并理解YOLOv8训练与推理流程,也可作为课程设计或毕业设计的完整参考。
1. 焊缝缺陷检测为什么选YOLOv8:这套毕设系统的三大件拆解
化工管道在长期承压运行后,焊缝是最容易出问题的部位,而射线底片评定一直依赖检验人员肉眼读片。YOLOv8介入之后,这件事就变成了一条可复现的流水线:加载一份训练好的权重,把底片或数字图像丢进去,出来就是带类别标签和置信度的检测框。你说的这个系统,本质上就是由“完整数据集 + YOLOv8源码 + 可视化界面 + 部署教程”四块拼起来的,适合机械、化工、计算机方向正在做毕设或课程设计的同学。
我看了不少类似项目,最容易被低估的不是训练,而是数据整理和界面对接。数据决定模型上限,界面决定演示效果,训练反而只占整个工作量的三成左右。如果你手上拿到的这份源码包连部署教程都带了,那你要做的不是改模型结构,而是把环境跑通、把数据看明白、把界面的调用逻辑理顺,然后换成自己的数据集重新训练一遍。这篇文章就按这个顺序来写。
2. 环境搭建与首次运行:把“简单部署”从口号变成能演示的界面
2.1 CPU也能跑:版本搭配方案
先解决环境。YOLOv8不是独立框架,它对应的是ultralytics这个Python包,环境要求其实很宽松:Python 3.8到3.10之间最稳,PyTorch 2.0以上,ultralytics装 8.x 版本即可。如果你的机器没有独立显卡,也不用慌,CPU能跑推理,只是速度慢一些,演示一张图完全够用。很多同学就是按“ubuntu20.04搭建yolov8环境cpu版本”的思路来做本地部署,这套组合是经过大量验证的。
我一般建议用conda先建一个干净环境,避免把系统Python搞乱:
conda create -n weld_yolo python=3.9 -y conda activate weld_yolo pip install ultralytics==8.2.0 torch torchvision如果你有NVIDIA显卡,先看驱动支持什么CUDA版本,再装对应的PyTorch。CPU版则不需要关心CUDA,直接pip install torch torchvision就会拉CPU版本。装完后用一行命令验证:
python -c "from ultralytics import YOLO; print(YOLO('yolov8n.pt'))"能打印出模型对象说明环境没问题。注意yolov8n.pt是ultralytics自动下载的预训练权重,首次运行需要联网,下载慢的话可以手动把权重文件放进项目weights目录。
2.2 目录结构与启动入口
拿到项目包先别急着双击任何文件,先看目录。典型结构是这样:
weld_defect_system/ ├── weights/ │ └── best.pt ├── datasets/ │ ├── images/ │ └── labels/ ├── main.py ├── requirements.txt └── 部署教程.mdweights/best.pt是训练好的模型,main.py是可视化界面入口,requirements.txt是依赖清单。先打开部署教程文档,再对照requirements.txt装依赖,顺序不要反。有些项目会用pip install -r requirements.txt一键装,但要注意里面可能有opencv-python、PyQt5这类体积较大的包,耐心等。
界面启动一般是:
python main.py如果项目里没有main.py,去找带“UI”“window”“gui”字样的脚本,比如window.py或gui.py。启动后界面会弹出文件选择框,选一张焊缝图像,点击检测按钮,左侧显示原图,右侧显示带框的结果。这一步能跑通,说明源码、权重、界面三块都是完整的。
2.3 用最小推理脚本验证权重
启动界面之前,我建议先用命令行把权重跑一遍,这样能快速定位问题是出在环境、权重还是界面上。新建一个test_infer.py:
from ultralytics import YOLO model = YOLO("weights/best.pt") results = model.predict( source="demo.jpg", conf=0.25, iou=0.5, imgsz=640, save=True, project="runs/detect", name="weld_test", ) print(results[0].boxes.cls) print(results[0].boxes.conf) print(results[0].boxes.xyxy)这段代码里的conf=0.25是置信度阈值,低于这个值的框会被丢弃,调低会看到更多候选框但误检也会增多;iou=0.5是NMS去重的交并比阈值,同一条缺陷的重叠框会被合并;imgsz=640是输入分辨率,焊缝气孔这种小目标可以试着提到768。跑完后结果图保存在runs/detect/weld_test下。
boxes.cls输出的是类别索引,比如[0, 1]表示检测到了两个缺陷,分别是第0类和第1类;boxes.conf是置信度;boxes.xyxy是左上角和右下角坐标。看到这行输出,就可以放心打开界面了。
3. 焊缝缺陷数据集与YOLO标注:六类缺陷的标签怎么做才算合格
3.1 六类缺陷长什么样,难在哪
化工管道焊缝射线底片里,最常见的缺陷类别有六种:气孔、夹渣、未熔合、未焊透、咬边、裂纹。这六类在底片上的形态差异很大,也是你在标注阶段要反复区分的对象:
- 气孔:圆形或椭圆形暗斑,边缘清晰,通常成串或成群出现,是最好标的一类。
- 夹渣:不规则块状暗区,边界模糊,常藏在焊缝内部,标的时候很容易画大。
- 未熔合:沿坡口方向的细长直线暗影,宽度很窄,需要放大图片才能看清。
- 未焊透:焊缝根部一条连续或断续的暗线,比夹渣更规则。
- 咬边:焊缝边缘母材上的凹陷,呈条状,紧贴焊缝边界。
- 裂纹:最棘手的一类,细长、曲折、末端尖锐,长宽比经常超过10:1。
难点很清楚:气孔和夹渣是大目标,裂纹和未熔合是小目标。如果你拿到的数据集是从公开的X射线焊缝数据集(比如GDXray这类)里整理的,那类别分布基本就是按上面六类给的;如果是自己拍的底片,标注前一定先做类别确认,别把“夹渣”和“未熔合”混在一起,后面训练的模型会一直懵。
3.2 labelme标注与YOLO格式转换
标注阶段用labelme还是labelImg都行,但要注意一点:labelme保存的是JSON文件,不能直接给YOLOv8训练,必须转成YOLO格式的txt。YOLO的标签格式是每行五个数:
class_id x_center y_center width height其中坐标全部是归一化比例,不是像素值。比如一张1000×800的图,缺陷中心在(500, 400),宽200、高100,那么这一行就是0 0.5 0.5 0.2 0.125。类别的顺序由你的类别清单决定,不是写死的气孔就是0。
转换完成后,写一个校验脚本检查所有标签,这步能省下训练时的排查时间:
import os label_dir = "datasets/weld/labels/train" class_names = ["porosity", "slag", "lack_of_fusion", "incomplete_penetration", "undercut", "crack"] for fname in os.listdir(label_dir): if not fname.endswith(".txt"): continue path = os.path.join(label_dir, fname) with open(path, encoding="utf-8") as f: lines = f.read().strip().splitlines() for i, line in enumerate(lines): parts = line.split() if len(parts) != 5: print(f"{fname} 第{i+1}行: 必须5个数, 实际{len(parts)}个") continue cls = int(parts[0]) x, y, w, h = map(float, parts[1:]) if not (0 <= cls < len(class_names)): print(f"{fname} 第{i+1}行: 类别索引越界 -> {cls}") if not (0 < x < 1 and 0 < y < 1 and 0 < w < 1 and 0 < h < 1): print(f"{fname} 第{i+1}行: 归一化坐标越界 -> {line}")这段脚本的重点是检查出三类问题:长度不对、类别越界、坐标越界。坐标越界最常见的来源是标注工具在图片边缘画框时,或转换脚本四舍五入出错,坐标计算成了负数或大于1的值。越界的标签不会被YOLO直接报错,但会产生质量很差的候选框,手动排查极麻烦。
3.3 数据集划分与重复检查
数据准备好后,按train / val划分,常见比例是85/15或80/20。我习惯用脚本随机划分,同时把图片和标签一起拷过去:
import random import os from shutil import copy2 random.seed(42) images = [f for f in os.listdir("images_all") if f.endswith((".jpg", ".png", ".jpeg"))] random.shuffle(images) split = int(len(images) * 0.85) train_list = images[:split] val_list = images[split:] for split_name, file_list in [("train", train_list), ("val", val_list)]: os.makedirs(f"datasets/weld/images/{split_name}", exist_ok=True) os.makedirs(f"datasets/weld/labels/{split_name}", exist_ok=True) for file_name in file_list: copy2(os.path.join("images_all", file_name), os.path.join("datasets/weld/images", split_name, file_name)) label_name = os.path.splitext(file_name)[0] + ".txt" src_label = os.path.join("labels_all", label_name) if os.path.exists(src_label): copy2(src_label, os.path.join("datasets/weld/labels", split_name, label_name)) else: print(f"警告: {file_name} 缺少标签文件")跑完脚本后检查两个事:第一,labels/val里不能有和images/train同名的文件,防止数据泄露;第二,检查是否有空标签文件,即txt文件大小为0,这类文件对应的图像会被YOLO忽略,导致实际参与训练的图像数量比预期少。检查空标签用一行find命令:
find datasets/weld/labels/train -name "*.txt" -size 0如果结果不为空,要么重新标注,要么把这些图片从数据集里移出去。
4. 训练自己的焊缝模型:五个必调参数与loss曲线判读
4.1 先写对data.yaml
YOLOv8训练时需要一个数据集配置文件,这是新手最容易写错的地方。在项目根目录建weld.yaml:
path: /home/yourname/weld_defect_system/datasets/weld train: images/train val: images/val nc: 6 names: 0: porosity 1: slag 2: lack_of_fusion 3: incomplete_penetration 4: undercut 5: crackpath必须写绝对路径,YOLOv8对相对路径的解析经常出问题;train和val是相对path的路径,不能加前缀/;nc是类别数,这里6;names的索引必须和标签txt里的class_id一一对应。很多“No labels found”的报错,最后查下来都是path少了一层目录。
4.2 训练命令与参数表
数据集就绪后,训练命令长这样:
yolo train model=yolov8n.pt data=weld.yaml epochs=120 batch=16 imgsz=640 workers=4 device=0yolov8n.pt是COCO预训练权重,在这个基础上做迁移学习,收敛比从零训练快得多。如果你有GPU但显存只有6G左右,把batch降到8;没有GPU就把device=cpu加上,但训练时间会拉长到几小时甚至十几小时,建议先跑少量轮次验证流程。
几个必调参数按下面这张表来:
| 参数 | 建议值 | 说明 |
|---|---|---|
| epochs | 100-150 | 焊接缺陷特征不算复杂,120轮左右足够收敛 |
| batch | 8-16 | 受显存约束,OOM就减半 |
| imgsz | 640-768 | 气孔、裂纹占比小,768能明显提升小目标召回 |
| lr0 | 0.005-0.01 | 预训练权重迁移时,学习率过大容易把已有特征冲掉 |
| patience | 30 | 验证集指标连续30轮不提升就早停 |
| workers | 4-8 | 数据加载线程数,CPU不够就调低 |
训练过程中终端会实时打印每个epoch的loss和mAP,不用盯着看,但要注意有没有出现nan。lr0设了0.01还出现loss爆炸,通常是数据里混入了异常标签,回到第3章跑一遍校验脚本。
4.3 用loss曲线和mAP判断训练状态
训练完成后,所有指标都落在runs/detect/train/目录下。不要只看最后的mAP,要把训练过程拉成曲线看。YOLOv8训练时自动生成results.csv,用脚本画出来:
import pandas as pd import matplotlib.pyplot as plt df = pd.read_csv("runs/detect/train/results.csv") plt.figure(figsize=(10, 4)) plt.plot(df["epoch"], df["train/box_loss"], label="train_box_loss") plt.plot(df["epoch"], df["val/box_loss"], label="val_box_loss") plt.xlabel("epoch") plt.ylabel("loss") plt.legend() plt.grid(True) plt.savefig("loss_curve.png")正常的收敛曲线是训练和验证的box_loss同步下降并趋于平缓。如果训练loss一直降但验证loss在后半段反弹,说明过拟合,常见对策是减小lr0、增大数据增强的scale参数,或者补充更多标注样本。如果两条loss都在高位震荡不下降,先查标签是否有错,再查学习率是否设置合理。
模型权重会保存两份:best.pt是验证集mAP最高的版本,last.pt是最后一轮的版本。部署时一律用best.pt,不要觉得训练轮数越多权重越好。我自己的习惯是只保留best.pt,然后拿它对一张训练集之外的底片做一次推理,确认检测框落在真实缺陷位置上,再谈部署。
5. 部署踩坑与排查:五个高频问题让项目翻车在答辩前
5.1 训练时反复报“No labels found”
- 现象:
yolo train启动后很快报错,提示Dataset 'weld.yaml' error、labels not found或No labels found in ...,训练直接中止。 - 原因:大概率是三件事之一——
weld.yaml里的path写成了相对路径;images/train和labels/train路径下对应文件不匹配;图片后缀不是.jpg/.png导致YOLO没扫描到。 - 解决:先用
ls datasets/weld/images/train | head -3看一下图片文件确实存在,再确认find datasets/weld/labels/train -name "*.txt" | wc -l的txt数量和图片数量一致,最后把path改成绝对路径后重新跑一次。
5.2 界面点“开始检测”直接卡死或白屏
- 现象:界面能打开,但一点检测按钮就无响应,Windows里显示“未响应”,过几秒甚至永久卡住。
- 原因:
main.py里把model.predict()直接放在了按钮点击回调里,推理阻塞了Qt的事件循环。这是新手写界面最常犯的错误,严格说不是YOLO的问题,是线程模型的问题。 - 解决:把推理放到
QThread里执行,或用线程池提交任务。如果不想改线程结构,至少在界面启动前先加载best.pt,不要在点击时才YOLO(...),模型加载也是耗时操作。改了线程后,推理过程中界面还能正常拖动窗口,就是一个合格的修复。
5.3 气孔、裂纹漏检严重,mAP卡在低位
- 现象:大目标夹渣能检出,小目标气孔和裂纹几乎全部漏掉,mAP50一直上不去。
- 原因:两个主因,第一是
imgsz=640对直径只有十几个像素的气孔来说太小;第二是置信度阈值设太高,比如conf=0.5,小目标本来置信度就低,直接被过滤了。 - 解决:训练时把
imgsz=768,推理时降到conf=0.15-0.2观察候选框数量。如果训练集里裂纹样本太少,比如不足150个实例,先做数据扩增——水平翻转、小角度旋转、亮度抖动,对细长目标有效。裂纹这类缺陷,样本数量压倒算法技巧。
5.4 CPU训练慢到让人想直接放弃
- 现象:一台只有CPU的笔记本跑
yolov8m+imgsz=640,一个epoch要好几分钟,120轮根本没法等。 - 原因:模型规模选大了。
yolov8m的参数量是yolov8n的三倍多倍数级,CPU推理本来就慢,训练更是放大十倍。 - 解决:训练用
yolov8n.pt+imgsz=640+batch=4,先把流程跑通;最后做演示时再考虑换大的模型权重。如果真的需要高性能权重,本地用小模型完成开发验证,用云GPU跑一轮完整训练,再把best.pt下载回来本地部署,这是个成熟的低成本路线。
5.5 中文路径导致图像读不出来、文字乱码
- 现象:图像放在
D:\实验数据\底片\001.jpg这种带中文路径的位置,界面检测不出结果,或者终端输出一堆???乱码;界面上的标签中文显示成方块。 - 原因:OpenCV 的
imread不支持中文路径,会静默返回空图;matplotlib 默认字体不含中文字形。 - 解决:训练推理都不走中文路径,项目目录统一用英文;如果必须读,用
np.fromfile加cv2.imdecode代替imread:
import cv2 import numpy as np data = np.fromfile("实验数据/底片/001.jpg", dtype=np.uint8) img = cv2.imdecode(data, cv2.IMREAD_COLOR)界面中文乱码则是在matplotlib里手动指定中文字体,比如plt.rcParams['font.sans-serif'] = ['SimHei']。这个问题在答辩演示时特别容易翻车,提前检查演示机的中文语言包。
6. 进阶验证:导出ONNX,用未参与训练的图像做一次读片测试
训练完模型,下一步不只是演示,而是做一个更接近工程实践的验证:把best.pt导出成ONNX,再用非训练数据检验泛化能力。ONNX的好处是摆脱PyTorch依赖,推理速度更快,也方便以后接C++或嵌入式端。
导出命令很简单:
yolo export model=runs/detect/train/weights/best.pt format=onnx imgsz=640 simplify=True导出的best.onnx可以直接用ultralytics的接口加载,省去手写预处理和后处理的麻烦:
from ultralytics import YOLO model = YOLO("runs/detect/train/weights/best.onnx") result = model.predict("unseen_001.jpg", conf=0.25, imgsz=640)[0] print(result.boxes.cls.tolist()) print(result.boxes.conf.tolist())我的验收标准是:从未参与训练和验证的图像里再挑出20张,六类缺陷至少能检出四类,且每张图的误报控制在两个以内。如果漏检集中在某一类,就针对那类补数据重新训练;如果误报多,就把置信度阈值上调。用这个指标代替单看mAP,会更贴近答辩时“现场演示一张没见过的图”的场景。
这几年在毕设辅导里,我提醒最多的一句话是:YOLOv8本身是稳定可靠的,项目翻车基本都是环境版本冲突、数据标签混乱、界面线程阻塞这三类问题,花在排查上的时间远大于写代码的时间。每次拿到新的数据集,先写一个校验脚本把标签全查一遍,再开始训练。希望这套操作思路帮你在自己的焊缝缺陷检测项目里少走一段弯路。
本文还有配套的精品资源,点击获取