《使用Qt Quick从零构建AI螺丝瑕疵检测系统》——7. AI赋能(上):训练你自己的YOLOv8瑕疵检测模型
目录
- 一、概述
- 1.1 背景介绍:传统视觉的局限性
- 1.2 AI的范式转移:让机器自主学习
- 1.3 为何本章切换至Python?
- 1.4 学习目标
- 二、Python环境与数据准备
- 2.1 安装Python与配置虚拟环境
- 2.2 核心概念:从“异常样本”到“训练数据”
- 2.3 数据建模:定义我们的瑕疵类别
- 2.4 实战:使用经典标注工具Labelme
- 2.5 数据整理与格式转换
- 2.6 提供预处理数据集
- 三、模型训练 (CPU版)
- 3.1 安装YOLOv8库
- 3.2 编写并运行训练脚本
- 3.3 模型导出为ONNX
- 四、总结与展望
一、概述
1.1 背景介绍:传统视觉的局限性
在上一篇文章中,我们成功地运用传统OpenCV算法实现了对螺丝尺寸的精确测量。这展示了传统视觉算法在处理几何特征明确(如长度、圆形度)问题上的强大能力。然而,当我们面对形态不规则、纹理多样的表面瑕疵(如划痕、油污、凹坑)时,会发现很难再写出固定的、普适的规则来描述它们。
强行用传统算法处理这类问题,往往会导致代码变得异常复杂,且对光照、角度等环境变化极为敏感,鲁棒性差。这正是传统视觉算法的“天花板”。要突破它,我们需要一种全新的方法。
1.2 AI的范式转移:让机器自主学习
【核心概念:从“编写规则”到“学习规律”】
传统算法的思路是人类工程师教计算机做事,我们把识别规则(如“颜色小于某个阈值且面积大于某个值的就是瑕疵”)一条条地写入代码。
而深度学习的思路则完全不同,它模仿人类大脑的学习方式,变成了计算机自己从数据中学习规律。我们不再编写具体的识别规则,而是:
- 准备大量带有“正确答案”的样本(例如,数千张已经圈出瑕疵的图片)。
- 构建一个“神经网络”模型(可以想象成一个结构复杂、有无数旋钮的“黑箱”)。
- 让模型一张张地“看”这些图片,并尝试自己去判断。
- 每判断一次,就将它的答案与“正确答案”对比,然后自动微调内部的无数个“旋钮”,让下一次的判断更准一些。
- 重复数万次后,这个“黑箱”内部的旋钮就自动调整到了一种能够准确识别瑕疵的状态。它自己总结出了规律,甚至是一些人类都无法用语言描述的规律。
【核心概念:目标检测 - “它是什么,它在哪”】
深度学习视觉任务中,与我们需求最匹配的就是目标检测(Object Detection)。它要解决两个核心问题:
- 分类(Classification): 图片里有什么?(例如,这是一处
scratch_head
瑕疵)。 - 定位(Localization): 它在图片的哪个位置?(用一个边界框把它圈出来)。
这完美地契合了工业瑕疵检测的需求。
【核心概念:YOLOv8 - 速度与精度的王者】
在目标检测领域,YOLO(You Only Look Once)系列算法因其卓越的速度和精度而闻名。与早期需要多个步骤才能完成检测的算法不同,YOLO创造性地将整个检测过程统一为一个步骤,实现了真正的端到端实时检测,使其在工业界得到了广泛应用。YOLOv8是该系列的高阶版本,由Ultralytics公司开发,它不仅性能顶尖,而且其配套的Python库极为易用,极大地降低了开发门槛。
1.3 为何本章切换至Python?
本系列教程以Qt/C++为主线,但本章的模型训练部分将完全使用Python完成。这是一个基于行业主流实践的慎重选择:
- AI生态系统: 当今几乎所有的主流深度学习框架(如PyTorch, TensorFlow)都以Python作为其首选和核心的开发语言。YOLOv8的官方库
ultralytics
也是纯Python的。Python拥有无与伦比的社区支持、丰富的第三方库和海量的教程资源,使得模型的研发、训练和调试过程变得极为高效和便捷。 - 职责分离: 在现代软件工程中,“算法研发”与“软件部署”通常是两个独立的阶段。使用Python进行快速的模型迭代和训练(研发阶段),然后将训练好的模型导出,用C++进行高性能的部署和集成(部署阶段),是目前业界最成熟、最高效的工作流。
- 学习效率: 直接在C++中搭建训练环境和编写训练代码,其复杂度和工作量远超Python。为了让读者能专注于理解和实践AI模型训练的核心流程,而不是陷入繁琐的环境配置中,使用Python是最佳选择。
因此,本章将引导读者搭建一个最小化的Python环境,完成模型训练这一特定任务。在下一章,我们将立刻回归C++的世界,学习如何将本章的“劳动成果”——训练好的模型,部署到我们的Qt应用程序中。
1.4 学习目标
通过本篇的学习,读者将能够:
- 搭建一个独立的Python环境,并安装必要的AI库。
- 掌握使用开源工具LabelImg对图像瑕疵进行标注的核心技能。
- 使用Ultralytics YOLOv8框架,在CPU上亲手训练一个能识别多种螺丝瑕疵的AI模型。
- 将训练好的PyTorch模型(
.pt
)导出为部署友好的ONNX格式,为下一章在C++中调用做好准备。
二、Python环境与数据准备
2.1 安装Python与配置虚拟环境
为了不干扰全局环境,推荐使用虚拟环境来管理项目的Python依赖。
1. 安装Python
- 访问Python官网,下载适用于Windo
- ws的最新稳定版Python安装包(例如 Python 3.10.0)。
- 运行安装程序,**务必勾选“Add python.exe to PATH”**选项,然后选择“Install Now”即可。
2. 创建虚拟环境
- 在项目根目录(例如
D:\code\ScrewDetector
)下,打开终端(CMD或PowerShell)。 - 运行以下命令创建虚拟环境:
这会在当前目录下创建一个名为python -m venv .venv
.venv
的文件夹,其中包含了独立的Python解释器和库。 - 激活虚拟环境:
激活成功后,会看到终端提示符前面出现了.\.venv\Scripts\activate
(.venv)
的字样,如下图所示。之后所有的pip安装都将局限于这个环境中。
2.2 核心概念:从“异常样本”到“训练数据”
YOLOv8这类监督学习算法,就像一个需要“教科书”来学习的学生。这本“教科书”就是我们的训练数据集。它必须包含两个部分:
- 图片(问题): 即带有各种瑕疵的螺丝图片。
- 标签(答案): 一个与之对应的文件,明确告诉模型图片中的瑕疵是什么(类别)以及在哪里(边界框坐标)。
而我们使用的MVTec AD数据集,其test
文件夹下虽然有大量瑕疵图片,但缺少对应的“答案”(标签文件)。good
文件夹中的图片由于没有瑕疵,暂时不用于训练(它们将在模型评估时作为负样本使用)。因此,在训练之前,我们必须先完成数据建模和数据标注这两个关键步骤。
2.3 数据建模:定义我们的瑕疵类别
【核心概念:归纳与合并】
我们的目标是设计一套清晰、无歧义、易于模型学习的瑕疵类别。分析MVTec screw
数据集的test
文件夹,它包含了manipulated_front
, scratch_head
, scratch_neck
, thread_side
, thread_top
这5种已分类的瑕疵。
为了让模型更容易学习,也为了让我们的教学目标更聚焦,我们采用一种更务实的策略:将视觉上相似或物理位置上相近的瑕疵进行归纳合并。
我们的合并逻辑如下:
- 所有发生在头部的表面划痕(
scratch_head
),都属于“头部瑕疵”。 - 所有发生在颈部的划痕(
scratch_neck
),都属于“颈部瑕疵”。 - 所有发生在螺纹区域的损伤,无论是侧面的(
thread_side
)、顶部的(thread_top
)还是驱动槽的损伤(manipulated_front
),都统一归为“螺纹瑕疵”。
由此,我们得到了一个更简洁、更鲁棒的分类方案:
类别ID | 类别名称 (Class Name) | 含义解释 | 包含的原始瑕疵 |
---|---|---|---|
0 | head_defect | 头部区域的任何可见损伤。 | scratch_head |
1 | neck_defect | 颈部区域的任何可见损伤。 | scratch_neck |
2 | thread_defect | 螺纹区域的任何可见损伤。 | thread_side , thread_top , manipulated_front |
对应的dataset.yaml
文件应如下:
# ...
names:0: head_defect1: neck_defect2: thread_defect
在接下来的标注和训练环节,我们将完全基于这个三分类方案进行。
2.4 实战:使用经典标注工具Labelme
为了确保标注过程的稳定、高效和离线可用,我们将使用一款在学术界和工业界都广受赞誉的经典开源工具——Labelme。
【核心概念:稳定可靠的桌面端标注】
Labelme是一个用Python编写的、跨平台的图形化图像标注软件。
- 为何选择 Labelme?
- 稳定与成熟: Labelme由麻省理工学院(MIT)的计算机科学与人工智能实验室(CSAIL)发起,经过了长时间的发展和迭代,功能非常稳定,是许多经典数据集的“御用”标注工具。
- 安装简单: 作为一个纯Python应用,它可以通过pip轻松安装,避免了复杂的环境依赖问题。
- 多功能: 虽然我们主要用它画矩形框,但它本身支持多边形、圆形、线条等多种标注形式,为未来可能的复杂任务(如语义分割)提供了扩展性。
- 格式转换方便: Labelme默认保存为JSON格式,但有大量现成的脚本可以轻松将其转换为YOLO格式。
1. 安装与启动
- 确保虚拟环境已激活,在终端中运行:
pip install "numpy<2.0" onnxruntime==1.18.0 labelme -i https://pypi.tuna.tsinghua.edu.cn/simple
- 安装完成后,运行以下命令启动程序:
labelme
- 一个图形化的应用程序窗口将会启动,如下图所示:
2. 标注流程演示
- (1) 标注设置:单击顶部菜单栏
文件
将其展开,将 “同时保存图像数据”复选框的勾去掉,然后单击“自动保存”按钮。此操作在每次打开Labelme软件时均需操作。 - (2) 打开目录: 在Labelme菜单栏中,选择
文件
->打开目录
,然后选择我们数据集中的一个瑕疵图片文件夹,例如.../dataset/screw/test/scratch_head
。
- (3) 创建矩形框: 在顶部菜单栏中,单击
编辑
->创建矩形
按钮。 - (4) 拖动画框并输入标签: 在图片中的瑕疵区域拖动鼠标,画出一个矩形框。松开鼠标后,会弹出一个对话框,让你输入该标注的标签(类别名称)。输入我们定义好的类别,如
head_defect
,然后点击OK。
- (d) 保存: 点击
文件
->保存
(或按快捷键Ctrl+S
)。Labelme会在图片同目录下创建一个同名的.json
文件,里面详细记录了标注信息。 - (e) 切换与重复: 使用
上一幅
和下一幅
按钮切换图片,重复(b)至(d)的步骤,直到所有图片标注完成。
2.5 数据整理与格式转换
经过上一步,我们已经在各个瑕疵子文件夹(如scratch_head
, thread_side
等)中,为每张图片都生成了一个对应的.json
标注文件。然而,这些数据还很分散,并且存在文件名冲突(每个文件夹下都有000.png
)。此外,Labelme的JSON格式也不是YOLOv8能直接使用的格式。
本节将通过一个自动化脚本,一步到位地解决所有问题。
【核心概念:YOLO标注格式】
在进行转换前,有必要了解YOLO的.txt
标注格式。对于每个图像文件(如image.png
),都有一个对应的文本文件(image.txt
),其内容格式如下:
<class_id> <x_center> <y_center> <width> <height>
<class_id>
: 类别的索引号(从0开始),对应于labels.txt
文件中的行号。<x_center> <y_center>
: 边界框中心的归一化坐标(值在0到1之间)。<width> <height>
: 边界框的归一化宽度和高度。
归一化意味着所有坐标值都除以了图像的原始宽度或高度。我们的自动化脚本将处理所有这些复杂的计算。
【核心概念:两步走策略】
我们的自动化流程将分为清晰的两步:
- 数据整理 (Python): 使用Python脚本,将所有分散的瑕疵图片和
.json
文件合并到一个统一的临时文件夹中,同时完成重命名和JSON内部路径的修正。 - 格式转换与划分 (labelme2yolo): 调用高效的
labelme2yolo
命令行工具,让它自动处理上一步生成的临时文件夹,完成从JSON到YOLO.txt
的格式转换,并自动按比例划分好训练集和验证集。
【例7-1】 自动化数据整理与转换脚本
1. 安装依赖库
- 确保虚拟环境已激活,安装
labelme2yolo
库。它会自动处理所有依赖。pip install labelme2yolo -i https://pypi.tuna.tsinghua.edu.cn/simple
2. 编写代码 (prepare_dataset.py)
- 在项目根目录下的
dataset
文件夹中,创建一个名为prepare_dataset.py
的Python脚本文件。
import os
import shutil
import json
import subprocessdef prepare_dataset(base_dir='.'):"""自动化处理MVTec Screw数据集:1. 使用Python合并、重命名、修正JSON,创建一个统一的数据池。2. 调用labelme2yolo命令行工具,完成格式转换和数据集划分。"""screw_dir = os.path.join(base_dir, 'screw', 'test')# 临时目录,用于存放所有整理好的图片和JSON文件temp_json_dir = os.path.join(base_dir, 'temp_labelme_dataset')# 清理并创建临时目录if os.path.exists(temp_json_dir):shutil.rmtree(temp_json_dir)os.makedirs(temp_json_dir, exist_ok=True)print("--- 步骤1: 开始整理原始数据 ---")# --- 合并、重命名与修正JSON ---file_count = 0for defect_type in os.listdir(screw_dir):defect_folder = os.path.join(screw_dir, defect_type)if os.path.isdir(defect_folder) and defect_type != 'good':print(f" 处理文件夹: {defect_type}")for filename in os.listdir(defect_folder):if filename.endswith('.json'):base_name = os.path.splitext(filename)[0]img_filename = base_name + '.png'# 创建唯一的新文件名new_filename_base = f"{defect_type}_{base_name}"# 复制并重命名图片shutil.copy2(os.path.join(defect_folder, img_filename), os.path.join(temp_json_dir, new_filename_base + '.png'))# 读取、修改并保存JSONjson_path = os.path.join(defect_folder, filename)with open(json_path, 'r', encoding='utf-8') as f:data = json.load(f)data['imagePath'] = new_filename_base + '.png' # 修正imagePathnew_json_path = os.path.join(temp_json_dir, new_filename_base + '.json')with open(new_json_path, 'w', encoding='utf-8') as f:json.dump(data, f, indent=4)file_count += 1print(f"数据整理完成,共处理 {file_count} 个文件,已全部存放在 '{temp_json_dir}'")print("\n--- 步骤2: 调用labelme2yolo进行转换和划分 ---")# --- 调用labelme2yolo命令行工具 ---# 构建命令行指令command = ["labelme2yolo","--json_dir", temp_json_dir,"--val_size", "0.1" # 指定10%的数据作为验证集]# 执行命令try:print(f" 执行命令: {' '.join(command)}")subprocess.run(command, check=True)print("\nlabelme2yolo成功执行!")# labelme2yolo会自动在temp_json_dir下创建YOLODataset文件夹final_dataset_path = os.path.join(temp_json_dir, "YOLODataset")print(f"最终的YOLO数据集已生成在: {final_dataset_path}")# (可选) 清理临时文件夹# shutil.rmtree(temp_json_dir)# print("已清理临时文件夹。")except subprocess.CalledProcessError as e:print(f"\n错误:labelme2yolo执行失败!请检查是否已正确安装。错误信息: {e}")except FileNotFoundError:print("\n错误:找不到'labelme2yolo'命令。请确保已激活虚拟环境,并且labelme2yolo已安装。")if __name__ == '__main__':# 确保在dataset目录下运行此脚本prepare_dataset()
3. 运行数据整理与转换脚本
- 运行: 在
dataset
目录下打开终端(确保虚拟环境已激活),并运行脚本:python prepare_dataset.py
- 脚本会依次执行两个步骤,并打印出详细的日志。完成后,在
dataset/temp_labelme_dataset
文件夹下,会生成一个名为YOLODataset
的文件夹。这,就是我们最终用于训练的标准YOLO数据集! 其内部结构如下:temp_labelme_dataset/ └── YOLODataset/├── images/│ ├── train/ (*.png)│ └── val/ (*.png)├── labels/│ ├── train/ (*.txt)│ └── val/ (*.txt)└── dataset.yaml
dataset.yaml
文件也会被labelme2yolo
自动创建好,内容类似如下:
path: \\?\D:\code\ScrewDetector\dataset\temp_labelme_dataset\YOLODataset # 数据集根目录的绝对路径
train: images/train # 训练集图片路径 (相对于path)
val: images/val # 验证集图片路径 (相对于path)
test:
# 类别名称
names:0: neck_defect1: thread_defect2: head_defect
至此,数据准备工作才算真正大功告告成!通过一个自动化脚本,我们将一个复杂的数据工程问题变得简单、清晰而可复现。
2.6 提供预处理数据集
手动标注和处理所有图片非常耗时。为了让读者能专注于模型训练本身,这里提供一个已经从MVTec数据集中挑选、标注并划分好的、可直接用于YOLOv8训练的数据集包。
-
下载链接: https://pan.baidu.com/s/1aJSD9eFiCTXPctk0AulT8w?pwd=bkst 提取码: bkst
-
目录结构: 解压后,其内部结构如下:
YOLODataset/ ├── train/ │ ├── images/ (*.png) │ └── labels/ (*.txt) ├── val/ │ ├── images/ (*.png) │ └── labels/ (*.txt) └── dataset.yaml
三、模型训练 (CPU版)
现在,万事俱备,可以开始训练我们的AI模型了。为了确保所有读者都能成功复现,本教程将全程使用CPU进行训练。
3.1 安装YOLOv8库
确保虚拟环境已激活,然后在终端中运行:
pip install ultralytics onnx -i https://pypi.tuna.tsinghua.edu.cn/simple
3.2 编写并运行训练脚本
【例7-1】 YOLOv8模型训练。
在项目根目录创建一个train.py
文件。
from ultralytics import YOLOif __name__ == '__main__':# 1. 加载一个预训练模型# yolov8n.pt 是最小的模型,适合快速教学和CPU训练model = YOLO('yolov8n.pt')# 2. 开始训练# data: 指向我们的数据集配置文件# epochs: 训练轮次,表示要将整个数据集看多少遍。对于教学,设置一个较小的值。# imgsz: 训练时图像的尺寸# device: 明确指定使用CPU进行训练results = model.train(data='./dataset/yolo_screw_dataset/dataset.yaml',epochs=50,imgsz=640,device='cpu')print("训练完成!模型保存在:", results.save_dir)
在终端中运行此脚本:
python train.py
程序会自动下载预训练模型,然后开始在CPU上进行训练。相比GPU,CPU训练会慢很多,请耐心等待。终端中会打印出每一轮的训练进度和性能指标。
最终输出如下:
50 epochs completed in 0.356 hours.
Optimizer stripped from runs\detect\train\weights\last.pt, 6.2MB
Optimizer stripped from runs\detect\train\weights\best.pt, 6.2MBValidating runs\detect\train\weights\best.pt...
Ultralytics 8.3.170 Python-3.10.0 torch-2.7.1+cpu CPU (12th Gen Intel Core(TM) i9-12900K)
Model summary (fused): 72 layers, 3,006,233 parameters, 0 gradients, 8.1 GFLOPsClass Images Instances Box(P R mAP50 mAP50-95): 100%|██████████| 1/1 [00:01<00:00, 1.04s/it]all 12 12 0.829 0.67 0.789 0.538neck_defect 1 1 0.916 1 0.995 0.895thread_defect 9 9 0.572 0.333 0.377 0.147head_defect 2 2 1 0.677 0.995 0.572
Speed: 1.3ms preprocess, 61.4ms inference, 0.0ms loss, 4.6ms postprocess per image
Results saved to runs\detect\train
训练完成!模型保存在: runs\detect\train
在验证集图像上的部分结果如下:
可以看到,模型的准确率还是比较高的。
【关于GPU训练的说明】
虽然本教程为了通用性采用了CPU训练,但在实际的工程项目中,深度学习模型的训练几乎全部在GPU设备上完成。GPU拥有数千个并行处理核心,其训练速度相比CPU可以有数十倍甚至上百倍的提升。
如果读者的电脑配备了NVIDIA显卡并正确安装了CUDA环境,只需将device='cpu'
改为device=0
(0代表第一块GPU),即可享受高速训练的体验。对于没有本地GPU设备的开发者,也可以考虑租用云服务商提供的线上GPU服务器来完成训练,这已成为业界非常普遍的做法。
3.3 模型导出为ONNX
训练完成后,最好的模型(通常是best.pt
)会保存在runs/detect/train/weights
目录下。.pt
是PyTorch格式,为了能在C++的OpenCV DNN模块中使用,我们需要将其导出为更通用的**ONNX (Open Neural Network Exchange)**格式。
【例7-2】 模型导出。
创建一个export.py
文件。
from ultralytics import YOLOif __name__ == '__main__':# 1. 加载我们自己训练好的模型# 路径需要根据实际训练结果的输出目录进行修改model = YOLO('./runs/detect/train/weights/best.pt')# 2. 导出为ONNX格式# opset是ONNX的版本,12或更高通常有较好的兼容性success_path = model.export(format='onnx', opset=12)if success_path:print("模型成功导出为ONNX格式:", success_path)
运行python export.py
后,会在相同的权重目录下生成一个best.onnx
文件。这个文件,就是我们下一篇文章中C++后端需要加载的AI模型!
四、总结与展望
在本篇文章中,我们完成了一次从传统视觉到现代AI的思想跨越。通过学习深度学习和目标检测的基本概念,我们理解了AI技术在复杂瑕疵检测中的必要性。随后,我们亲手实践了数据标注、环境搭建、模型训练和模型导出这一套完整的AI模型研发流程。
现在,我们手中已经握有了驱动程序智能化的“AI大脑”。但它还静静地躺在Python的世界里。如何跨越语言的边界,在C++的性能世界里唤醒并驱动这个模型?
这将是我们下一篇文章**【《使用Qt Quick从零构建AI螺丝瑕疵检测系统》——8. AI赋能(下):在Qt中部署YOLOv8模型】**的核心主题。我们将学习如何使用OpenCV的DNN模块,在Qt程序中实现AI模型的推理,让我们的应用真正变得“智能”。