YOLOv8道路破损检测实战:962张图训练全流程指南
简介这是一份面向道路破损检测场景的 YOLO 系列目标检测数据集已完成训练集、验证集与测试集划分并内置 data.yaml 配置文件可直接适配 yolov5、yolov7、yolov8、yolov9、yolov10、yolo11 等算法训练和验证。压缩包共包含 2000 个文件整体大小约 45.39MB其中主要有 962 个 yolo 格式的 txt 标签、962 个 voc 格式的 xml 标签、75 张 jpg 图像以及 1 个 data.yaml 配置文件txt 与 xml 标签分别存放便于在主流检测框架中灵活切换。yolo 格式采用 class、x_center、y_center、width、height 的相对坐标表示xml 遵循 voc 标准结构配置文件可直接被各版本 yolo 项目读取。对从事道路破损检测的研究者或工程人员来说该数据集已划分好且无需自行标注下载后即可快速进行模型训练、精度对比与测试节省大量数据准备时间。目前已有 93 人学习下载适合作为路面裂缝、坑槽等破损检测任务的入门或基准数据集。1. 962张带标签的道路破损图为什么值得动手训练一个YOLO检测器道路破损检测的落地场景比想象中更急迫市政巡检车每天拍几千张路面图靠人工看图标注裂缝和坑槽一天下来眼睛几乎累瞎而 yolo算法 配合一份带完整标签的道路破损检测数据集是当前成本最低的自动识别路线。这份962张图像、每张都带目标框标签的压缩包规模不大但恰好能跑通一条完整链路拆包、数据体检、迁移学习训练、踩坑排错、导出部署模型。它适合三类人第一次想用YOLO做垂直场景检测的工程师、需要快速验证道路破损检测可行性的解决方案团队以及想搞清楚“小数据集到底能不能训出能用的模型”的算法同学。962张图不能保证你直接得到一个生产级模型但它足够让你把流程跑通并为后续数据扩充提供一个可迭代的基线。接下来我从拆包开始把每个环节的细节和坑逐个过一遍。2. 拆开压缩包从目录结构到标签文件先确认这片数据能不能训拿到 .zip 后最忌讳的就是解压后直接开训。先花半小时做数据体检后面训练和调参会省出至少半天。这一章把解压、标签检查、可视化预览、类别统计一次讲完。2.1 看一眼压缩包里有什么目录与关键文件先不解压用unzip -l查看压缩包内部结构unzip -l road_damage.zip | head -40输出一般会包含images/、labels/两个目录旁边可能还有一个classes.txt或data.yaml。images下是.jpg或.png原图labels下是每个图片同名的.txt标签文件。没有data.yaml不影响训练后面自己写一个就行但classes.txt值得认真看它决定了类别ID的顺序这也是训练中最容易出隐患的地方。确认目录没问题后解压unzip road_damage.zip -d ./road_damage然后检查实际目录层级find ./road_damage -maxdepth 2 -type d | sort如果发现images和labels不在同一个父目录下不用慌后面用data.yaml的path字段分别指定即可。这里的关键不是路径长什么样而是images下的文件名与labels下的文件名要一一对应IMG_1024.jpg对应的标签必须是IMG_1024.txt。文件名对不上训练时 Ultralytics 会直接报no labels found或者更隐蔽地——只训了部分图像还不报错。这种工业图像数据集的结构问题我见过太多次解压后先数一下图片数量和标签数量是否一致ls ./road_damage/images | wc -l ls ./road_damage/labels | wc -l两个数量不一致时写个小脚本找出缺失项import os images {f[:-4] for f in os.listdir(./road_damage/images) if f.endswith((.jpg, .png))} labels {f[:-4] for f in os.listdir(./road_damage/labels) if f.endswith(.txt)} print(缺少标签的图片:, images - labels) print(没有图片的标签:, labels - images)注意如果压缩包里带嵌套目录比如road_damage/道路破损/images/这种中文路径建议先改到纯英文路径。中文路径在 Windows 和某些 Linux 容器里会让基于 glob 的读取出现莫名其妙的漏读排查起来非常费时间。2.2 标签坐标的归一化规则class_id 与 xywh 要较真YOLO 格式的标签文件是纯文本每行一个目标五列class_id x_center y_center width height。前四列是归一化到 0~1 的浮点数不是像素值。拿到任何一个标签先抽样看一眼head -5 ./road_damage/labels/0001.txt期望输出类似0 0.342187 0.583750 0.126562 0.184583 2 0.810625 0.467083 0.071875 0.089583如果某一行出现大于 1 的坐标值说明标签是像素坐标或者是别的标注工具导出的 COCO 格式被直接改名成了 txt。这种情况在下载的数据里很常见处理办法是统一归一化。常见做法是用 OpenCV 读原图尺寸然后做坐标转换import cv2, glob, os labels glob.glob(./road_damage/labels/*.txt) for lab in labels: img_path os.path.join(./road_damage/images, os.path.basename(lab)[:-4] .jpg) if not os.path.exists(img_path): continue # 缺失对应图像时跳过统一排查 img cv2.imread(img_path) if img is None: continue h, w img.shape[:2] lines [] for line in open(lab): parts line.strip().split() if len(parts) ! 5: continue cls parts[0] x1, y1, x2, y2 map(float, parts[1:]) # 如果文件里存的是像素框转换为归一化 YOLO 坐标 x_c (x1 x2) / 2 / w y_c (y1 y2) / 2 / h bw (x2 - x1) / w bh (y2 - y1) / h lines.append(f{cls} {x_c:.6f} {y_c:.6f} {bw:.6f} {bh:.6f}) if lines: with open(lab, w) as f: f.write(\n.join(lines) \n)这段脚本做的事就是把像素级 x1、y1、x2、y2 改写成归一化的 x_center、y_center、width、height。注意cv2.imread读入的shape[:2]顺序是 H、W如果把h, w img.shape[:2]写反成w, h ...所有框都会横向错位。另外部分工业相机拍的是单通道灰度图img.shape只有两个值H,W而不是三个上面代码用len(img.shape)3去判断就会出错稳妥做法是先cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)统一成三通道。还有一个高发问题标签里的 class_id 超出你定义的类别范围。比如classes.txt里有 5 个类别ID 为 0~4但某张图的 txt 里出现了 5训练不会立刻报错但 loss 会异常震荡。规避办法是扫描所有标签取出 class_id 最大值与classes.txt行数对比超过就说明数据本身有问题需要回源标注或者删除异常样本。2.3 画框预览标注质量的第一道过滤器坐标格式没问题不代表标注内容没问题。裂缝漏标、框偏移、类别标反这些错误从数值上看不出来必须画框可视化。用一段脚本把标签直接画到原图上import cv2, glob, os for lab in glob.glob(./road_damage/labels/*.txt)[:30]: img_path os.path.join(./road_damage/images, os.path.basename(lab)[:-4] .jpg) img cv2.imread(img_path) if img is None: continue h, w img.shape[:2] for line in open(lab): parts line.strip().split() if len(parts) ! 5: continue cls, x_c, y_c, bw, bh map(float, parts) # 归一化坐标转回像素坐标注意 center-width-height 到左上角/右下角 x1 int((x_c - bw / 2) * w) y1 int((y_c - bh / 2) * h) x2 int((x_c bw / 2) * w) y2 int((y_c bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 0, 255), 2) cv2.putText(img, str(int(cls)), (x1, max(y1 - 5, 0)), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1) cv2.imwrite(f./preview/{os.path.basename(img_path)}, img)画完框后再花十分钟翻一遍预览图重点盯三类问题框是不是明显大于目标本身一条长裂缝是不是被一个过宽的框包住类别标错比如把修补区标成坑槽。标注质量问题在小数据集里会被放大因为模型没有足够的干净样本去“纠正”错误标注。2.4 数据摸底类别分布与目标尺度决定后续策略962 张图像属于小数据集先做一次全量统计每类多少个框、每张图平均几个目标、框的宽高比分布如何。这段统计做得好后面很多翻车都能提前规避。import glob from collections import Counter label_counter Counter() box_counter Counter() for lab in glob.glob(./road_damage/labels/*.txt): with open(lab) as f: for line in f: parts line.strip().split() if len(parts) ! 5: continue cls int(parts[0]) w, h float(parts[3]), float(parts[4]) label_counter[cls] 1 if w / h 3 or h / w 3: box_counter[长条型] 1 elif w * h 0.02: box_counter[小目标] 1 else: box_counter[普通] 1 print(各类别目标数:, label_counter) print(框形态分布:, box_counter)这段统计能直接告诉你三件事。第一类别是否均衡。如果裂缝类占 80% 以上坑槽只有几十个框训练出来的模型对坑槽几乎失效。此时要么补数据要么对少数类做重采样。老实说在 962 张的规模下重采样的上限很低最有效的路径是裁剪和旋转扩充少数类的样本而不是靠调参。第二小目标占比。道路破损里的裂缝本质是细长目标如果大量框的宽度或高度小于图像尺寸的 5%常见做法是提高输入分辨率imgsz或者在标注层面把一条裂缝切成多段框避免一个框把细长特征压扁。这个问题在第 4 章和第 5 章还会有具体对策。第三有多少空标签。如果某个 txt 是 0 字节对应图就是纯背景图。纯背景图对训练有一定价值能降低误检但数量不能太多超过 10% 会影响正负样本平衡。空标签文件不用删保留少量背景图反而有用。统计完成之后无论数据长什么样都要把结果记下来。后面如果 mAP 上不去回头对照这个基线能快速判断是数据问题还是训练参数问题——这个习惯比任何调参技巧都重要。3. 用YOLOv8跑通第一个训练周期环境、配置与完整命令这一章直接给能抄的作业从安装依赖到组织目录、写配置、跑训练一条龙。目标是让你在半小时内跑出第一个best.pt。3.1 环境安装ultralytics 依赖与入门显卡就能跑YOLOv8 的官方实现是ultralytics包pip 安装即可。我默认读者有一张 NVIDIA 显卡显存 6GB 以上没有的话用 CPU 也能跑只是慢很多。pip install ultralytics装完后顺手验证版本python -c import ultralytics; print(ultralytics.__version__)训练首选yolov8s.pt或yolov8n.pt这样的预训练权重。Ultralytics 会自动从官方地址下载如果网络环境下载困难可以手动把权重文件放到~/.config/Ultralytics/目录下。在 962 张图的规模下yolov8s是均衡选择。yolov8n虽然快但对裂缝这种细长目标的特征提取偏弱yolov8m及以上版本在数据量不足时容易过拟合不建议第一个实验就用。先用 s 跑通再根据过拟合程度决定换 n 还是 m。3.2 目录组织train、val 与 labels 的对应关系解压后的数据如果是images/和labels/平铺结构训练前要按标准方式组织好。常见的目录结构是这样的./road_damage/ ├── data.yaml ├── images/ │ ├── train/ │ │ ├── 0001.jpg │ │ └── ... │ └── val/ │ ├── 0135.jpg │ └── ... └── labels/ ├── train/ │ ├── 0001.txt │ └── ... └── val/ ├── 0135.txt └── ...如果原始数据是平铺的写个脚本按比例切分。比例用 8:2 比较合适962 张图的前提下验证集太大训练集会变得更薄import random, os, shutil random.seed(42) src_images road_damage/images src_labels road_damage/labels for split in [train, val]: os.makedirs(froad_damage/images/{split}, exist_okTrue) os.makedirs(froad_damage/labels/{split}, exist_okTrue) files [f for f in os.listdir(src_images) if f.endswith((.jpg, .png))] random.shuffle(files) split_idx int(len(files) * 0.8) for f in files[:split_idx]: shutil.move(f{src_images}/{f}, froad_damage/images/train/{f}) shutil.move(f{src_labels}/{f[:-4]}.txt, froad_damage/labels/train/{f[:-4]}.txt) for f in files[split_idx:]: shutil.move(f{src_images}/{f}, froad_damage/images/val/{f}) shutil.move(f{src_labels}/{f[:-4]}.txt, froad_damage/labels/val/{f[:-4]}.txt)这段脚本注意三点random.seed(42)保证切分结果可复现后缀判断要写全.jpg和.png切分后数一下val目录里的图片数量确保验证集有 100 张以上mAP 指标才有参考意义。如果只有 80 张验证图指标的随机波动会很大。注意这里用的是随机切分只是一种快速起步方式。真正的道路巡检数据要按路段分组切分避免同一条裂缝在不同帧里同时出现在训练集和验证集这个问题在第 4 章专门讲。3.3 写数据配置 yamlpath、names 与类别顺序data.yaml是训练的核心配置文件。以常见的四类道路破损标注为例# road_damage/data.yaml path: /home/user/road_damage train: images/train val: images/val names: 0: crack 1: pothole 2: alligator_crack 3: patchpath是数据集根目录的绝对路径train和val是相对路径。这里最容易被坑的是names顺序训练时模型输出的类别 ID 完全依赖这个列表顺序如果和classes.txt不一致所有标注都会错位。YOLOv8 的data.yaml不再需要download、temp这些旧字段保持精简即可。如果你后面想测试没有标注的图片可以加一个test字段但第一个训练周期不需要。3.4 从预训练权重开始训练epochs、batch 与 imgsz 的取舍配置文件就绪后直接跑第一个完整训练周期cd /home/user/road_damage yolo detect train \ datadata.yaml \ modelyolov8s.pt \ epochs200 \ imgsz640 \ batch16 \ patience50 \ workers4 \ seed42参数含义逐个说明。yolov8s.pt是 COCO 预训练权重迁移学习的基础epochs200对 962 张图来说偏多配合patience50的意思是连续 50 个 epoch 验证集指标没有提升就提前停止所以设 200 也不用怕跑太久imgsz640是训练输入分辨率道路破损图像通常来自车载相机原图往往是 1920×1080 甚至更大直接imgsz640会丢失大量细节第一个周期先跑通后面再提分辨率batch16在 6GB 显存左右是上限不够就降到 8 或 4并同步调低workersseed42固定随机种子保证两次训练结果可比。注意batch 设多大不只看显存还要看数据量。962 张图用 batch16 意味着每个 epoch 约 60 步200 个 epoch 就是 12000 步对于小数据集的微调来说已经接近过拟合边缘。如果训练日志里训练 loss 持续下降而验证 loss 上升说明过拟合已经发生优先缩短patience并加大数据增强。训练结束后输出目录runs/detect/train/下会有weights/best.pt和weights/last.pt。best.pt是验证集指标最好的权重后面所有推理和导出都用它。下一步用验证集跑一次评估yolo detect val datadata.yaml modelruns/detect/train/weights/best.pt这条命令会在命令行打印mAP0.5、mAP0.5:0.95、precision、recall四个核心指标。第一次训练的目标不是刷高指标而是确认流程跑通loss 收敛、mAP 数值稳定在合理区间。只要没报错说明数据、配置、训练链路已经全部打通。4. 把962张图物尽其用迁移学习、数据增强与分组切分小数据集训练的胜负手不在模型结构而在数据策略。这一章讲清楚四件事为什么必须迁移学习、如何按场景切分数据、增强参数怎么设、小目标裂缝怎么处理。4.1 迁移学习是默认选项为什么从 COCO 预训练权重起步962 张图你完全可以直接从零开始训练但结果通常会让你怀疑数据集有问题。YOLOv8 的 backbone 如果从随机初始化开始需要数百万张图才能学到稳定的底层视觉特征我们手里只有 962 张且道路破损的目标类别形态复杂、光照多变从零训练很容易在头几十个 epoch 就出现梯度消失或过拟合。用 COCO 预训练权重是更稳妥的起手式。COCO 预训练权重带来的能力是通用的边缘、纹理、角点等底层特征只有输出层需要重新学习道路破损的语义。用yolov8s.pt时Ultralytics 会自动裁剪和初始化输出层 head你要做的只是“调头不调身”。如果数据量再少到两三百张甚至应该冻结 backbone# 冻结前10层 backbone 的示例写法 from ultralytics import YOLO model YOLO(yolov8s.pt) for name, param in model.model.model[:10].named_parameters(): param.requires_grad False但这里多说一句冻结层数越多训练越快但模型对新场景的适应能力越差。对于 962 张图一般只建议在第一批训练的前 30 个 epoch 冻结 backbone等训练稳定后再全量开放。4.2 按拍摄场景分组切分避免同源泄露数据切分不能简单随机。道路破损图像通常是从一段道路连续抓帧得到的同一个裂缝可能在相邻几帧里反复出现。如果随机切分训练集和验证集里会出现大量高度相似的同源图像验证集指标被刷得很高实际部署到新路段马上现原形。这就是常见的“同源泄露”。正确的做法是看文件命名或拍摄时间把连续帧归为同一组再切分。假设文件名包含路段编号比如road_01_0042.jpg中的 01 是路段编号import glob, re, os, shutil from collections import defaultdict files glob.glob(road_damage/images/*.jpg) groups defaultdict(list) for f in files: seg re.search(rroad_(\d)_, os.path.basename(f)).group(1) groups[seg].append(f) # 按路段分组后以组为单位切分同一路段只落在一个集合里 segs sorted(groups.keys()) val_segs set(segs[-15:]) for f in files: seg re.search(rroad_(\d)_, os.path.basename(f)).group(1) if seg in val_segs: os.makedirs(road_damage/images/val, exist_okTrue) shutil.move(f, road_damage/images/val/ os.path.basename(f)) else: os.makedirs(road_damage/images/train, exist_okTrue) shutil.move(f, road_damage/images/train/ os.path.basename(f))这段脚本的核心是val_segs按路段编号取最后的一部分而不是随机选图labels 目录也要按同样的路段分组逻辑移动。在真实项目里这一步省不了因为巡检车的连续帧之间高度相关随机切分会让你误以为模型泛化能力很好。尤其是同一条裂缝从远处逐渐拉近的过程会占据很多帧这种数据天然存在时间相关性。如果路段数量太少导致按组切分后验证集只有 30 张图就改用 K-Fold 交叉验证。962 张图建议 3 折每折用 2/3 训练、1/3 验证取三折的平均 mAP 作为最终指标。注意折数越高单个模型见到的图片越少3 或者 4 是均衡值。4.3 增强策略mosaic、翻转与亮度抖动的参数962 张图的增强参数设置比模型结构更影响最终效果。Ultralytics 默认有一套增强参数但道路破损场景和 COCO 数据集差异很大需要针对性调整。常用的一组配置# hyp.yaml 片段 mosaic: 1.0 fliplr: 0.5 flipud: 0.0 hsv_h: 0.015 hsv_s: 0.7 hsv_v: 0.4 scale: 0.5 translate: 0.1 degrees: 0.0 shear: 0.0道路破损场景里flipud垂直翻转要慎重因为路面图像的方向语义较强上下翻转会让模型学到“天上掉下来的裂缝”反而干扰学习。degrees旋转也是一样路面图像基本没有大角度倾斜设 0 即可如果设 15 度以上框和背景的匹配关系会混乱。mosaic: 1.0的含义是训练时每张图由 4 张拼接而成。在小数据集上 mosaic 增强几乎必须开启它把多张图的上下文信息混合显著提升对遮挡和复杂背景的适应能力。但要注意mosaic 增强在目标非常稀疏时会产生大量无目标的拼接块所以如果你发现很多图只有 1 个框可以把 mosaic 降到 0.5 做对比实验。亮度饱和度方面hsv_v: 0.4用来模拟阳光直射和阴影交替的路面工业巡检图像的光照变化很大这个增强很重要。scale: 0.5做随机缩放能模拟车载相机远近变化。这些参数可以搜索调整但在 962 张图上跑完整的自动化搜索性价比太低不如手动做两组对比实验来得快。4.4 针对小目标裂缝增大输入分辨率与切块推理道路破损检测最核心的目标是裂缝和龟裂这两种目标都有一个共性细长且相对于整张 1920×1080 的原图非常小。imgsz640时原图被压缩到 1/3宽度只有 5 像素的裂缝在降采样后几乎消失。解决思路有两种。第一种常见做法是增大训练输入分辨率yolo detect train \ datadata.yaml \ modelruns/detect/train/weights/best.pt \ epochs120 \ imgsz1280 \ batch8 \ patience30imgsz增大对显存压力很大6GB 显存下 1280 分辨率建议 batch4。这个改动通常能让mAP0.5:0.95提升 3~5 个点因为小目标在高分辨率下保留了更多纹理信息。做图像超分辨率重建来放大裂缝在这种场景下效果并不稳定我实测过还不如直接提高训练输入分辨率。第二种做法是切块推理patched inference。把原图切成 4 个 640×640 的块每块做一次推理再把结果合并回原图坐标。好处是显存需求不变坏处是裂缝跨越切分边界时会被切成两半导致漏检。常见折中是让切分块互相重叠 30%重叠区域的检测框再用 NMS 合并。这个方案如果后续要部署到边缘设备推理时长会翻倍一般只用在离线的市政巡检分析中。对于 962 张图我更推荐先试imgsz960而不是直接 1280。小数据集下高分辨率会放大过拟合960 比 640 在细节保留上改善明显显存消耗中等是性价比最高的档位。5. 962张图训练翻车实录5个高频坑与排查路径小数据集训练最耗时的不是训练本身而是排错。这一章写五个我在类似项目里反复踩过的坑每条按“现象 → 原因 → 解决”展开你可以直接当排查手册用。5.1 现象所有目标被预测成同一个类别loss 正常但 mAP 很低训练 log 里 loss 照常下降验证集 mAP 却在 0.2 左右徘徊打开预测可视化才发现不管图上是什么破损模型都输出 crack。原因几乎都出在类别 ID 错位数据集的标签类别顺序是 crack0、pothole1、alligator_crack2、patch3但data.yaml里 names 写成了 crack0、alligator_crack1、pothole2、patch3两个顺序对不上导致模型在学习“外观-标签”映射时被强行带偏。这是标签文件与配置文件顺序不一致导致的。解决方法是写一段脚本把classes.txt和data.yaml的 names 逐行对比以classes.txt为准修正data.yaml。同时把训练输出目录里的预测结果图全部打开翻一遍确认每个类别的框都出现在对应目标上再进入下一轮调参。5.2 现象训练到一半 loss 变成 NaN之后直接中断出现 NaN 的常见原因有两个。一是训练集中有损坏的图片比如 0 字节的 jpg、截断的 pngcv2.imread读取失败后返回的数组为空前向传播里出现无效值。二是标签坐标越界归一化坐标里出现负数或大于 1 的值导致 loss 计算时对无效面积取对数。排查路径先把data.yaml里的图片全部用cv2.imread检查能否正常读取再写脚本扫描标签的边界范围python -c import glob for l in glob.glob(road_damage/labels/*.txt): for ln in open(l): parts ln.split() nums [float(x) for x in parts[1:]] if max(nums) 1.0001 or min(nums) -0.0001: print(l, ln.strip()) 如果某个标签的坐标范围超出 [0, 1]把这个文件找出修正或删除对应目标。值得强调的是x_center width / 2 1这种轻微越界在 YOLOv8 里有时不报错只会默默增加 loss 的奇怪惩罚项所以要在训练前主动扫一遍。5.3 现象验证集指标很高换到新路段照片效果一塌糊涂这是迁移学习的经典陷阱道路破损检测里几乎必然出现。原因是数据切分用了随机切分同一个裂缝的不同帧被分到了训练集和验证集验证集 mAP 看似 85%实际模型学到的是“记忆这条裂缝”而不是“识别裂缝”。解决方法是按 4.2 的方式按路段分组切分再训练。如果数据太少导致分组切分后验证集指标剧烈抖动可以改用 K-Fold 交叉验证取平均。我最常用的配置是 3 折每折 640 张训练、320 张验证最后取平均 mAP 作为模型真实能力的估计。5.4 现象模型能检出坑槽却丢光裂缝细长目标找回率低裂缝在图像上往往只有几个像素宽检测框是长条状。模型内部的特征金字塔对高度不到 20 像素的目标响应很弱尤其是裂缝与背景纹理混杂时很容易被当成噪声滤掉。解决路径分两步。第一步提高imgsz把输入分辨率从 640 提到 960 或 1280。第二步检查标注质量如果标注框把整条一米长的裂缝框成一个长条模型在降采样后根本学不到裂缝的局部纹理。更合理的做法是把长裂缝切成数段每段一个框框的宽高比尽量小于 3:1。这属于标注策略问题在数据量少时影响特别大。5.5 现象从零初始化训练与预训练微调效果差异大到不像同一个数据集这不是 bug而是小数据集的必然。用yolov8s.yaml而不是yolov8s.pt启动训练backbone 随机初始化在 962 张图上 loss 可能跌到一定程度就卡住mAP 长期在 10% 以下。很多人以为是模型结构不对其实是数据量支撑不起从零学习。我的建议是小数据集场景下永远用预训练权重并且在实验记录里写清楚“baseline 用的 yolov8s.pt、权重热启动”这一行。如果非要实验从零训练请把 epochs 拉到 500 以上并且对学习率做 warmup否则很难得到有意义的对比。6. 量化验收mAP、PR曲线与ONNX导出的最后几步训练跑完不代表活干完。这一章讲怎么验收模型、怎么导出部署格式以及我在最后几步经常复查的细节。6.1 用 val 命令跑出 mAP 与 PR 曲线训练结束后跑一次完整验收入口yolo detect val \ datadata.yaml \ modelruns/detect/train/weights/best.pt \ imgsz1280 \ save_jsonTrue加上save_jsonTrue会把检测结果和指标写入 JSON 文件便于存档对比。imgsz1280表示验证时输入分辨率与实测保持一致如果部署相机图像是 1920×1080这里别用 640否则你验证的不是最终部署的模型行为。跑完后输出目录里会出现PR_curve.png和confusion_matrix.png这两个图比单纯 mAP 数字更能说明问题。PR 曲线如果在召回率端掉得很快说明漏检集中在困难样本优先去补这些样本而不是无脑调阈值。6.2 用实际巡检图片做推理验证找一段训练集中完全没出现过的路段图片执行yolo detect predict \ modelruns/detect/train/weights/best.pt \ sourcetests/new_road/ \ imgsz1280 \ conf0.35 \ saveTrueconf0.35表示置信度阈值这个值应该由 PR 曲线来确定。曲线里 precision 和 recall 的交点附近通常是合理阈值如果业务更看重不漏报比如巡检考核就降到 0.25如果更看重少误报比如自动上报维修工单就升到 0.5。这里的小技巧是每张图的检测框同时用save_cropTrue把破损区域单独裁出来人工快速翻一遍。962 张图训出的小模型这步能帮你快速发现哪些框经常跑到树影、水渍、轮胎印上——这些误报在混淆矩阵里往往不显眼但实际落地时用户第一眼就会注意到。6.3 导出 onnx 与部署时的精度对齐验证通过后导出部署格式yolo export modelruns/detect/train/weights/best.pt formatonnx imgsz1280 opset12导出后先用 onnxruntime 做一次推理与 PyTorch 前向输出对比坐标和置信度import numpy as np import cv2, onnxruntime as ort sess ort.InferenceSession(best.onnx) img cv2.imread(test.jpg) img cv2.resize(img, (1280, 1280)) img img[:, :, ::-1].transpose(2, 0, 1)[None].astype(np.float32) / 255.0 outs sess.run(None, {sess.get_inputs()[0].name: img}) print(outs[0].shape)这里最常踩的坑是输入预处理不一致PyTorch 推理时 Ultralytics 默认做了 letterbox保持宽高比加填充而上面手动 resize 直接拉伸两者输出 shape 相同但坐标映射会偏移几像素。正确做法是部署代码里也按 letterbox 预处理。如果要对齐 TensorRT优先用 TensorRT 的 ONNX parser 直接加载 ONNX不要在模型外面再包一层预处理否则坐标偏差叠加后细长裂缝的框很容易错位。最后说一个我养成的习惯每次训练完把data.yaml、增强参数和完整命令行拷贝到实验记录里连同best.pt放在同一个目录压包存档。小数据集训练最可怕的不是效果差而是效果差时你记不清当时是怎么组合出来的想重跑一次还得从头推。每一步留痕后面调整参数才有后悔药。希望这些从拆包到部署的细节能帮你把这份道路破损检测数据集真正用起来少走一段弯路比多跑十几个 epoch 值钱。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联
返回资讯列表 →