金属表面缺陷检测这件事,真正落地时最麻烦的不是模型选型,而是把检测能力装进一个操作人员愿意用的桌面工具里。用 YOLOv8 或 YOLOv5 做目标检测,再用 PySide6 写界面,是目前比较常见的组合:模型负责从图像里找到划痕、麻点、氧化、锈斑这些缺陷,PySide6 负责把模型封装成导入图片、点击检测、查看结果的软件。这篇文章就围绕这套方案,从环境准备、数据标注、模型训练、推理接口、界面集成到批量处理和问题排查,按实际落地顺序拆一遍。
看完之后你可以得到一个基本判断:这套系统适合谁、需要准备什么数据、单张检测怎么跑通、批量检测要注意什么、界面卡住或漏检时先查哪里。如果你是刚接触深度学习目标检测,或者已经在用 YOLO 但想把模型接到桌面程序里,下面的内容应该能省去不少试错时间。
1. 先确认需求:缺陷检测不是“装个模型就行”
1.1 工业场景里的缺陷检测到底在检测什么
工业金属表面的缺陷种类很多,常见的包括划痕、凹坑、麻点、锈斑、氧化色差、油污、压伤、边缘缺损等。这些缺陷的共同点是:形状不一定规则、对比度不稳定、位置随机,有的缺陷甚至只靠单像素级别的小变化才能看出来。
传统视觉算法处理这种问题会非常吃力。固定阈值、模板匹配、边缘检测这类方法,适合背景稳定、打光稳定、缺陷形态固定的场景。但金属表面本身有纹理、反光和材料差异,同一个缺陷在不同角度、不同光照下拍出来可能差别很大。深度学习目标检测在这里的优势是:模型学习的是“缺陷区域的特征分布”,而不是一个固定规则。只要数据标注足够,模型能自己找到哪些视觉模式需要关注。
所以,如果你面对的是多品种、多形态、表面纹理复杂的金属件,用目标检测模型做缺陷定位是合理的。不要一上来就幻想模型能解决所有问题。它解决的是“在图像里找到可疑缺陷的位置和类别”,至于这条缺陷算不算废品、要不要停机、要不要做二次复判,还需要人工或后续规则去处理。
1.2 为什么选 YOLOv8 / YOLOv5 而不是直接上传统算法
YOLO 系列是当前工业项目里用得最多的目标检测算法之一。YOLOv5 成熟稳定,资料多,很多早期项目都在用;YOLOv8 在检测头、训练策略上有调整,训练流程更统一,也支持实例分割、姿态估计等任务。缺陷检测大多数时候只需要回归出缺陷框和类别,所以 YOLOv5 和 YOLOv8 都是合适的选择。
和 Halcon 深度学习工具这类商业方案相比,YOLO 路线的优势是可控性强,模型文件相对轻量,Python 生态完整,方便对接桌面程序和后续二次开发。Halcon 有很强的视觉算法库和工业现场积累,但授权成本和集成方式不一定适合所有团队。我见过不少从 Halcon 转过来的项目,图像预处理和采集端仍用 Halcon,模型训练和推理换到 YOLO,再通过 PySide6 做操作界面,这套混搭在中小型项目里很常见。
选择 YOLOv8 还是 YOLOv5,可以先看两件事:第一,团队以前有没有用过对应仓库;第二,部署环境支不支持推理框架。YOLOv5 的代码结构在早期项目里遗留很多,社区问答好找;YOLOv8 的 API 更简洁,训练和导出一条命令能完成。对新手来说,YOLOv8 的体验通常更顺,但最终还是要以你自己的数据和测试结果为准。
1.3 缺陷检测系统的最小闭环
一个最小的金属表面缺陷检测系统,至少要包含四部分:
- 图像采集或输入:可以是相机拍照、文件夹导入、拖拽单张图片。
- 推理服务:加载训练好的 YOLO 模型,对输入图像做预处理、前向推理、后处理,输出缺陷位置、类别、置信度。
- 结果展示:在界面上绘制检测框,显示类别名称和置信度,方便人工确认。
- 结果保存:保存检测后的图像、统计数据、日志,便于追溯。
很多人做系统时会把重点放在模型训练上,结果模型准确率很高,但到了现场发现:图片导入不方便、检测结果看不出是哪根金属件、保存的报告格式客户不认可。这些都是因为缺少产品化设计。用 PySide6 做桌面端,核心目的就是把模型推理能力包装成一个普通操作员能用的工具。
2. 系统架构:模型、界面、任务链路怎么拆
2.1 一个桌面缺陷检测系统的模块划分
把功能拆成三个模块会更清晰:
- 数据模块:负责读图、缩放、格式转换、路径管理。
- 推理模块:负责加载模型、执行检测、解析结果。
- 界面模块:负责按钮、画布、表格、日志、进度条。
这三个模块尽量别混在一起。界面直接调用推理函数虽然简单,但一旦模型推理耗时较长,界面会卡住,表现为“点按钮没反应”“窗口变成白色”。正确做法是把推理放到工作线程里,界面主线程只负责接收结果并更新显示。
从工程上看,可以用一个Detector类封装推理,用QThread或QThreadPool跑任务,界面通过信号接收结果。这样单张检测和批量检测可以复用同一套推理逻辑,只是任务组织方式不同。
2.2 从点击“检测”到显示结果,内部发生了什么
如果只是单张图片检测,链路并不复杂:
- 用户点击“打开图片”,选择一张金属表面图片。
- 界面把图片路径发送给检测线程。
- 检测线程读取图片,转成模型需要的 RGB 格式和尺寸。
- 模型执行推理,输出预测框、类别索引、置信度。
- 后处理过滤掉置信度过低的框,必要时做非极大值抑制合并重叠框。
- 结果通过信号传回界面,界面在图片上绘制矩形框和标签。
- 用户点击“保存结果”,程序把标注后的图片写出到指定目录。
这个过程中最容易出问题的是第三步和第五步。图片通道顺序不对、尺寸缩放方式不一致、置信度阈值设置过高或过低,都会导致结果异常。我一般会先把原始图片和检测结果临时保存出来,确认输入输出都对了,再继续做界面优化。
2.3 训练端和检测端要不要放在同一个进程里
不要放在同一个进程。训练需要占用大量 GPU 显存,如果界面还开着,模型训练中间经常出现显存不足或界面无响应。更推荐的做法是分离:
- 训练阶段用命令行或独立脚本跑,不打开 PySide6 界面。
- 训练完成后导出权重文件,基于权重文件再启动检测系统。
- 如果确实需要在线训练或增量训练,也建议把训练放到后台服务,界面只接收训练日志和状态。
这样做的另一个好处是,现场操作员接触的软件不包含训练代码,误操作概率会低很多。模型训练是研发阶段的事,检测才是产线阶段的事。
3. 环境准备与模型选型:先把最小样例跑起来
3.1 Python 环境和依赖怎么装
搭建环境时,建议先创建一个 Python 虚拟环境,避免把系统 Python 弄乱。PySide6 是 Qt6 的 Python 绑定,和旧版 PySide2、PyQt5 的 API 有一些差别,安装时不要混装。
一般需要安装的核心依赖包括:
- PySide6
- ultralytics 或针对 YOLOv5 的 requirements
- opencv-python
- numpy
- Pillow
安装命令类似:
pip install pyside6 opencv-python numpy pillow pip install ultralytics如果网络速度比较慢,可以用国内镜像源,比如-i https://pypi.tuna.tsinghua.edu.cn/simple。注意,安装完 ultralytics 后,第一次运行可能会下载预训练权重,这需要网络。离线环境下需要提前准备好权重文件。
3.2 YOLOv8 和 YOLOv5 到底怎么选
从实际使用角度看,两者内核思想一致,都是把目标检测任务转换成回归问题,在图片上划分网格并预测边界框。区别主要在工程细节、训练策略和 API 组织上。
YOLOv5 的优点:
- 早期版本多,许多老项目有现成代码,搜索引擎能找到大量踩坑记录。
- 部署到边缘设备时,社区针对 YOLOv5 的转换资料更多。
- 如果团队已经有一套 YOLOv5 训练和标注流程,迁移成本最低。
YOLOv8 的优点:
- 训练代码更简洁,默认配置对新手更友好。
- 支持更多任务类型,实例分割、姿态估计这些扩展容易。
- 导出 ONNX、TensorRT 等格式的接口更统一。
选型建议:如果做的是新项目、团队没有历史包袱,优先考虑 YOLOv8;如果项目需要和老代码兼容,或者控制板卡上只提供 YOLOv5 的示例工程,就选 YOLOv5。不要因为别人说某个版本“更准”就盲目切换,最后还是要用你自己的标注数据跑一轮对比,看 mAP 和实际漏检情况。
3.3 数据集要多少,标注要怎么做
缺陷检测的数据量没有绝对标准。常见起步经验是:每类缺陷至少准备 300 到 500 张带标注图片。如果缺陷形态差异很大,比如划痕有粗有细、有长有短,那就要多收集不同形态的样本,而不是只追求总数。
标注格式推荐 YOLO 的 txt 格式,每行内容为:
类别索引 center_x center_y width height坐标值和宽高都按图片宽高归一化到 0 到 1 之间。LabelImg、X-AnyLabeling 这类工具都可以输出 YOLO 格式。标注时要注意:缺陷边界如果不明显,宁可框得稍微紧一些,也不要包含太多背景;类别定义要清晰,划痕和裂纹如果不做区分,就别分成两个类别,否则模型会被反复出现的标注不一致搞晕。
数据清洗也很重要。模糊图片、过暗图片、重复图片要剔除。工业现场拍摄的图片如果带有大量反光或遮挡,应该保留并在训练时通过数据增强增强模型鲁棒性,但不要让低质量样本占太大比例。
4. 模型训练与推理接口:从权重文件到可调用函数
4.1 训练参数怎么设:轮数、批次、图像尺寸
训练轮数、批次大小和输入图片尺寸,是三个最常被问的参数。
epochs:训练轮数。不是越大越好,要看验证集损失是否还在下降。一般先设 100 轮,观察曲线,如果 50 轮后验证损失不再下降,后续轮数基本没有帮助。batch-size:批次大小。显存越大可以设越大。常见 8、16、32 都有人用。如果训练时显存不够,可以降到 4 或 2,但训练时间会变长。imgsz:输入图片尺寸。YOLOv8 默认常用 640。图片分辨率不高或者目标很小,可以试 960 或 1280,但显存和速度会明显上升。不要盲目把尺寸拉满,先确认硬件的显存水平。
训练命令用 ultralytics 的写法很简单:
yolo train data=data.yaml model=yolov8n.pt epochs=100 imgsz=640 batch=16训练结束后,会在runs/detect/train目录下生成权重文件best.pt和last.pt。判断训练效果不能只看训练集损失,还要看results.png里的验证损失、mAP、精确率和召回率曲线。如果 mAP 很高但实际测试漏检,通常是训练数据和现场数据分布不一致,而不是参数没调好。
4.2 模型导出:PT、ONNX、TensorRT 怎么选
训练好的best.pt可以直接用 Python 推理,但很多桌面系统或边缘设备不适合直接跑 PyTorch,这时候需要导出成其他格式。标题里提到“训练模型后如何导出便于 Qt 调用”,其实 PySide6 不需要特殊格式,用best.pt就能调用,前提是环境里有 PyTorch 和 ultralytics。但如果你想减少依赖、提高速度,可以导出 ONNX 后用 ONNX Runtime 推理。
用 ultralytics 导出 ONNX:
from ultralytics import YOLO model = YOLO("best.pt") model.export(format="onnx", imgsz=640)导出完会生成best.onnx。后续推理可以使用 ONNX Runtime 加载,不再依赖 PyTorch。这样做的好处是部署环境更轻,坏处是如果更换模型结构或任务类型,需要重新导出。
如果部署到 NVIDIA 显卡且对速度要求高,可以尝试 TensorRT 的.engine格式,但 TensorRT 版本和显卡驱动需要严格匹配,配置成本更高。我建议先跑通pt或onnx,确认检测效果满足要求后,再考虑要不要做 TensorRT 加速。
4.3 推理接口怎么封装更合适
无论是单张检测还是批量检测,封装一个统一的检测器类会让代码干净很多。伪代码如下:
class MetalDefectDetector: def __init__(self, model_path, conf_threshold=0.5): self.model = YOLO(model_path) self.conf_threshold = conf_threshold def detect(self, image_path): results = self.model.predict(image_path, conf=self.conf_threshold) detections = [] for r in results: boxes = r.boxes.xyxy.cpu().numpy() classes = r.boxes.cls.cpu().numpy() scores = r.boxes.conf.cpu().numpy() for box, cls, score in zip(boxes, classes, scores): detections.append({ "bbox": box.tolist(), "class_id": int(cls), "confidence": float(score) }) return detections这里没有把界面逻辑放进去,是因为检测器只负责输出结构化结果,画框和显示是界面层的事。这样组件之间可以独立测试,后续做接口服务或命令行走批也方便。
5. PySide6 界面集成与批量处理:从单图到产线
5.1 界面布局怎么设计合理
一个缺陷检测桌面工具,界面布局不需要花哨,关键是要让操作员一眼看清当前状态。我常用的是三块区域:
- 左侧:图片显示区,显示原始图像和检测结果。
- 右侧上方:控制区域,包括打开图片、开始检测、选择模型、设置置信度阈值、批量处理按钮。
- 右侧下方:结果列表和日志,列出每次检测的缺陷类别、置信度、坐标,并记录日志。
使用 PySide6 时,可以用QLabel显示图片,用QTableWidget显示结果列表,用QPlainTextEdit显示日志。注意图片显示不要直接加载原图尺寸,要按显示区域缩放,否则大图会把界面撑爆。
5.2 单图检测按钮的线程处理
新手最容易犯的问题是:在按钮回调里直接跑模型推理。我理解为什么这样写,因为代码最短。但一旦推理耗时超过两秒,窗口就会进入“未响应”状态,用户会以为程序崩了。
正确做法是把检测任务丢到QThread中。PySide6 里用信号把结果传回主线程:
class DetectWorker(QThread): finished = Signal(object) def __init__(self, detector, image_path): super().__init__() self.detector = detector self.image_path = image_path def run(self): detections = self.detector.detect(self.image_path) self.finished.emit(detections)主界面连接finished信号后,再更新结果。这样界面不会卡住,进度条也能正常显示。如果是实时检测摄像头输入,则需要考虑持续读取视频帧和模型推理的并发问题,不要让推理阻塞视频采集。
5.3 批量图像处理:进度、命名、失败重试
批量处理是工业场景里最常见的需求。用户希望一次性丢进几十张甚至几百张图片,程序自动检测并输出结果。批量处理不能只是简单循环调用单张检测,至少要考虑三件事:
- 进度显示:用
QProgressBar显示已处理数量和总数,并允许取消任务。 - 输出命名:每张图片输出结果后,保存路径不能覆盖原图。建议输出目录按 “原文件名_defect.jpg” 命名,同时生成 CSV 汇总。
- 失败重试:单张图片读取失败或检测异常时,要记录日志并继续处理下一张,而不是整个程序退出。
批量检测的安全做法是:先处理 3 到 5 张图,确认输出格式和命名符合预期,再放开到全量。不要一上来就开最大并发,金属缺陷检测场景的图片分辨率通常不低,并发过大会导致显存波动或者内存暴涨。
5.4 检测结果导出:图片、CSV、日志
结果导出要按需求做。最简单的方案是把画好框的图片保存到输出目录。更完整的方案是在 CSV 中记录:
filename,class,confidence,x1,y1,x2,y2 metal_001.jpg,scratch,0.87,120,30,260,95 metal_001.jpg,pit,0.76,310,180,390,220这样后续可以统计每个批次的缺陷数量、每类缺陷占比,甚至生成质量报表。如果客户需要追溯,建议同时保存一张“原始图 + 检测结果图”,而不是只保存检测图。因为后续判断模型是否漏检,需要对照原始图像。
6. 常见问题、性能边界与排查顺序
6.1 漏检和误检不一定是模型问题
很多人训练完发现测试效果不错,换一批现场图片后漏检多了,第一反应是调模型。其实先检查数据更有效。
排查顺序可以按这样来:
- 先看输入图片现场拍摄和训练集图片差多远:光照、角度、分辨率、缺陷占比。
- 再看标注文件是否标准:有些标注工具导出的坐标原点在左上角还是右下角,会影响结果。
- 再看置信度阈值:阈值设 0.5 或 0.3 差别很大,可以先降低阈值看是不是缺陷实际被检出来了,只是置信度不够高而被过滤。
- 再看类别定义:如果两个缺陷类别在视觉上相似,模型会分不清,此时可以合并类别或收集更多区分性样本。
- 最后才调整模型或训练参数。
很多情况下,漏检不是模型能力不行,而是“现场数据分布”和“训练数据分布”不一致。解决办法是补充现场样本做二次训练,而不是无限加正则化或改网络结构。
6.2 PySide6 界面卡死或无响应,先查什么
界面卡死,最直接的原因是主线程里跑了耗时操作。排查顺序:
- 检查按钮槽函数里有没有调用模型预测、大文件读图、循环写日志。
- 如果有,把这些操作移到
QThread或QThreadPool中。 - 检查信号槽连接是否跨线程正确,结果对象是否使用
Signal传递,而不是直接用界面控件在线程里更新。 - 检查批量处理时有没有每张图都做完整的前后处理,是否可以对图片先压缩后再显示。
如果程序启动后直接崩溃,优先看控制台报错。常见原因是缺少依赖、模型路径错误、图片格式不支持。先看日志,再改代码,不要盲猜。
6.3 低配置机器能跑吗,批量任务怎么控制资源
低配置机器可以跑,但要看怎么定义“能跑”。如果只是偶尔检测单张图片,CPU 跑 YOLO 也是可以接受的,只是速度会比较慢。例如一张 640x640 的图片,CPU 推理可能需要 1 到 3 秒,甚至更久,具体取决于 CPU 性能、模型大小和是否使用了 ONNX 优化。
如果是批量处理几百张图片,低配置机器要注意:
- 不要同时开很多线程,推理线程数建议从 1 开始,观察 CPU 占用和单张耗时。
- 可以把输入图片先等比缩放到模型输入尺寸,减少预处理耗时。
- 批量处理阶段,不要在主界面做高分辨率原图画框显示,显示缩略图即可,把完整结果图保存到文件。
- 如果一张图片检测时间超过可接受范围,再考虑换更小的模型,比如
yolov8n.pt代替yolov8m.pt,或者导出 ONNX 后用 ONNX Runtime 的 CPU 推理。
低配能跑不代表适合生产。现场如果要求每秒处理多张图片,最好的方案还是配备 GPU 或边缘加速卡,并把批量任务做成队列,保证吞吐稳定。
6.4 训练指标和实际效果不一致时怎么办
训练日志里的mAP50高,不代表现场效果就好。因为 mAP 是综合所有类别和不同置信度阈值算出来的,而现场操作员只关心“这个缺陷有没有被框出来”。举一个常见例子:如果一张图里有大划痕,模型确实框出来了,但框的位置偏差较大,mAP 可能轻微下降;如果模型把背景里的纹理误判成缺陷,但置信度不高,经过阈值过滤后被忽略,mAP 可能不受影响,但操作员会看到误检。
所以验证模型时,不要只看指标,要多看实际图片的检测结果。把典型缺陷图、困难样本、无缺陷图三类图片分别过一遍,观察漏检、误检、框的贴合度。只有这些视觉层面满足了,指标才有参考意义。
6.5 系统再往前走:接口化、日志化和可追溯
如果只是个人学习或内部实验,PySide6 界面加本地文件存储足够了。如果要在产线试用,我建议提前规划:
- 把检测器封装成独立服务,界面通过 HTTP 或本地进程通信调用。
- 图片和检测结果写入数据库,便于后续统计和分析。
- 日志记录每次操作人员修改的阈值、复判结果,方便质量追溯。
- 模型文件按版本管理,不能直接在界面里替换,至少要有备份和回滚机制。
这些都不是必须一开始就做,但如果在开发阶段就预留了接口,后面改造会省很多事。尤其是模型更新迭代,直接换一个路径或版本号是常见需求,别把模型路径写死在代码里。
我个人的经验是:先把单张检测跑稳,再把批量处理做好,最后再考虑接口化和部署。每一步都留好日志和输出,遇到问题能快速定位是模型、数据还是界面代码的问题。金属表面缺陷检测是个典型工程问题,模型再强也替代不了流程管理,真正影响项目成败的往往是数据一致性、界面稳定性和现场操作的易用性。
这套用 YOLOv8/YOLOv5 加 PySide6 的组合,最大的价值不是某一个算法有多新,而是从“训练模型”到“桌面软件”的链路够短,适合快速验证,也适合中小团队自研落地。只要把数据、模型、界面三件事分开做好,后续扩展实时检测、实例分割、不同工位的切换,都会顺很多。