YOLO训练马路裂缝数据集:3258张带标签图像的全流程指南
简介面向目标检测与道路病害识别场景的马路裂缝数据集适配yolov5、yolov7、yolov8、yolov9、yolov10及yolo11等主流yolo系列框架已完成训练集、验证集、测试集划分并附带可直接引用的data.yaml配置文件能有效解决道路裂缝样本收集难、标注成本高的痛点。压缩包内共2000个xml标注文件标签同时提供yolo格式与voc格式两种版本分别存放于独立文件夹整体约104.55MB其中yolo格式包含类别索引和归一化后的中心点、宽高信息便于直接送入模型迭代。目前已有187人学习下载。对于从事路面病害检测、智慧交通或相关毕业设计的研究者拿到后可快速开展训练与验证无需手动转换标签双格式标注也方便横向对比不同检测框架能够节省大量数据预处理时间加速算法效果验证。1. 用 yolo 算法跑马路裂缝数据集3258 张带标签图像的起点与边界开车跑一段国道让摄像头自动把路面裂缝标出来这个需求听着不复杂真做起来最卡人的往往不是算法选型而是手里没有一份能直接喂给 yolo 的带标签数据。像标题里这种“yolo算法-马路裂缝数据集-3258张图像带标签”的压缩包正好补上这一课3258 张真实路面图像每张都带目标框理论上可以直接用来做 yolov5/yolov8 的裂缝检测训练。但它不是解压完就能躺着出结果的“带标签”不保证标签格式统一裂缝也不是普通目标——多数裂缝只有几个像素宽、长宽比极端还爱顺着路面纹理走。这篇文章就把整个过程顺一遍数据集体检、标注格式统一、YOLOv8 训练、坑位排查和落地上限适合刚入门 yolo 的工程师也适合想快速验证道路裂缝检测可行性的团队。2. 拿到 3258 张带标签图后的第一步不是训练而是给数据集做体检2.1 为什么“带标签”不等于“能直接开训”很多新手拿到压缩包后的第一反应是解压、放目录、跑 train结果训练到一半发现 loss 不降、验证集 mAP 为零回过头排查才发现是标签坐标完全错位。带标签这三个字在不同打包者手里含义差别很大有的是 VOC 的 xml一个 xml 对应一张图有的是 COCO 的 json所有标注堆在一个文件里有的直接就是 YOLO 的 txt 归一化框。这三种格式让 yolo 吃下去的成本完全不同。另外标签里的目标类别未必只有一种裂缝。网状裂缝、横向裂缝、纵向裂缝、修补区域甚至路面其他病害都可能混在同一套标注里分类名也可能中英文混杂。所以不要急着把训练命令跑起来先用二十分钟给数据集做一次资产盘点这一步通常能拦下大半后面会翻车的训练实验。2.2 用脚本清点图片、标签与类别分布先确认压缩包的基本形态。解压后建议用 find 命令数一下文件建立“图片数 vs 标签数”的第一印象unzip yolo算法-马路裂缝数据集-3258张图像带标签.zip cd 解压后的目录 find . -maxdepth 2 -type f -name *.jpg | wc -l find . -maxdepth 2 -type f -name *.txt | wc -l find . -maxdepth 2 -type f -name *.xml | wc -l逻辑说明如果 txt 数量和 jpg 数量相同大概率是 YOLO 格式如果 xml 数量与 jpg 相同大概率是 VOC 格式如果两个都不对上就再去看看有没有 json 文件。maxdepth 限制在 2 层是为了避免把缓存目录里的临时文件也算进去。参数说明jpg 扩展名要按实际图像格式调整有些数据集是 png 或 bmp如果图片和标签分散在不同子目录先find . -type d看一下目录层级再决定 maxdepth 取值。如果确认是 YOLO txt 格式可以用一个 Python 脚本统计类别、框数和框的宽高比分布import os, glob from collections import Counter label_dir ./labels cls_count Counter() box_count 0 aspect_ratios [] for txt in glob.glob(os.path.join(label_dir, *.txt)): for line in open(txt): parts line.strip().split() if len(parts) ! 5: continue cls parts[0] w float(parts[3]) h float(parts[4]) cls_count[cls] 1 box_count 1 aspect_ratios.append(w / max(h, 1e-6)) print(总框数, box_count) print(类别分布, cls_count) print(宽高比中位数, sorted(aspect_ratios)[len(aspect_ratios) // 2])逻辑说明YOLO txt 每行是“类别 cx cy w h”其中 w 和 h 是归一化到 0~1 的相对值。统计宽高比的目的是判断裂缝框的形状普通目标长宽比接近 1而裂缝经常出现 5 倍以上甚至 20 倍的长宽比。如果中位数明显偏离 1说明这个数据集的标签形状分布很极端后面训练时 anchor 或特征金字塔的配置就要有针对性。参数说明1e-6 是防除零保护如果某个 txt 中一行少于 5 个字段说明标签文件可能有空行或格式破损应该额外记录文件名并复查。2.3 按路段划分 train/val别让同一段路出现在两端划分数据集时最容易犯的错误是纯随机切分。裂缝数据集通常来自连续拍摄同一个路段拍下来的几十张图背景高度相似甚至裂缝是同一道。如果纯随机把其中一部分分进训练集、一部分分进验证集验证集的难度会被严重低估最终得到的 mAP 是虚高的模型一换路段就现原形。我一般按文件名中的路段编号分组后再切分。假设文件名格式是“roadA_001.jpg”下划线前是路段 ID脚本可以这样写import os, random, shutil from collections import defaultdict random.seed(42) img_names os.listdir(./images) groups defaultdict(list) for name in img_names: road_id name.split(_)[0] groups[road_id].append(name) group_ids list(groups.keys()) random.shuffle(group_ids) split int(len(group_ids) * 0.8) train_groups set(group_ids[:split]) val_groups set(group_ids[split:]) os.makedirs(images/train, exist_okTrue) os.makedirs(images/val, exist_okTrue) os.makedirs(labels/train, exist_okTrue) os.makedirs(labels/val, exist_okTrue) for name in img_names: road_id name.split(_)[0] img_src os.path.join(./images, name) label_src os.path.join(./labels, name.replace(.jpg, .txt)) if road_id in train_groups: shutil.move(img_src, os.path.join(images/train, name)) shutil.move(label_src, os.path.join(labels/train, name.replace(.jpg, .txt))) else: shutil.move(img_src, os.path.join(images/val, name)) shutil.move(label_src, os.path.join(labels/val, name.replace(.jpg, .txt)))逻辑说明先按路段分组再把路段列表随机打乱而不是对单张图片打乱。这样可以保证同一个路段的所有图片只出现在训练集或验证集其中一个里。这种做法比纯随机切分更能反映模型在未知路段上的真实表现。参数说明0.8 是常见比例如果总路段数很少比如只有 10 个路段建议改成 0.7否则验证集可能只剩两三段评估波动会很大。文件名如果没有路段 ID退而求其次可以用“采集时间”或“拍摄批次”作为分组字段。3. 把 VOC/COCO 标注统一成 YOLO 格式转换脚本与裂缝目标的四个边界坑3.1 三种主流标注格式的坐标体系YOLO 训练默认吃 txt每行一个目标格式是“class cx cy w h”坐标全部归一化到 0~1。VOC 的 xml 用的是图像原始像素坐标COCO 的 json 用的也是绝对坐标但 bbox 写法是 [x, y, w, h]不是 xmin/ymin/xmax/ymax。三者之间的换算如果做错最常见的问题是框整体偏移或者宽高变成两倍训练出来 mAP 直接归零。推荐先把三类格式的特点记牢格式文件形态坐标示例坐标特点VOC每张图一个 xmlxmin, ymin, xmax, ymax绝对像素坐标COCO整个数据集一个 jsonbbox: [x, y, w, h]绝对像素坐标YOLO每张图一个 txtclass cx cy w h归一化相对坐标转换前先确认原标注用的是哪种别见到 xml 就统一按 VOC 处理labelme 导出的 json 里也有类似 polygon 结构处理方式完全不同。3.2 把 XML 转成 YOLO txt 的可复用脚本常见做法是写一个函数输入 xml 路径和类别映射表输出 YOLO 格式的文本行import xml.etree.ElementTree as ET def voc_to_yolo(xml_path, class_map): tree ET.parse(xml_path) root tree.getroot() w float(root.findtext(size/width)) h float(root.findtext(size/height)) lines [] for obj in root.findall(object): cls obj.findtext(name) if cls not in class_map: continue box obj.find(bndbox) xmin float(box.findtext(xmin)) ymin float(box.findtext(ymin)) xmax float(box.findtext(xmax)) ymax float(box.findtext(ymax)) x_center (xmin xmax) / 2 / w y_center (ymin ymax) / 2 / h bw (xmax - xmin) / w bh (ymax - ymin) / h lines.append(f{class_map[cls]} {x_center:.6f} {y_center:.6f} {bw:.6f} {bh:.6f}) return lines逻辑说明计算 x_center 时先求 xmin 与 xmax 的平均值再除以图片宽度得到的就是归一化中心点。bw 是框宽除以图片宽bh 同理。这样得到的所有值都在 0~1 之间符合 YOLO 训练要求。参数说明class_map 典型的写法是{crack: 0, pothole: 1}。类别 ID 必须从 0 开始连续编号如果中间跳过数字YOLO 的类别数 nc 会与实际行数对不上。转换时建议统一保留 6 位小数过少的位数会在 640 分辨率下积累出几个像素的框偏移小裂缝尤其敏感。3.3 多边形标签转普通框还是旋转框很多裂缝数据集不是用矩形框标注的标注工具里沿着裂缝画的是 polygon甚至是一条折线。直接用多边形外接的轴对齐矩形会把大量背景包进框里裂缝越斜框里背景占比越高模型学到的东西就越脏。旋转框是更好的表达。OpenCV 的 minAreaRect 可以从多边形点集得到最小外接旋转矩形import cv2 import numpy as np polygon np.array([[x1, y1], [x2, y2], [x3, y3], ...], dtypenp.float32) rect cv2.minAreaRect(polygon) # rect ((cx, cy), (width, height), angle) cx, cy rect[0] rot_w, rot_h rect[1] angle rect[2]逻辑说明minAreaRect 返回的是旋转矩形的中心、宽高和旋转角。裂缝检测场景里rot_w 通常对应裂缝延伸方向的长度rot_h 对应裂缝宽度。但通用 YOLOv8 检测头不支持旋转框要用 YOLOv8-OBB 的格式才能吃下这个标签。我的建议是第一阶段先容忍轴对齐外接框把流程跑通拿到 baseline等确认模型能收敛后再切到 YOLOv8-OBB。不要一上来就追求旋转框因为 OBB 的后处理和评估都比普通框复杂排查成本高不少。3.4 抽查可视化审查标注比调参重要转换完成后不要直接开训先随机抽 20 到 30 张图把标签框画出来人工过一遍。这一步能发现的典型问题包括坐标转换导致框偏移、裂缝标注一半有框一半没框、类别 ID 错乱、标签文件与图片文件名对不上。import cv2 def draw_yolo_boxes(img_path, txt_path): img cv2.imread(img_path) h, w img.shape[:2] for line in open(txt_path): parts line.strip().split() cls, cx, cy, bw, bh map(float, parts) x1 int((cx - bw / 2) * w) y1 int((cy - bh / 2) * h) x2 int((cx bw / 2) * w) y2 int((cy bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) return img逻辑说明画框是把归一化坐标乘以图像宽高还原成像素坐标。cx 减掉 bw 的一半得到左上角 x加上一半得到右下角 x。如果画出来的框和裂缝贴合得很好说明转换脚本没问题如果框大面积偏到图像外多半是归一化时用了错误的图片宽高。参数说明OpenCV 的 rectangle 默认在原图上直接画如果图片较大可以先 resize 到 800 宽再可视化方便快速扫图。检查时重点看长宽比特别大的框这类框最容易出现错位。4. 用 YOLOv8 训练裂缝检测目录结构、训练命令与四条曲线4.1 data.yaml 与目录组织的正确姿势数据体检和标注转换都完成后把数据组织成 YOLOv8 约定俗成的目录结构road_crack/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ └── crack.yamlimages 与 labels 下的子目录必须同名YOLO 默认认为一张图的标签位于 labels 下相同相对路径、相同文件名但扩展名为 txt 的文件里。data.yaml 写成这样path: ./road_crack train: images/train val: images/val names: 0: crack逻辑说明path 是数据集根目录train 和 val 相对于 path 填写。names 的 key 从 0 开始和标签文件里的 class ID 一一对应。YOLOv8 会自动根据 names 长度推导 nc不需要手动写 nc。参数说明类别名建议统一用英文小写。如果数据集里同时有裂缝和修补区域names 写成{0: crack, 1: patch}即可但要先确认原标注的类别数量和你写的完全一致。4.2 训练命令从 8n 起步越小的模型越不容易在 3258 张图上过拟合3258 张图对于目标检测来说是中等偏小的数据规模直接上 yolov8x 大概率过拟合。我建议先用 yolov8n 跑通流程验证数据没问题后再尝试 yolov8s 或 yolov8m。yolo detect train \ datacrack.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ close_mosaic10 \ patience20 \ projectruns \ namecrack_v1逻辑说明model 参数指定预训练权重YOLOv8 会加载 COCO 上训练好的参数做迁移学习这比从零训练收敛快得多也避免小数据集上冷启动。参数说明imgsz输入图像分辨率。裂缝宽度经常只有几个像素320 分辨率会把裂缝直接抹掉推荐 640如果显存足够且裂缝普遍细小可以试 1280但训练时间会明显增加。close_mosaic最后 10 轮关闭马赛克增强。马赛克增强会用四张图拼成一张裂缝很容易被切断模型学到的是碎片而不是完整裂缝最后阶段关闭可以让模型在真实分布上稳定收敛。patience20 轮内验证集 mAP 不提升就早停3258 张图训练通常 100 轮内就能收敛这个参数能防止你花一晚上等一个已经过拟合的实验。batch16 在 12GB 显存上跑 yolov8n 比较稳妥如果报 OOM降到 8不需要动其他参数。4.3 训练时盯住的四条曲线训练开始后不要只盯着终端输出。YOLOv8 会把每个 epoch 的指标写到runs/detect/crack_v1/results.csv直接读这个文件画曲线import pandas as pd df pd.read_csv(runs/detect/crack_v1/results.csv) print(df.columns)逻辑说明results.csv 里能看到 train/box_loss、train/cls_loss、val/box_loss、metrics/mAP50(B)、metrics/mAP50-95(B) 等字段。裂缝数据集上我最关注的是 val/box_loss 和 metrics/mAP50(B) 这两列。参数说明mAP50 反映的是框与真值框 IoU 大于 0.5 时的平均精度对裂缝这种长条目标mAP50 容易做到 0.8 以上但 mAP50-95 通常偏低因为裂缝框和真值框稍微错开几个像素IoU 就掉得很快。如果你的 mAP50 很高但 mAP50-95 只有 0.2 左右不用惊慌这是长宽比极端目标的正常特征。真正要警惕的是 val/box_loss 先降后升同时 mAP50 开始掉这是典型的过拟合信号。5. 裂缝场景训练避坑5 条实测踩坑记录5.1 细长框训练前期大范围漏检现象前几十轮训练 loss 从高位震荡mAP50 几乎为 0模型完全没有输出。原因裂缝框长宽比极端尤其是横向裂缝默认 anchor 先验匹配不上这种细长形状前期正样本太少检测头不知道怎么回归。解决先确认框宽高比分布如果中位数超过 5直接放弃老版 YOLOv5 默认 anchor 配置。YOLOv8 是 anchor-free 的对极端形状的容忍度高很多同时把 imgsz 从 640 提升到 1280让裂缝在特征图上多占几个像素正样本数量会明显增加。另一种常见做法是改用 YOLOv8-OBB用旋转框匹配裂缝走向但代价是部署和后处理复杂度上升。5.2 验证集 mAP 很高换一条新路段就翻车现象在验证集上 mAP50 到 0.85实拍一段新路面的视频漏检和误检都很多尤其光照角度一变裂缝几乎看不见。原因训练集和验证集来自同一个采集车、同一个时间段、同一批路段。模型学到了特定光照下的纹理特征而不是裂缝本身的几何特征。纯随机划分数据集时这个问题更严重因为同一个路段的多张图同时进入了训练集和验证集。解决按路段划分 train/val这一步在 2.3 里已经强调过。再就是数据增强上做保守处理不要开太强的 HSV 颜色扰动因为裂缝检测依赖的是灰度纹理颜色抖动过大反而让模型学到错误的颜色相关性。我一般把 hsv_h 从默认 0.015 升到 0.02把 hsv_s 和 hsv_v 降到 0.2 左右让模型更关注结构特征。5.3 一条裂缝被检测成好几段修复工作量虚高现象一条两米长的纵向裂缝模型输出四五个不连续的框统计病害数量时一条裂缝被重复计数。原因真值框本身就是按几个局部段标注的模型学到的天然就是片段另外推理时默认 IoU 阈值偏低相邻框没有被合并。解决推理时提高 NMS 的 IoU 阈值让更容易合并相邻框yolo detect predict \ modelruns/detect/crack_v1/weights/best.pt \ sourcetest_road/ \ imgsz640 \ iou0.3参数说明iou0.3 的意思是两个预选框 IoU 超过 0.3 就合并默认值 0.7 是为了防误检的背景分离但裂缝场景下相邻框本来就是同一个目标0.3 更合理。如果依然断成几段需要在后处理里做框合并计算两个框的距离和重叠区域中心距离小于两者平均宽高一半时合并成一个大框。常见做法是用cv2.rectangle框的并集坐标直接替换为一个大外接框能少报很多虚增数量。5.4 早期细微裂缝漏检肉眼能看到模型看不到现象图像上一条几像素宽、对比度很低的细微裂缝人工标注了模型硬是没检测出来。原因YOLOv8 默认下采样到 32 倍640 分辨率输入时特征图上只有 20 个像素宽度 3 像素的裂缝经过多层卷积后响应被周围路面纹理淹没。解决不要把整张大图直接喂进模型而是做切片推理也就是 tiling。把图像切成 640x640 的块逐块检测再拼回原坐标def sliding_infer(model, img, patch640, stride320): h, w img.shape[:2] dets [] for y in range(0, h, stride): for x in range(0, w, stride): y1, x1 y, x y2, x2 min(y patch, h), min(x patch, w) roi img[y1:y2, x1:x2] result model.predict(roi, imgszpatch, verboseFalse) for box in result[0].boxes: cx, cy, bw, bh box.xywh[0].tolist() conf box.conf[0].item() dets.append([cx x1, cy y1, bw, bh, conf]) return dets逻辑说明每个检测框的中心点要加上切片左上角的偏移量才能映射回原图坐标。stride 小于 patch 是为了让相邻切片有重叠区域避免裂缝恰好落在切片边界被截断。重叠区域里同一个裂缝可能被重复检测最后再做一次全局 NMS。参数说明patch 越大单次推理的上下文越丰富但越容易出现小目标被下采样压没patch640、stride320 是一个平衡选择。如果显存紧张可以把 patch 降到 512但重叠率要保持在 20% 以上。5.5 标签里混入了大面积“网裂”框现象训练集的标签里出现一批面积特别大、长宽比接近 1 的框把一整片网状裂缝全框进去模型训练时被这些框干扰输出的框越来越大。原因标注人员对“一条裂缝”和“一片网裂”的尺度定义不一致有人按单条病害标有人按整个破损区域标。解决写一个清洗脚本把面积占比过大或长宽比接近 1 的框筛出来人工复核# 假设已经读取txt每一行的 cls cx cy w h if (bw * bh) 0.33 or max(bw / bh, bh / bw) 1.5: print(疑似大范围/方形框需人工复核, txt_path)逻辑说明bw 乘以 bh 是框面积占整张图的比例超过三分之一说明很可能框了整片区域长宽比接近 1 说明目标形态不像裂缝。筛出来之后不要直接删先可视化确认。如果确实是大片网裂可以考虑把它单独立为一个类别而不是混在裂缝类里。6. 3258 张图的上限与进阶从检测框到长度估算、部署与回注6.1 用二值化把检测框变成裂缝长度统计检测框解决的是“有没有裂缝”和“裂缝在哪”的问题但道路养护部门往往更关心“这条裂缝有多长”。一个常用做法是在检测框内再对灰度图做二值化提取裂缝像素并统计长度import cv2 gray cv2.cvtColor(img[y1:y2, x1:x2], cv2.COLOR_BGR2GRAY) thr cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV cv2.THRESH_OTSU)[1] pixel_cm 0.05 # 每个像素对应的厘米数由标定得出 length_cm cv2.countNonZero(thr) * pixel_cm逻辑说明Otsu 阈值会在检测框内自动区分裂缝暗色像素和路面背景。pixel_cm 需要通过已知尺寸的路面标定板换算不同拍摄高度和焦距下数值不同。如果裂缝有分叉这种统计会偏大更严谨的做法是先做骨架细化再统计骨架像素数。如果你想得到裂缝的像素级轮廓而不是矩形框后续可以切换到 yolo 实例分割把这套矩形框标签转成分割掩码后再训练。6.2 导出 ONNX 并部署到巡检设备3258 张图训练出的模型虽然规模不大但部署价值已经够了。导成 ONNX 后用 onnxruntime 做推理比直接跑 PyTorch 更稳定也为后续 TensorRT 加速留好接口yolo export \ modelruns/detect/crack_v1/weights/best.pt \ formatonnx \ imgsz640 \ halfTruehalfTrue 会把权重转成 FP16显存占用和推理延迟都会降低。导出后先检查输出节点是否为 1 个或 3 个维度的数组不要直接丢给生产环境至少要用 10 张没有参与训练的路面图做一次对比测试。6.3 半自动回流把漏检案例变成下一轮训练集模型上线后的第一次巡检大概率会收集到一批漏检图。我自己的习惯是把置信度低于 0.25 但框内确实有裂缝的图像挑选出来配合半自动标注工具补标签再混入原始 3258 张图做增量训练。这个循环比任何调参技巧都重要道路场景日新月异固定数据集训练出来的模型只能覆盖训练集里见过的路面纹理。后续建议每个季度做一次数据增量不需要一次加很多每次几百张新图就能让模型在新路段上的表现明显改善。最后说一句我自己的心里话裂缝检测这个方向算法选型不是最难的真正让人熬夜的是数据和数据里那些看不见的坑。3258 张带标签图是一块好起点但只有把数据体检、标注转换、训练调参和后处理全链路想清楚它才能真正变成能上路的检测能力。希望帮到你。本文还有配套的精品资源点击获取