ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

YOLOv5钢材表面缺陷检测实战:数据集构建、权重微调与Qt界面部署全流程

YOLOv5钢材表面缺陷检测实战:数据集构建、权重微调与Qt界面部署全流程 简介面向深度学习视觉检测学习者和工业质检开发者这是一套可直接运行的YOLOv5钢材表面缺陷检测完整工程。模型已训练完成内置多种缺陷类型的识别权重附带训练过程PR曲线与loss曲线并配套经LabelImg标注的钢材缺陷数据集图片为jpg格式XML与TXT两种标签分别存放便于直接接入训练或验证流程。PyQt5图形界面支持图片、视频及摄像头实时检测可快速体验模型效果。资源共180个文件涵盖Python源码、UI文件、pt权重、yaml配置、XML标注及演示视频等压缩包约141.66MB目录结构清晰适合毕业设计、课程项目或工业视觉入门参考。目前已有1755人学习下载具有较高参考价值。1. 把 YOLOv5 钢材缺陷检测跑起来权重、数据集和 Qt 界面这条线一次讲透做过工业视觉的人应该都有同感模型在公开数据集上 mAP 再漂亮落到现场总差点意思。钢材表面的麻点、夹杂、划痕这类缺陷样本少、形态碎、光照还总变真正能用的模型往往不是从零训出来的而是基于一份靠谱的预训练权重配合贴合产线的数据微调出来的。这篇文章要拆的这份资源正好把 YOLOv5 钢材缺陷检测的完整闭环都打包了能直接跑的缺陷检测权重、带标注的钢材表面缺陷数据集、封装好的 Qt 图形界面。你不用再去 GitHub 上拼凑三四个仓库、再为数据集格式浪费一星期。接下来我会从数据组织讲到训练参数再讲到界面集成和部署踩坑全程按我实际跑通的经验来写每一步你都能照着复现。2. 从数据集到标签文件钢材缺陷检测的起步不是模型而是数据2.1 数据集的来源与目录组织拿到压缩包后先干什么这份资源里的数据集是典型的工业检测场景数据拍的是钢材表面在轧制过程中出现的缺陷。常见类型包括麻点rolled-in scale、划痕scratches、夹杂inclusion、斑块patches这几类和 NEU-DET 数据集的类别划分比较接近但这份资源里自带的是已经按 YOLOv5 需要的格式排布好的版本不是原始图片加 XML 的裸数据。你解压后大概率会看到这样的目录结构steel_defect_dataset/ ├── images/ │ ├── train/ │ │ ├── 00001.jpg │ │ ├── 00002.jpg │ │ └── ... │ └── val/ │ ├── 00051.jpg │ └── ... ├── labels/ │ ├── train/ │ │ ├── 00001.txt │ │ ├── 00002.txt │ │ └── ... │ └── val/ │ └── 00051.txt ├── data.yaml ├── classes.txt └── README.md第一次拿到压缩包不要急着解压完就训练。先检查一件事labels/train/里每个 txt 文件名是否和images/train/里的 jpg 一一对应。我见过太多数据集在打包传输过程中丢文件的情况缺一个标签文件YOLOv5 训练时会直接跳过对应图片而且不报错你根本不知道数据少了。对应检查用一段简单的 Python 就能做import os img_dir steel_defect_dataset/images/train label_dir steel_defect_dataset/labels/train imgs {os.path.splitext(f)[0] for f in os.listdir(img_dir) if f.endswith(.jpg)} labels {os.path.splitext(f)[0] for f in os.listdir(label_dir) if f.endswith(.txt)} missing_label imgs - labels missing_img labels - imgs print(f缺失标签的图片数量: {len(missing_label)}) print(f缺失图片的标签数量: {len(missing_img)}) for name in list(missing_label)[:10]: print(f 缺少标签: {name}.jpg)这段代码用集合差集找两边不对应的文件名。YOLOv5 的数据加载逻辑是按 basename 去匹配 image 和 label 的所以只要文件名对不上这张图就等于白放。检查完这个再打开data.yaml看一眼类别配置。2.2 data.yaml 的配置逻辑类别数错了训练就是白忙YOLOv5 的训练入口train.py会读取data.yaml里面定义了数据集路径、类别数量和类别名。这份资源里自带的 data.yaml 大概长这样train: steel_defect_dataset/images/train val: steel_defect_dataset/images/val nc: 4 names: [rolled-in_scale, scratches, inclusion, patches]这里最关键的参数是ncnumber of classes必须和names列表的长度一致而且顺序不能乱。YOLOv5 的标签文件里存储的是类别的索引号不是名字。如果你把names的顺序换了或者nc写成了 5训练出来的模型在推理时就会把类别对应错画出来的框标签全是乱的。我一般会把train和val路径写成相对于你当前终端工作目录的相对路径或者干脆写绝对路径。因为 YOLOv5 在解析 data.yaml 时是直接基于你传入的路径字符串去找图片的如果你把 yaml 文件放在数据集目录里而终端在别处路径解析就会出问题。一个典型坑Windows 上路径分隔符是反斜杠\YAML 里需要转义或者统一用正斜杠/我统一用正斜杠省事。检查完了数据集结构再看标签内容的格式是否正确。YOLOv5 的标签格式是class_id x_center y_center width height其中x_center, y_center, width, height全部是相对于图片宽高归一化到 0~1 的值。我遇到过数据集里混入了几张 VOC 格式左上角 xyxy的标签文件训练时不报错但损失函数直接崩掉或者 mAP 异常低。2.3 VOC 格式转 YOLO 格式我常用的转换脚本模板如果这份数据集的原始标注是 XML 格式你需要先转成 YOLO 的 txt。这里给一个我常用的转换脚本你只需要修改class_mapping和输入输出路径import xml.etree.ElementTree as ET import os class_mapping { rolled-in_scale: 0, scratches: 1, inclusion: 2, patches: 3 } def convert_voc_to_yolo(xml_path, output_dir, img_width, img_height): tree ET.parse(xml_path) root tree.getroot() # 读取图片实际尺寸防止 XML 里的标注超出边界 img_width int(root.find(size/width).text) img_height int(root.find(size/height).text) lines [] for obj in root.iter(object): name obj.find(name).text if name not in class_mapping: print(f跳过未知类别: {name} in {xml_path}) continue bndbox obj.find(bndbox) x_min int(bndbox.find(xmin).text) y_min int(bndbox.find(ymin).text) x_max int(bndbox.find(xmax).text) y_max int(bndbox.find(ymax).text) # 坐标归一化注意防止越界 x_center ((x_min x_max) / 2) / img_width y_center ((y_min y_max) / 2) / img_height width (x_max - x_min) / img_width height (y_max - y_min) / img_height # 裁剪到 0-1 范围避免训练时越界 x_center min(max(x_center, 0.0), 1.0) y_center min(max(y_center, 0.0), 1.0) width min(max(width, 0.0), 1.0) height min(max(height, 0.0), 1.0) line f{class_mapping[name]} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f} lines.append(line) if lines: base_name os.path.splitext(os.path.basename(xml_path))[0] with open(os.path.join(output_dir, f{base_name}.txt), w) as f: f.write(\n.join(lines) \n) # 使用示例 xml_folder Annotations yolo_folder labels os.makedirs(yolo_folder, exist_okTrue) for xml_file in os.listdir(xml_folder): if xml_file.endswith(.xml): convert_voc_to_yolo( os.path.join(xml_folder, xml_file), yolo_folder, img_width640, img_height640 )转换脚本里需要注意的细节width和height是归一化后的相对值如果你把像素宽高代入作为中心点坐标的一部分训练时就全错了。另一个细节是class_mapping的索引不要从 1 开始YOLO 的类别索引必须从 0 开始否则模型输出和标签对不上。数据这关过了后面训练才有的放矢。3. 训练配置与权重选型从预训练权重到模型收敛的三层递进3.1 预训练权重怎么选COCO 预训练和从零训练差距有多大这份资源里带了一份已经训练好的钢材缺陷检测权重但如果你是打算在自己的数据上微调选对预训练权重比调节超参数更省时间。YOLOv5 官方提供的yolov5s.pt、yolov5m.pt是在 COCO 数据集上预训练的COCO 有 80 类其中包含了一些和钢材表面纹理近似的物体类别比如金属容器表面因此它的底层特征提取器对纹理、边缘、光照变化已经有不错的泛化能力。在钢材表面缺陷这种小目标且纹理细密的场景下直接加载 COCO 预训练权重做微调通常比从零训练快 3~5 倍收敛。一份训练命令模板如下python train.py \ --data steel_defect_dataset/data.yaml \ --weights yolov5s.pt \ --img 640 \ --batch-size 16 \ --epochs 100 \ --device 0这里的--weights yolov5s.pt会自动下载 COCO 预训练权重到weights/目录。注意--img 640表示输入分辨率钢材表面缺陷这种小目标场景我用 640 起步如果你显存足够比如 24G 的 3090/4090可以试--img 1280小目标的 mAP 通常会明显上涨但训练时间也会成倍增加。batch-size的设置要考虑显存上限我一般先设 16 试跑一个 epoch观察显存占用再往上调。还有一条训练指令里容易被忽略的--cache参数python train.py \ --data steel_defect_dataset/data.yaml \ --weights yolov5s.pt \ --img 640 \ --batch-size 16 \ --epochs 100 \ --cache ram \ --device 0加--cache ram会把图片预加载进内存省去每个 epoch 重复读磁盘的时间。对于钢材缺陷这种几千张图片规模的数据集效果非常明显。如果内存不足用--cache disk它会生成一个.cache文件存归一化后的标签省内存但也省不了多少时间我优先用 ram。3.2 超参数文件 hyp.scratch-low.yaml 的三个关键参数YOLOv5 训练时的超参数不是写死在代码里的而是通过--hyp指向一个 yaml 文件。默认的hyp.scratch-low.yaml里有三项对钢材缺陷检测影响极大lr0: 0.01 # 初始学习率 lrf: 0.01 # 最终学习率因子余弦退火到 lr0*lrf momentum: 0.937 # SGD 动量 weight_decay: 0.0005 # 权重衰减 warmup_epochs: 3.0 # 预热轮数针对钢材表面缺陷数据量小、类别不平衡的特点我一般会把lr0从 0.01 改成 0.005因为数据量小时大的初始学习率容易震荡warmup_epochs保持 3.0 可以但如果你的批次里有大量背景占比大的图片我倾向把它提到 5.0让模型先稳定适应数据分布再进入正式训练。如果你是在这份资源自带的权重上继续微调注意--weights要指向那份权重路径并且--epochs不需要太多50~80 轮足够。接着还能用--freeze参数冻结前几层python train.py \ --data steel_defect_dataset/data.yaml \ --weights runs/train/exp/weights/best.pt \ --img 640 \ --batch-size 16 \ --epochs 50 \ --freeze 10 \ --device 0--freeze 10表示冻结前 10 层这些层学到了 COCO 数据的通用纹理特征在钢材表面上同样适用只微调后面的检测头。这样不仅省显存收敛速度也快这是资源自带权重微调时的推荐做法。3.3 损失函数和训练日志怎么判断模型是真收敛还是过拟合YOLOv5 训练时会输出几项关键指标你需要盯的不是 mAP 一个数而是box_loss、obj_loss、cls_loss三条曲线的走势。在runs/train/exp/目录下有results.png我每次都先看它一眼就能分辨出问题如果val_box_loss下降后在某个 epoch 开始回升但训练集损失还在降说明过拟合了需要加数据增强或者增大weight_decay。如果obj_loss一直不降说明模型学不会区分前景背景这时候不是调参能解决的大概率是标签坐标有严重问题比如框的中心点全在图片边缘外。如果mAP_0.5和mAP_0.5:0.95都稳步上升且最后差距不大说明模型学到了稳定的特征表达这时候可以放心用best.pt。钢材表面缺陷的实例往往只占图片很小一块区域所以obj_loss的权重比cls_loss更重要。如果你发现检测出的框老是偏大或偏小直接调节每个尺度特征图的 anchor 大小。YOLOv5 会在--img 640训练时自动从数据里重新计算 anchor用--noautoanchor可以禁止这个行为。我一般会先让它自动算一次然后查看runs/train/exp/anchors.png看 anchor 和数据集中真实框的尺寸分布是否贴合如果偏差大再用--noautoanchor手动改model.yaml里的 anchor。训练完成后weights/best.pt是你需要保留的。这份资源自带的训练权重实际上也是在类似流程下得到的结果你可以对照自己的训练结果来验证它的合理性。4. Qt 界面集成与模型推理别让算法死在封装这一步4.1 界面整体架构加载权重、选图片、看结果三板斧这份资源的 Qt 界面本质上是把推理流程包装成了一个可视化的工具方便非算法人员使用。它解决的痛点很具体现场调试的人不会敲命令行也无法从一堆终端日志里寻找检测结果。所以界面的核心就三个区域模型加载区、图片选择区、结果展示区。我在自己项目里复刻过类似界面其工作流程是这样的用户点击「加载模型」→ 选择 best.pt 权重文件 → 模型加载到内存 用户点击「选择图片」→ 弹出文件对话框 → 选一张钢材表面图 界面自动执行推理 → 把检测框画到图片上 → 左侧显示原图右侧显示标注后的图 界面下方输出检测类别、置信度、位置坐标Qt 界面调用 YOLOv5 推理最有争议的点是用 PyTorch 直接推理还是用 ONNX。直接用 PyTorch 加载.pt权重好处是省去模型转换环节坏处是启动慢、依赖环境复杂。用 ONNX Runtime推理速度可以提升 20%~40%但需要额外做一次权重导出。我建议界面走PyTorch 加载因为这是资源自带的方式不额外增加转换复杂度而且钢材缺陷检测不是实时视频流场景单张图片推理 200ms 的延迟完全不影响使用。界面里和模型推理相关的核心代码大概是这样的import os import torch from PyQt5.QtWidgets import QFileDialog, QLabel, QPushButton, QVBoxLayout, QWidget from PyQt5.QtGui import QPixmap, QImage import cv2 import numpy as np # 加载模型在界面初始化时完成 model torch.hub.load(ultralytics/yolov5, custom, pathweights/best.pt, force_reloadFalse) def on_select_image(self): # 弹出文件选择框 file_path, _ QFileDialog.getOpenFileName( self, 选择图片, , Image files (*.jpg *.jpeg *.png *.bmp) ) if not file_path: return # 读取图片并推理 img cv2.imread(file_path) img_rgb cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # 推理results 对象里包含 boxes、labels、confidences results model(img_rgb, size640) # 获取标注后的图像 annotated_img results.render()[0] # 返回的是 RGB 格式的 ndarray # 显示结果 self.display_result(annotated_img) # 输出检测详情 detections results.pandas().xyxy[0] for _, row in detections.iterrows(): print(f类别: {row[name]}, 置信度: {row[confidence]:.2f}, f坐标: ({row[xmin]:.0f}, {row[ymin]:.0f}, f{row[xmax]:.0f}, {row[ymax]:.0f}))这里有几个注意点model(img_rgb, size640)的size参数要和训练时的--img保持一致。训练时用的 640推理时也传 640否则模型输入的尺度变化会影响检测精度。另外results.render()[0]返回的已经是画好框的 RGB 图像可以直接转成 QPixmap 在 QLabel 上显示。如果直接拿 OpenCV 读到的 BGR 图去显示颜色会偏蓝。图片显示这块有个小坑QLabel 默认是按内容大小自动调整的但如果你设置的固定尺寸比原图小QLabel 不会自动缩放图片需要手动把 QPixmap 缩放到 QLabel 的尺寸。我习惯在显示前统一做一次缩放def display_result(self, img_rgb, label_widget: QLabel): h, w, _ img_rgb.shape pixmap QPixmap.fromImage( QImage(img_rgb.data, w, h, 3 * w, QImage.Format_RGB888) ) # 缩放适应界面显示区域 pixmap pixmap.scaled( label_widget.width(), label_widget.height(), Qt.KeepAspectRatio, Qt.SmoothTransformation ) label_widget.setPixmap(pixmap)QImage的构造函数里有一个很容易出错的地方bytesPerLine参数必须传3 * wRGB 三通道每通道一个字节。如果漏了这个参数写成了默认值图片显示出来会是一张斜线撕裂的图。4.2 推理细节置信度阈值和 NMS 参数不是默认值就最好YOLOv5 在推理时有两个关键后处理参数置信度阈值conf_thres和 IoU 阈值iou_thres。默认值分别是 0.25 和 0.45。但在钢材表面缺陷场景里我通常会把置信度阈值调到0.3~0.4因为钢材表面的划痕和背景纹理在视觉上太接近置信度过低会输出大量误检框。调参在 Qt 界面上可以直接通过参数传给模型results model(img_rgb, size640, conf_thres0.35, iou_thres0.5)iou_thres控制 NMS 时两个框重叠到什么程度算同一个目标。钢材表面的麻点经常是一簇一簇出现的如果iou_thres太高比如 0.7重叠的检测框不容易被合并会输出一堆冗余框调到 0.45~0.5 更合适。一个容易忽略的细节是max_det参数默认值是 300。在钢材表面缺陷这种一图多目标的场景中如果一张图上有几十处缺陷而max_det设置过小后面的框会被直接截断。如果发现检测出的框数量始终不够检查一下这个参数。Qt 界面代码里如果没暴露这个参数可以在调用模型时显式传入results model(img_rgb, size640, conf_thres0.35, iou_thres0.5, max_det500)把这三个参数做成界面上的可调节控件比如 QSpinBox 或者 QSlider实时调参的效果远好于改代码重新跑。我那份项目里就是把 conf_thres 做成了滑块现场调试时非常方便。4.3 导出 ONNX 的必要性和操作步骤如果后续做的是工业产线部署Qt 界面只是原型验证最终很可能要用 C 重写推理端。这时候 ONNX 导出就成了必经之路。C 环境下用 ONNX Runtime 加载模型不需要安装 PyTorch这在工控机上意义重大——很多工控机根本没有 NVIDIA 显卡和 CUDA 环境。导出 ONNX 的命令很简单python export.py \ --weights weights/best.pt \ --img 640 \ --batch 1 \ --include onnx \ --simplify加--simplify会用 onnx-simplifier 优化计算图去掉一些冗余算子这对推理速度提升有帮助。如果你打算用一种叫 OpenVINO 的推理引擎在 Intel 平台上部署还可以加--include openvino直接导出 IR 格式。不过对于 Qt 界面演示项目PyTorch 直接推理已经够用ONNX 导出属于锦上添花。导出的 ONNX 模型可以用官方的 ONNX Runtime 做一次验证import onnxruntime as ort import cv2 import numpy as np sess ort.InferenceSession(weights/best.onnx) input_name sess.get_inputs()[0].name # 输入预处理resize 到 640x640像素归一化到 0-1并转成 NCHW img cv2.imread(test.jpg) img cv2.resize(img, (640, 640)) img img[:, :, ::-1] # BGR - RGB img img.astype(np.float32) / 255.0 img np.transpose(img, (2, 0, 1)) img np.expand_dims(img, axis0) outputs sess.run(None, {input_name: img})注意 PyTorch 模型导出 ONNX 后输入张量的通道顺序是 NCHW和你训练时一致。这里的预处理多了np.expand_dims增加 batch 维度以及np.transpose把 HWC 转成 CHW。漏掉任何一步推理结果都会是一堆奇怪的框。5. 常见问题与避坑记录从数据到界面最容易翻车的四个典型场景以下四个问题是复用这份资源时高频出现的每一条都是真实发生过的现场事故按现象到原因到解决来写直接对照排查即可。问题一训练时 loss 为 nan现象训练刚开始几个 epochbox_loss和obj_loss全部变成nan终端日志刷红模型权重直接废掉。原因最常见的是标签文件里有坐标越界或出现负数。比如x_center是 1.2宽度是 0.8超出了归一化范围。YOLOv5 训练时对这类标签不会主动过滤而是直接丢进损失计算产生梯度爆炸。解决训练前批量扫描标签文件把每个 txt 的每一行拆出来检查是否都在 0~1 区间内。检查脚本核心逻辑def validate_labels(label_dir): for txt_file in os.listdir(label_dir): with open(os.path.join(label_dir, txt_file), r) as f: for line_num, line in enumerate(f.readlines()): parts line.strip().split() if len(parts) ! 5: print(f异常行: {txt_file} 第{line_num1}行) continue cls_id, x_c, y_c, w, h parts vals [float(x_c), float(y_c), float(w), float(h)] if not all(0 v 1 for v in vals): print(f越界: {txt_file} 第{line_num1}行: {line.strip()})问题二Qt 界面点「加载模型」后卡死现象界面点击加载权重后鼠标变成转圈状态整个窗口无响应几分钟后才恢复或者直接崩溃。原因torch.hub.load在第一次加载模型时会花大量时间做初始化包括读取权重文件、构建模型结构、检查算子是否可用。如果你的权重路径写的是相对路径而当前工作目录不对它还会尝试联网下载这一联网卡住就是几分钟。解决绝对路径加载权重 在加载前设置好环境变量import os os.environ[TORCH_HOME] os.path.join(os.path.dirname(__file__), .torch_cache) # 在后台线程加载模型主线程保持界面响应 from PyQt5.QtCore import QThread class LoadModelThread(QThread): def __init__(self, weight_path): super().__init__() self.weight_path weight_path def run(self): self.model torch.hub.load( ultralytics/yolov5, custom, pathself.weight_path, force_reloadFalse, # 改成 False避免每次都要重新下载 verboseFalse )模型加载放进QThread后台线程后界面就不会卡死了。还有个细节force_reload一定要设成 False否则每次点加载都会重新从 GitHub 拉代码。问题三推理出的检测框全部偏在图片左上角现象不管输入什么图片检测框都集中在左上角而且框的位置和真实缺陷位置完全不匹配置信度还挺高。原因典型的输入预处理错误。Qt 界面里用cv2.imread读进来的是 BGR 格式但模型训练时用的是 RGB或者你调用了model(img, size640)但传进去的图片数组不是np.ndarray而是 QPixmap 转出来的格式。YOLOv5 不会报错但会用到错误的像素值做推理。解决在界面推理代码里统一走一遍转换流程img cv2.imread(file_path) img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # 训练时是 RGB results model(img, size640)如果用了results.render()[0]拿到的结果图是 RGB不要再用cv2.imwrite直接存它会存成 BGR 导致颜色怪异正确做法是再转回 BGR 再存。问题四同一张图、同一个权重检测结果和命令行跑不一致现象命令行推理某个图片能检出 5 个缺陷框Qt 界面只检出 2 个甚至完全检不到。原因命令行推理时默认输入尺寸是 640而界面代码里可能漏传了size导致模型使用了默认的 320 或 480。YOLOv5 的model(img)如果不传 size默认是 640但如果界面代码里写的是model(img, size480)结果自然不同。还有可能在界面里误用了conf_thres的默认值。解决把所有推理参数显式写清楚results model( img, size640, # 和训练时保持一致 conf_thres0.30, # 固定阈值不随界面滑块变化 iou_thres0.45, verboseFalse # 关掉终端日志避免界面卡顿 )6. 从单张图片到批量检测用 os 遍历目录做产线预演资源和界面的搭配已经能跑通单张图片检测了但实际在产线验证时不只是一张图而是一个批次的图片文件。最后做一个批量检测小工具把已经训练好的权重用在几十张图片上同时统计检测精度和耗时这是验证这份权重到底能不能落地的最快方式。核心逻辑是遍历文件夹下所有图片逐张推理并保存标注结果import os import time import cv2 import torch import pandas as pd model torch.hub.load(ultralytics/yolov5, custom, pathweights/best.pt, force_reloadFalse) img_folder test_images output_folder test_results os.makedirs(output_folder, exist_okTrue) all_detections [] total_time 0.0 for img_name in os.listdir(img_folder): if not img_name.lower().endswith((.jpg, .jpeg, .png)): continue img_path os.path.join(img_folder, img_name) img cv2.imread(img_path) img_rgb cv2.cvtColor(img, cv2.COLOR_BGR2RGB) start time.time() results model(img_rgb, size640, conf_thres0.30, iou_thres0.45) elapsed time.time() - start total_time elapsed # 保存标注后的图片 annotated results.render()[0] cv2.imwrite(os.path.join(output_folder, img_name), cv2.cvtColor(annotated, cv2.COLOR_RGB2BGR)) # 收集检测结果 df results.pandas().xyxy[0] for _, row in df.iterrows(): all_detections.append({ image: img_name, class: row[name], confidence: round(row[confidence], 4), xmin: int(row[xmin]), ymin: int(row[ymin]), xmax: int(row[xmax]), ymax: int(row[ymax]) }) print(f{img_name}: {len(df)} 个缺陷, 耗时 {elapsed:.2f}s) # 导出检测明细 det_df pd.DataFrame(all_detections) det_df.to_csv(os.path.join(output_folder, detections.csv), indexFalse) print(f平均推理耗时: {total_time / len(os.listdir(img_folder)):.2f}s) print(f检测总数: {len(det_df)})这段代码里我把results.render()[0]的输出又做了一次 BGR 转换因为render()返回的是 RGB直接cv2.imwrite会导致保存的图片颜色偏蓝。pandas().xyxy[0]返回的 DataFrame 提供了便捷的逐行访问方式适合导出 CSV 做后续分析。批量检测跑完之后你手上有四样东西标注好的图片、检测明细 CSV、模型权重、界面程序。这套东西能支撑你完成一次完整的产线预演挑 50 张真实产线采集的图片跑一遍统计检出率和误检率发现阈值不合适就回到 Qt 界面调滑块重新验证。从那以后我每次拿到新的工业检测模型都不会直接信训练日志里的 mAP而是强制走一遍这个批量验证流程用真实场景的检出率说话。实践下来的感受是数据格式规范和阈值标定这两件事比换模型结构更能决定项目成败。希望这份实操拆解能帮你把 YOLOv5 钢材缺陷检测这条路走顺少踩几个我已经踩过的坑。本文还有配套的精品资源点击获取
返回列表