做机器视觉这行的朋友,应该没少被“缺陷检测”折腾过。工件表面的划痕、麻点、脏污,靠人工肉眼盯着产线看,累不说,漏检率还下不来。我去年在做一个金属零件出厂质检项目时,就遇到了同样的困境:客户要求检测节拍要快,缺陷种类又杂,现场环境光照还不稳定。后来我直接把方案换成了YOLOv8,配合PyQt5做了个可视化检测界面,整套流程从数据标注到训练、再到界面部署,跑通之后效果立竿见影。这篇文章就把我完整的实操过程分享出来,包括环境配置、数据集制作、训练调参、界面开发,以及一路踩过的坑,希望能给正在做类似项目的朋友省点时间。
1. 项目整体设计与思路拆解
1.1 核心需求解析
工业零件表面缺陷检测,场景上有几个鲜明的特点。第一,缺陷种类多但每类样本稀少,比如划痕可能成百上千个样本,而凹坑、裂纹可能只有几十个;第二,缺陷尺寸通常很小,甚至只有几十个像素,属于典型的小目标检测;第三,产线对推理速度有硬性要求,单张图片的处理时间往往要控制在几十毫秒以内。这些特性决定了YOLOv8这类单阶段目标检测器非常合适。
那为什么是YOLOv8而不是Faster R-CNN或者SSD?主要原因是它在速度和精度的平衡上做得最稳。Faster R-CNN精度高但速度慢,SSD速度快但小目标召回率一般,而YOLOv8在COCO数据集上的mAP和推理速度综合表现都很好,尤其是对小目标的检测能力比前代YOLOv5提升了一个档次。再加上ultralytics官方把训练、验证、导出代码封装得非常干净,基本开箱即用,不用自己去拼一堆脚本。
这个系统的完整链路是:先准备并标注缺陷数据集,然后训练YOLOv8目标检测模型,最后把训练好的权重接进PyQt5界面,实现图片、视频、摄像头实时检测。界面部分负责交互,模型部分负责核心推理,两者通过一个轻量级的推理线程衔接,互不干扰。
1.2 技术选型为什么是YOLOv8 + PyQt5
我先说说选型时的思考过程。模型这块,YOLOv8在2023年初发布以后,社区活跃度非常高,模型结构上引入了C2f模块和Anchor-Free检测头,收敛速度和精度都优于YOLOv5。而且ultralytics提供了丰富的预训练权重,从n、s、m、l到x五个尺寸,可以根据显卡性能灵活选择。对于工业部署场景,我一般用yolov8n或yolov8s,在GTX 1660 Ti这种中端卡上也能跑出不错的帧率。
界面这块,PyQt5的优势在于:跨平台、控件丰富、开发效率高,而且和OpenCV、NumPy的配合非常顺滑。比如用cv2读到的图像是BGR格式的numpy数组,转成QImage再显示到QLabel上,代码就几行。你要是换了其他的GUI框架,比如Tkinter,做图像显示和缩放就会很别扭;用C++ Qt的话,开发周期又太长,不适合快速迭代。
1.3 系统的功能模块划分
整个系统从功能上可以拆成四个模块:数据模块、训练模块、推理模块和界面模块。数据模块负责标注格式转换、数据划分和增强;训练模块封装了YOLOv8的训练流程,支持自定义参数和权重导出;推理模块加载模型权重,处理输入图像并输出检测结果;界面模块负责用户交互,包括加载模型、选择检测源、显示结果、保存日志等。
这四个模块的依赖关系是单向的:数据模块不依赖其他模块,训练模块依赖数据模块的输出,推理模块加载训练模块产出的权重,界面模块调用推理模块的接口。这样分层的好处是每个模块可以独立测试和替换,比如你以后想换ResNet或者ViT做特征提取,只需要改推理模块内部实现,界面完全不用动。
2. 环境配置:先把坑趟平
2.1 一套能直接用的大环境清单
环境配置是这个项目最烦人的环节,没有之一。我前后折腾过三台机器,总结出一套相对稳妥的配置组合:
- 操作系统:Windows 10/11 或 Ubuntu 20.04/22.04,我实测两者都能跑通
- Python版本:3.8~3.10,注意不要用3.11以上的版本,有些依赖包还没完全适配
- CUDA:11.8,对应cuDNN 8.6
- PyTorch:2.0.1或2.1.0
- ultralytics:8.0.x以上版本,推荐8.1.0
- PyQt5:5.15.9或5.15.10
- OpenCV-Python:4.8.1.78
- 其他常用库:numpy>=1.23,pandas,matplotlib,tqdm
我建议先把CUDA和PyTorch的匹配关系理清楚。具体操作是先去NVIDIA官网下载CUDA 11.8安装包,装完后在命令行输入nvidia-smi确认显卡驱动识别正常,然后用pip安装PyTorch:
pip install torch==2.0.1+cu118 torchvision==0.15.2+cu118 --extra-index-url https://download.pytorch.org/whl/cu118装完之后跑一句Python验证CUDA是否可用:
import torch print(torch.cuda.is_available()) print(torch.cuda.get_device_name(0))如果输出True和你的显卡型号,就说明PyTorch的GPU版本装对了。这里有个常见的坑:如果你直接用pip install torch,默认装的是CPU版本,训练速度会慢得让人怀疑人生。别问我怎么知道的。
2.2 PyQt5安装的几道坎
PyQt5的pip安装本身很顺利,pip install PyQt5一行搞定,但真正折磨人的是装完以后界面显示不出来。我遇到过两种情况,第一种是程序启动后界面闪一下就不见了,第二种是直接报OpenGL相关的错误。
这里必须先解释一个底层原因:PyQt5的高版本默认依赖OpenGL,如果你的电脑显卡驱动装得不对,或者使用的是远程桌面、虚拟机环境,OpenGL上下文创建就会失败,界面自然就黑屏或者崩掉了。我当时的排查思路是:先升级显卡驱动,再安装OpenGL相关的依赖库:
pip install PyOpenGL PyOpenGL_accelerate如果还是不行,有一个从社区学来的workaround,在创建QApplication之前设置环境变量:
import os os.environ["QT_OPENGL"] = "software"这样会强制Qt使用软件渲染模式,虽然渲染速度慢一些,但能解决90%的界面无显示问题。我当时在远程桌面环境里就是靠这一行代码把界面救回来的。
2.3 版本兼容性的血泪教训
版本兼容性是环境配置里最容易翻车的环节。我这里列一张表,是我踩过坑以后整理出来的:
| 组件 | 推荐版本 | 避坑提醒 |
|---|---|---|
| Python | 3.10 | 3.11以上某些依赖没有wheel包 |
| PyTorch | 2.0.1+cu118 | 不要装2.2以上,ultralytics有些钩子API会变 |
| ultralytics | 8.1.0 | 8.2之后部分参数名有调整 |
| PyQt5 | 5.15.10 | 5.15.7之前opengl问题更严重 |
| opencv-python | 4.8.1.78 | 不要装contrib版,体积大且容易冲突 |
| numpy | 1.24或1.26 | 不要用2.x,opencv和ultralytics都会报错 |
特别是numpy这个坑,我有一次装新环境时顺手装了numpy 2.0,结果ultralytics直接报错,说找不到np.float之类的属性,回退到1.26才恢复正常。所以如果你也是从零开始配环境,最好严格按照上表来,别用pip的时候图省事,给关键库加个版本限制。
3. 数据集制作:训练效果的分水岭
3.1 工业缺陷数据从哪来
很多朋友问过我:工业零件表面缺陷的数据集到底去哪找?说实话,工业场景的数据集公开的非常少,因为每家的零件形态、缺陷类型都不一样。如果你做的是轴承、齿轮、电机这类标准零件,可以去搜一下CWRU轴承数据集、德国KITTI相关的工业视觉数据集,但更常见的情况是你得自己攒数据。
我当时的做法是:从客户现场拿一批真实拍摄的零件表面图,数量大概在500张左右,然后每张图做多种数据增强,旋转、平移、翻转、亮度扰动,一层一层扩充到3000多张,再手工标注。这里面有一个原则必须遵守:增强操作不能引入虚假缺陷。比如你不能做随机的椒盐噪声增强,否则模型会把噪声当成真实缺陷学进去,导致误检率高得离谱。
3.2 用LabelImg标注并转为YOLO格式
标注工具我推荐LabelImg,轻量、稳定、启动快。安装方式很简单:
pip install labelImg打开后设置YOLO格式的标注模式,用矩形框把缺陷区域框起来,每张图标注完会生成一个同名.txt文件。YOLO格式的标注内容是:类别id、中心点x坐标、中心点y坐标、框宽、框高,五个数字都用归一化来表示,取值范围在0到1之间。
这里有一个很关键的细节:YOLO的归一化坐标是用像素坐标除以图片宽高得到的,如果是小数也不碍事,训练时模型自己会处理。但如果你用的是Pascal VOC格式的XML或者COCO格式的JSON,需要先做一个格式转换,别直接把LabelImg的VOC标注喂给ultralytics,会直接报错。
转换脚本我贴在下面,可以参考:
import os import xml.etree.ElementTree as ET def voc_to_yolo(xml_file, out_file, class_names): tree = ET.parse(xml_file) root = tree.getroot() img_w = int(root.find('size').find('width').text) img_h = int(root.find('size').find('height').text) with open(out_file, 'w') as f: for obj in root.iter('object'): cls_name = obj.find('name').text if cls_name not in class_names: continue cls_id = class_names.index(cls_name) bbox = obj.find('bndbox') x1 = float(bbox.find('xmin').text) y1 = float(bbox.find('ymin').text) x2 = float(bbox.find('xmax').text) y2 = float(bbox.find('ymax').text) # 转YOLO格式,顺便做边界裁剪 x_center = ((x1 + x2) / 2) / img_w y_center = ((y1 + y2) / 2) / img_h w = (x2 - x1) / img_w h = (y2 - y1) / img_h f.write(f"{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}\n")3.3 数据划分与类别平衡
数据做好以后,需要按比例划分成训练集、验证集和测试集。我的习惯是7:2:1,训练集用来拟合参数,验证集用来监控过拟合和调参,测试集只做最终评估。划分时注意两点:一是按文件夹把图片和标签一起划分,别拆散;二是尽量保证每个类别在每个集合里都有一定数量的样本,避免验证集里某个类别一个样本都没有,指标失真。
类别不平衡是工业缺陷检测里非常头疼的问题。我当时的项目里有三个缺陷类别:划痕、麻点、脏污。划痕样本占了60%,麻点只有15%。如果不做任何处理,模型最终会对麻点几乎无感,漏检率很高。解决办法有两个:一是用image augmentation做在线增强,给少数类加大增强概率;二是在损失函数里调整类别权重。ultralytics里没有直接暴露类别权重参数,但可以直接修改数据集yaml文件,把少数类样本复制几份再做训练,算是一种简单的过采样策略。
4. YOLOv8模型训练与调优
4.1 数据配置与训练启动
训练前需要准备两个关键文件:一个是数据集配置文件data.yaml,一个是模型配置文件。data.yaml的内容长这样:
path: D:/projects/defect_yolov8 train: images/train val: images/val test: images/test nc: 3 names: ['scratch', 'pit', 'stain']这里path是数据集根目录,train/val/test填的是相对路径,ultralytics会自动拼接。nc是类别数,names是类别名字列表。重点提醒:names的顺序必须和标注时LabelImg里设置的类别顺序一致,否则类别对应关系会全乱。
训练命令很简单:
yolo detect train data=data.yaml model=yolov8n.pt epochs=200 imgsz=640 batch=8 lr0=0.01 patience=30我解释一下这几个参数的含义。model=yolov8n.pt是加载预训练权重,这比从头训练快得多,因为模型在COCO上已经学会了通用的特征表达。imgsz=640是输入图片尺寸,虽然可以用512或768,但工业缺陷往往是小目标,我实测640比512的mAP高不少。batch=8是受限于显卡显存的,如果你是8GB显存就老老实实用8,硬调大会爆显存。
4.2 训练过程中的监控与判断
训练跑起来以后,ultralytics会实时输出每个epoch的metrics,包括box_loss、cls_loss、dfl_loss、precision、recall和mAP50等。这里很多人只看mAP,其实我更习惯看loss曲线的下降趋势。正常情况下,train loss和val loss应该是同步下降的;如果train loss持续下降但val loss开始回升,就说明过拟合了,就该考虑加正则化或者缩小模型。
找最好的权重文件是一个很多人都忽略的细节。ultralytics训练结束后会在runs/detect/train目录下生成多个pt文件,其中best.pt是验证集上指标最好的权重,last.pt是最后一个epoch的权重。我自己倾向于用best.pt而不是last.pt,因为last往往已经过拟合或者训练过头了。
训练结束以后,可以用下面这段代码做一次验证测试:
from ultralytics import YOLO model = YOLO("runs/detect/train/weights/best.pt") metrics = model.val(data="data.yaml", split="test") print(metrics.box.map50) print(metrics.box.map) # mAP50-95如果mAP50能达到85%以上,对于工业质检场景一般够用了。注意工业场景对漏检的容忍度非常低,你宁可将精度降一些,也要把recall提上去,尽量让所有的缺陷都被找出来。
4.3 损失函数曲线图的绘制
看训练趋势更直观的方法是画loss曲线。ultralytics训练完会在results.png里自动生成各类曲线图,但我还是习惯自己读训练日志再画一次,因为可以统一坐标轴,方便多轮实验对比。
实现思路是:训练日志会记录每个epoch的loss值,把文本日志解析成DataFrame,再用matplotlib画曲线。核心代码如下:
import matplotlib.pyplot as plt import pandas as pd # 从runs/detect/train目录下的results.csv读取 df = pd.read_csv("runs/detect/train/results.csv") epochs = df["epoch"] train_box_loss = df["train/box_loss"] val_box_loss = df["val/box_loss"] plt.figure(figsize=(10, 6)) plt.plot(epochs, train_box_loss, label="train box_loss") plt.plot(epochs, val_box_loss, label="val box_loss") plt.xlabel("epoch") plt.ylabel("loss") plt.legend() plt.grid(True) plt.savefig("loss_curve.png", dpi=300)从loss曲线里能看出很多问题。比如val loss在epoch 80左右出现明显回升,说明该early stopping了;比如train和val loss的差距一直很大,说明模型过拟合;如果两条曲线都在高位震荡不下降,那大概率是学习率设置不对或者数据本身有问题。
4.4 模型评估与导出
训练完以后,我习惯先跑一遍测试集,看一下每张图的实际检测效果。可以用下面这个命令可视化预测结果:
yolo detect predict model=best.pt source=test_images save=True conf=0.25这里conf=0.25是置信度阈值,低于这个分数的不显示。工业场景下我会把阈值适当调高到0.4,宁可漏掉一些轻微缺陷也要保证检出来的都是真缺陷,否则现场误触发报警比漏检还烦人。
如果测试效果可以,接着就是模型导出,导出为ONNX以便后续做TensorRT加速部署:
yolo export model=best.pt format=onnx opset=12导出后可以用onnxruntime做推理验证,确认输出格式正确再继续做界面集成。我之前在这个环节踩过一个坑:导出ONNX时默认opset=12在TensorRT8.6里有些算子不支持,建议改成opset=11或13比较稳。
5. PyQt5界面开发与推理集成
5.1 界面功能规划
PyQt5界面是整个系统面向用户的部分,所以交互逻辑要尽量简单清晰。我设计的界面分成四个区域:左上角是模型加载和参数设置区,右上角是检测结果显示区,下方是操作按钮和日志信息区,侧边是检测历史列表。
功能上至少需要这些:
- 加载模型权重文件(.pt或.onnx)
- 选择检测源:单张图片、视频文件、摄像头实时画面
- 显示检测结果,在原图上绘制边界框和类别标签
- 一键保存检测结果图片或导出检测报告
- 实时显示帧率和检测耗时
我建议用QThread把推理放到后台线程,避免界面卡死。实际开发中很多新手直接在主线程里跑YOLO推理,导致界面拖动都卡成了幻灯片,记得一定要用线程。
5.2 推理线程与信号槽传递
QThread的标准用法是写一个继承自QThread的类,在run方法里做推理循环,用pyqtSignal把结果传回主线程。我这里贴一个最核心的推理线程模板:
from PyQt5.QtCore import QThread, pyqtSignal import cv2 import numpy as np class InferThread(QThread): result_ready = pyqtSignal(dict) def __init__(self, model, source="camera", parent=None): super().__init__(parent) self.model = model self.source = source self.running = True def run(self): if self.source == "camera": cap = cv2.VideoCapture(0) while self.running: ret, frame = cap.read() if not ret: break results = self.model(frame) # 把检测结果打包成dict发回主线程 boxes = results[0].boxes.xyxy.cpu().numpy() confs = results[0].boxes.conf.cpu().numpy() cls_ids = results[0].boxes.cls.cpu().numpy().astype(int) self.result_ready.emit({ "frame": frame, "boxes": boxes, "confs": confs, "cls_ids": cls_ids }) cap.release() else: # 读取视频帧或单张图片,逻辑类似 pass主线程里只需要负责显示结果,用cv2和QImage做一个颜色空间转换再setPixmap上就行。色彩转换这里有个常见的坑:OpenCV读图是BGR顺序,如果不转换成RGB,显示出来的图片颜色会偏蓝偏暗。正确做法是用cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)转一下再塞给QImage。
5.3 界面与检测结果的联动展示
检测结果有一个很重要的细节:图像缩放。工业零件相机拍出来的图往往很大,一个像素尺寸可能超过2000x2000,但界面显示区域有限。如果你直接把原图缩小显示,检测框的坐标和字体大小也会跟着缩,最后字小到根本看不清。我的做法是固定显示区域尺寸,比如640x480,然后在绘制结果时按显示区域的缩放比例同步缩放框的坐标和文字大小。
绘制代码大致是:
def draw_boxes_on_frame(frame, boxes, confs, cls_ids, names, show_size=(640, 480)): h, w = frame.shape[:2] scale_x = show_size[0] / w scale_y = show_size[1] / h dst = cv2.resize(frame, show_size) for box, conf, cls_id in zip(boxes, confs, cls_ids): x1, y1, x2, y2 = box # 坐标按显示区域比例缩放 x1s, y1s = int(x1 * scale_x), int(y1 * scale_y) x2s, y2s = int(x2 * scale_x), int(y2 * scale_y) color = (0, 0, 255) cv2.rectangle(dst, (x1s, y1s), (x2s, y2s), color, 2) label = f"{names[cls_id]}: {conf:.2f}" cv2.putText(dst, label, (x1s, max(0, y1s - 5)), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 1) return dst如果想把标注过的图片保存下来,记得按原图尺寸重新绘制一遍,不要在缩放的图上保存,否则导出的图片质量太差,没法做成检测报告。
5.4 PyQt5界面适配分辨率的问题
界面做出来以后,在1920x1080的屏幕上看着挺好,换到1366x768的笔记本上按钮都挤到一起了。这是因为我没有考虑DPI缩放的问题。解决方法是设置Qt的全局缩放策略,在创建QApplication之前加上:
from PyQt5.QtCore import Qt, QCoreApplication QCoreApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True) QCoreApplication.setAttribute(Qt.AA_UseHighDpiPixmaps, True)然后在QVBoxLayout和QHBoxLayout里尽量使用strech参数,让控件能根据窗口大小自动拉伸,而不是写死坐标。尤其是结果展示的那块,用setMinimumSize和setScaledContents配合,可以让QLabel里的图片适应窗口缩放了,但要注意锁住宽高比,否则图片会被拉伸变形。
6. 部署实践与性能优化
6.1 模型加速:从PyTorch到TensorRT
训练出的best.pt是PyTorch格式,在实际质检产线上推理速度可能不够快。以GTX 1660 Ti为例,yolov8s处理一张640x640的图大概需要25毫秒左右,在部分高节拍产线上已经有点吃紧了。这时就需要导出TensorRT模型,利用FP16量化把推理速度提升一倍以上。
导出流程是:先用前文提到的方式转ONNX,然后用TensorRT的trtexec工具转engine:
trtexec --onnx=best.onnx --saveEngine=best.engine --fp16 --workspace=4096转换完成后,在界面推理线程里加载engine文件替换PyTorch模型即可。有一个地方值得注意:TensorRT推理时输入输出张量需要自己处理,不像ultralytics那样一行代码搞定。如果不想造轮子,可以用ultralytics自带的trt支持,在YOLO类里直接model.predict(frame),但需要确保装对TensorRT版本。
6.2 边缘设备部署:RK3588上的落地经验
除了工控机,很多客户会要求在边缘计算设备上部署,比如正点原子的RK3588开发板。RK3588的NPU对常见的检测模型支持还不错,但需要把模型转成RKNN格式,过程比TensorRT要曲折一些。
先要在PC端安装rknn-toolkit2,然后用Caffe、ONNX或PyTorch格式转成RKNN。这里一个关键的注意点是:RKNN转换不支持所有的YOLOv8算子,需要在转换时开启量化感知训练,或者在模型结构里避免一些不太好转换的动态算子。如果你完全跟着官方文档走,大概率会卡在某个op的报错上,我的建议是先用yolov8n转一次,确认链路通了再去换大模型,别一上来就转最佳的精度配置,排查时间会翻倍。
6.3 界面推理视频流时的性能瓶颈
做视频流或摄像头实时检测时,推理线程和显示线程是两个环节,瓶颈不一定在模型本身。我当时排查过一个很典型的问题:模型帧率能到40 FPS,但界面上显示的只有10 FPS。后来发现原因是主线程用了QTimer定时刷新整个QLabel,产生了不必要的重复绘制。
解决办法是只在新结果到达时触发刷新,不要固定频率刷新。另外,视频流解码和推理之间可以做一个带队列的生产者-消费者模型,设置队列最大长度,满了就丢弃最旧的帧,这样能保证推理永远处理的是最新的画面,而不是堆积的旧帧。这个思路对实时产线检测特别重要,因为检测结果一旦有延迟,现场抓拍和报警就会整体滞后。
7. 常见问题与排查技巧实录
7.1 PyQt5界面无显示或黑屏
这个我在前面环境配置里提到过,再做一个完整的排查流程。第一步先看是不是OpenGL上下文创建失败:运行程序时如果命令行输出QOpenGLContext相关的错误,就说明和显卡驱动有关,把显卡驱动升级到最新版。第二步设置QT_OPENGL=software,强制用软件渲染。第三步如果还是黑屏,检查是不是装了多个PyQt5版本,在pip list里看一下PyQt5和PyQt5-Qt5的版本号是否匹配,不一致的话卸载重装。
7.2 训练时loss不下降或为nan
loss为nan是训练最恶心的问题之一。常见原因有三个:一是学习率设得太大,初始lr超过0.01容易导致梯度爆炸,降到0.001试一下;二是数据标签里出现了越界的检测框坐标,比如x_center大于1,ultralytics会尝试归一化,但也会产生无效梯度,用脚本检查一下标注文件里的坐标范围;三是batch内出现了空标签,也就是某张图没有缺陷标注,而yaml里又配置了必须检测,这时候把数据检查一遍,确认每张训练图的txt文件都至少有一个标注框。
7.3 检测精度差,误检漏检多
如果训练完的mAP50不高或者现场误检多,第一步不是改模型,而是去看数据。我遇到最多的情况是不同批次拍摄的图像光照不一致,导致同一缺陷在不同图片里看起来差别很大。解决办法是在数据增强里加亮度、对比度扰动,模拟不同光照环境;另一个方向是做图像预处理,统一色温,比如在推理前做CLAHE直方图均衡化,可以显著减少光照差异带来的误检。
7.4 推理速度慢
推理慢要分三段排查:输入图像的缩放、模型推理本身、结果后处理。第一段检查是不是直接把原始大图送进模型了,YOLOv8会自动缩放,但如果原图长宽比极端,会浪费大量算力在填充区域,可以自己先裁剪一下。第二段把模型转成TensorRT,参照6.1节。第三段如果用的是PyTorch模型,后处理里有很多numpy操作,可以尽量用yolov8自带的predict封装,它内部做了很多优化,比自己手写处理要快。
7.5 标注报错或格式错乱
标注文件里最常见的问题是类别ID越界和坐标越界。比如data.yaml里nc=3,标注时却写了一个类别ID 3,训练会直接报错。我的建议是训练前先写一个脚本扫描所有标注文件,检查每个txt里的类别ID是否在合法范围内,以及坐标值是否都在0~1之间。还有一个容易忽视的细节:如果图片是黑白图,LabelImg在保存时可能输出不完全一样的标签格式,需要统一处理成RGB三通道,否则训练到一半会报通道数不匹配。
我在这次工业零件缺陷检测系统的开发中,最深的一个体会是:整个项目里最重要的不是模型选型,也不是界面美化,而是数据本身。YOLOv8这个模型足够强大,数据质量差的话,换什么模型都救不回来。做工业视觉项目,前期花在数据清洗、标注规范、光照统一上的时间,往往比模型训练多好几倍,但这也恰恰是项目能不能真正落地到产线的关键。
最后再分享一个我实践中的小技巧:在PyQt5界面上可以加一个实时置信度滑条和类别过滤的复选框,现场调试设备时经常需要临时调一下阈值或者只看某一类缺陷,有这个功能就不用一边改代码一边重启程序了。这种做法在客户现场演示和验收时非常加分,整个系统的实用性和灵活性一下就体现出来了。