当前位置: 首页 > ops >正文

《使用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的范式转移:让机器自主学习

【核心概念:从“编写规则”到“学习规律”】

传统算法的思路是人类工程师教计算机做事,我们把识别规则(如“颜色小于某个阈值且面积大于某个值的就是瑕疵”)一条条地写入代码。

而深度学习的思路则完全不同,它模仿人类大脑的学习方式,变成了计算机自己从数据中学习规律。我们不再编写具体的识别规则,而是:

  1. 准备大量带有“正确答案”的样本(例如,数千张已经圈出瑕疵的图片)。
  2. 构建一个“神经网络”模型(可以想象成一个结构复杂、有无数旋钮的“黑箱”)。
  3. 让模型一张张地“看”这些图片,并尝试自己去判断。
  4. 每判断一次,就将它的答案与“正确答案”对比,然后自动微调内部的无数个“旋钮”,让下一次的判断更准一些。
  5. 重复数万次后,这个“黑箱”内部的旋钮就自动调整到了一种能够准确识别瑕疵的状态。它自己总结出了规律,甚至是一些人类都无法用语言描述的规律。

【核心概念:目标检测 - “它是什么,它在哪”】

深度学习视觉任务中,与我们需求最匹配的就是目标检测(Object Detection)。它要解决两个核心问题:

  1. 分类(Classification): 图片里有什么?(例如,这是一处scratch_head瑕疵)。
  2. 定位(Localization): 它在图片的哪个位置?(用一个边界框把它圈出来)。

这完美地契合了工业瑕疵检测的需求。

【核心概念:YOLOv8 - 速度与精度的王者】

在目标检测领域,YOLO(You Only Look Once)系列算法因其卓越的速度和精度而闻名。与早期需要多个步骤才能完成检测的算法不同,YOLO创造性地将整个检测过程统一为一个步骤,实现了真正的端到端实时检测,使其在工业界得到了广泛应用。YOLOv8是该系列的高阶版本,由Ultralytics公司开发,它不仅性能顶尖,而且其配套的Python库极为易用,极大地降低了开发门槛。

1.3 为何本章切换至Python?

本系列教程以Qt/C++为主线,但本章的模型训练部分将完全使用Python完成。这是一个基于行业主流实践的慎重选择:

  1. AI生态系统: 当今几乎所有的主流深度学习框架(如PyTorch, TensorFlow)都以Python作为其首选和核心的开发语言。YOLOv8的官方库ultralytics也是纯Python的。Python拥有无与伦比的社区支持、丰富的第三方库和海量的教程资源,使得模型的研发、训练和调试过程变得极为高效和便捷。
  2. 职责分离: 在现代软件工程中,“算法研发”与“软件部署”通常是两个独立的阶段。使用Python进行快速的模型迭代和训练(研发阶段),然后将训练好的模型导出,用C++进行高性能的部署和集成(部署阶段),是目前业界最成熟、最高效的工作流。
  3. 学习效率: 直接在C++中搭建训练环境和编写训练代码,其复杂度和工作量远超Python。为了让读者能专注于理解和实践AI模型训练的核心流程,而不是陷入繁琐的环境配置中,使用Python是最佳选择。

因此,本章将引导读者搭建一个最小化的Python环境,完成模型训练这一特定任务。在下一章,我们将立刻回归C++的世界,学习如何将本章的“劳动成果”——训练好的模型,部署到我们的Qt应用程序中。

1.4 学习目标

通过本篇的学习,读者将能够:

  1. 搭建一个独立的Python环境,并安装必要的AI库。
  2. 掌握使用开源工具LabelImg对图像瑕疵进行标注的核心技能。
  3. 使用Ultralytics YOLOv8框架,在CPU上亲手训练一个能识别多种螺丝瑕疵的AI模型。
  4. 将训练好的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这类监督学习算法,就像一个需要“教科书”来学习的学生。这本“教科书”就是我们的训练数据集。它必须包含两个部分:

  1. 图片(问题): 即带有各种瑕疵的螺丝图片。
  2. 标签(答案): 一个与之对应的文件,明确告诉模型图片中的瑕疵是什么(类别)以及在哪里(边界框坐标)

而我们使用的MVTec AD数据集,其test文件夹下虽然有大量瑕疵图片,但缺少对应的“答案”(标签文件)。good文件夹中的图片由于没有瑕疵,暂时不用于训练(它们将在模型评估时作为负样本使用)。因此,在训练之前,我们必须先完成数据建模数据标注这两个关键步骤。

2.3 数据建模:定义我们的瑕疵类别

【核心概念:归纳与合并】

我们的目标是设计一套清晰、无歧义、易于模型学习的瑕疵类别。分析MVTec screw数据集的test文件夹,它包含了manipulated_front, scratch_head, scratch_neck, thread_side, thread_top这5种已分类的瑕疵。

为了让模型更容易学习,也为了让我们的教学目标更聚焦,我们采用一种更务实的策略:将视觉上相似或物理位置上相近的瑕疵进行归纳合并

我们的合并逻辑如下:

  1. 所有发生在头部的表面划痕(scratch_head),都属于“头部瑕疵”。
  2. 所有发生在颈部的划痕(scratch_neck),都属于“颈部瑕疵”。
  3. 所有发生在螺纹区域的损伤,无论是侧面的(thread_side)、顶部的(thread_top)还是驱动槽的损伤(manipulated_front),都统一归为“螺纹瑕疵”。

由此,我们得到了一个更简洁、更鲁棒的分类方案:

类别ID类别名称 (Class Name)含义解释包含的原始瑕疵
0head_defect头部区域的任何可见损伤。scratch_head
1neck_defect颈部区域的任何可见损伤。scratch_neck
2thread_defect螺纹区域的任何可见损伤。thread_side, thread_top, manipulated_front

对应的dataset.yaml文件应如下:

# ...
names:0: head_defect1: neck_defect2: thread_defect

在接下来的标注和训练环节,我们将完全基于这个三分类方案进行。


2.4 实战:使用经典标注工具Labelme

为了确保标注过程的稳定、高效和离线可用,我们将使用一款在学术界和工业界都广受赞誉的经典开源工具——Labelme

【核心概念:稳定可靠的桌面端标注】

Labelme是一个用Python编写的、跨平台的图形化图像标注软件。

  • 为何选择 Labelme?
    1. 稳定与成熟: Labelme由麻省理工学院(MIT)的计算机科学与人工智能实验室(CSAIL)发起,经过了长时间的发展和迭代,功能非常稳定,是许多经典数据集的“御用”标注工具。
    2. 安装简单: 作为一个纯Python应用,它可以通过pip轻松安装,避免了复杂的环境依赖问题。
    3. 多功能: 虽然我们主要用它画矩形框,但它本身支持多边形、圆形、线条等多种标注形式,为未来可能的复杂任务(如语义分割)提供了扩展性。
    4. 格式转换方便: 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>: 边界框的归一化宽度和高度。

归一化意味着所有坐标值都除以了图像的原始宽度或高度。我们的自动化脚本将处理所有这些复杂的计算。

【核心概念:两步走策略】

我们的自动化流程将分为清晰的两步:

  1. 数据整理 (Python): 使用Python脚本,将所有分散的瑕疵图片和.json文件合并到一个统一的临时文件夹中,同时完成重命名和JSON内部路径的修正。
  2. 格式转换与划分 (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模型的推理,让我们的应用真正变得“智能”。

http://www.xdnf.cn/news/16549.html

相关文章:

  • RTSP协议详解与C++实现实例
  • 津发科技带你了解皮肤电信号中的SCL与SCR
  • 深度解读|美创科技参编国家标准《数字水印技术实现指南》
  • windows 获取 APK 文件的包名和启动 Activity 名称
  • Kafka——Kafka控制器
  • 深入浅出设计模式——创建型模式之建造者模式 Builder
  • pnpm 入门与实践指南
  • ZKmall开源商城架构工具链:Docker、k8s 部署与管理技巧
  • [leetcode] 实现 Trie (前缀树)
  • 暑期算法训练.10
  • 【智能协同云图库】智能协同云图库第八弹:基于阿里云百炼大模型—实现 AI 扩图功能
  • 1 RAG三问
  • 云端文档管理新纪元:Paperless-ngx与cpolar打造的无边界文件生态
  • GO 开发环境安装及配置
  • 【21】C# 窗体应用WinForm ——图片框PictureBox属性、方法、实例应用
  • 【C++算法】80.BFS解决FloodFill算法_岛屿数量
  • 符号计算与算法实践|使用Maple教授​​群论​​和​​图论​​课程
  • 20250729使用WPS打开xlsx格式的电子表格时候隐藏显示fx的编辑栏的方法
  • 【数据可视化-74】电信用户流失数据可视化分析:Python + Pyecharts 炫酷大屏(含完整的数据,代码)
  • 如何在Linux系统下进行C语言程序的编写和debug测试
  • 建筑兔零基础python自学记录114|正则表达式(1)-18
  • 15-C语言:第15~16天笔记
  • JSON解析
  • 力扣刷题(第一百零二天)
  • BitMart 启动中文品牌“币市”:引领加密资产本地化发展新篇章
  • 闪测影像测量软件见证工业美学中的精密制造-VisionX轮廓度评价
  • Node.js 内置模块
  • 【Mac版】Linux 入门命令行快捷键+联想记忆
  • Qt 移动应用界面设计原则
  • 2025北京师范大学数学分析考研试题