
简介面向YOLO系列、Faster RCNN、SSD等目标检测模型训练的路面病害识别数据集以航拍图像标注为主适合缺陷检测项目、算法对比实验及深度学习入门练习也可用于道路巡检与养护管理等场景。类别覆盖纵向裂缝、横向裂缝、龟裂、斜裂缝、修补、块裂、坑槽7类常见路面病害共对应3151张航拍图片的标注信息图片与TXT标签已按训练集、验证集、测试集划分标签坐标按YOLO格式归一化可直接用于YOLO算法训练。下载包共2000个文件主要为TXT标签文件另含1个YAML类别配置文件压缩包约194.08MB标签文件按图像命名便于与原始图片一一关联。目前已有390人学习下载适合需要现成标注数据快速完成模型训练、验证数据划分流程或比较不同检测器的开发者也可借此熟悉YOLO与VOC标注文件的组织方式。1. 航拍路面病害识别数据集不是把无人机飞起来拍一圈那么简单无人机巡检在公路养护里已经不算新鲜词真正让人头疼的是数据处理环节飞一次带回几百张高分辨率影像里面裂缝、坑槽、修补区混在一起靠人工看图标注一个标段就能耗掉两周。更尴尬的是把通用目标检测模型直接拉过来跑结果惨不忍睹——COCO预训练模型认得出猫狗汽车却把横向裂缝识别成路面纹理噪声。航拍路面病害识别数据集目标检测要解决的就是这个衔接问题把航拍视角下的路面病害按目标检测格式组织好让检测模型在俯拍大图、小目标、复杂背景里真正学会找病害。它和常规路面检测数据集最大的区别在于视角和尺度无人机在15到30米高度拍出来的病害宽度往往只有几十个像素和普通车载相机拍到的数据完全是两套分布。这篇文章会从数据构成、格式转换、训练参数到部署推理讲一遍最后把最容易翻车的地方单独拉出来说。适合读的人很明确手里有无人机和一套养护项目、正愁没有标好数据的人准备拿目标检测做路面病害识别课题的研究生还有想评估这方向值不值得投入的团队负责人。读完你会知道这套数据集该怎么用以及用的时候在哪些地方容易栽跟头。2. 航拍病害数据长什么样先搞清它和普通路面数据的底层差异2.1 航拍病害数据与普通路面数据的三处不同第一个不同是视角。车载相机拍路面病害在画面里占比大、纹理清晰一条裂缝能占满三分之一画面航拍则是俯视正射或倾斜视角一条0.3米宽的裂缝在20米高度下占整张图的比例可能只有千分之几这在目标检测里属于典型的小物体。如果你拿LabelImg随手框完就去训YOLO会把大尺寸输入缩放成640x640裂缝直接缩成两个像素特征完全丢失。第二个不同是光照和阴影。车载相机离地面近补光可控航拍要面对太阳角度变化、树影、桥梁遮荫、甚至云层遮挡。病害和阴影在灰度分布上高度重叠网裂在阴影里几乎无法辨认。这会导致一个常见结果同一段路上午飞和下午飞模型表现差异极大。第三个不同是背景复杂度。航拍画面里除了路面还有车道线、交通标线、防眩板影子、护栏、路肩植被。很多算法会在车道线和护栏边缘产生大量误检因为它们的边缘特征和裂缝在局部纹理上很像。2.2 病害类别怎么定从裂缝到坑槽的标注规范航拍路面病害识别数据集的类别定义直接决定检测效果类别定得太粗算法分不清病害类型定得太细样本不够模型学不过来。常见做法是分成五类横向裂缝、纵向裂缝、网状裂缝、坑槽、修补区。前两者是线性病害后三者是区域病害这个划分在公路技术状况评定标准里有对应关系也是养护决策最关心的粒度。标注规范里最容易被忽略的是框法。横向裂缝和纵向裂缝常见有两种框法一种是框住整条裂缝的外接矩形一种是框住裂缝中一段代表性区域。我建议统一用外接矩形因为裂缝往往横贯整个画面如果只框一段模型会学到裂缝只出现在局部在滑窗推理时产生大量重复检测。另外两条相邻裂缝要不要合并成一个框也要规定清楚——间距小于裂缝宽度的合并否则分开标。修补区包含已修补的裂缝和龟裂修复面如果训练时没有单独类别模型会把修补区当成背景这是修补后路面最容易被漏检的场景。2.3 标注格式与工具链VOC/COCO/YOLO三种归宿数据集交付时一般会同时提供Pascal VOC的xml、COCO的json以及YOLO的txt三种格式因为不同训练框架吃的格式不一样。VOC适合跑SSD、Faster R-CNN这类经典检测器COCO是mmdetection生态常用的YOLO格式则是现在效率最高的选择——每张图片对应一个同名txt文件每行是类别号 x_center y_center width height所有坐标都是相对于图片尺寸归一化后的0到1之间的浮点数。标注工具方面目标检测常用标注工具就那几个LabelImg上手快适合小样本labelme能标多边形但不直接输出YOLO格式Roboflow的在线标注和格式转换一体但是大图上传和标注速度受网络影响大。我的建议是如果原始航拍图是几千乘几千的像素先用工具在原始图上标注太浪费显存和人力更合理的流程是先切patch再标注后面训练直接用patch不用每次训练前都切一次。2.4 数据量、均衡性与难例先验收再开训数据量没有一个绝对门槛但按照做目标检测数据集处理的常见经验一个类别低于300个实例的模型基本不可用1500到3000张图、每类500个以上实例是比较稳妥的起点。航拍路面病害的难例集中在三种低对比度裂缝光线暗或者缝里填了泥、被树影和车轮印干扰的裂缝、以及刚修补完颜色发黑的修补区。如果数据里大部分是晴天正午拍摄的清晰裂缝模型在早晨和傍晚的航拍图上一定掉点。拿到数据集先别急着训先做一个验收动作把train和val的类别分布打印出来如果横向裂缝3000个、网状裂缝只有200个就得先做均衡处理否则训练过程会被多数类主导。另一件要验收的是标注质量——随机抽20张图叠加显示gt框和原图看框是否贴着病害边缘、有没有漏标。这一步花半小时能省掉后面模型为什么训不好的一整天的玄学调试。3. 把数据集喂给YOLO格式转换、切patch与训练参数3.1 用标注工具导出数据集从LabelImg到Roboflow的坑如果你拿到的是未标注的航拍原图需要自己标注流程是先切patch再标注最后导出。切patch这一步直接决定后面所有工作的质量。我一般用Python脚本把6000x4000的原始航拍图切成1024x1024的patch步长取896保证10%到15%的重叠。为什么要重叠因为病害正好被切在patch边缘时检测框会被截断重叠区域让同一病害至少在一个patch里完整出现。import os from PIL import Image def split_image(image_path, out_dir, patch_size1024, step896): img Image.open(image_path) width, height img.size base_name os.path.splitext(os.path.basename(image_path))[0] os.makedirs(out_dir, exist_okTrue) patch_id 0 for y in range(0, height - patch_size 1, step): for x in range(0, width - patch_size 1, step): box (x, y, x patch_size, y patch_size) crop img.crop(box) crop.save(os.path.join(out_dir, f{base_name}_{patch_id:04d}.jpg)) patch_id 1这段代码的逻辑是以step为滑动步长在原始大图上切出patch_size大小的块最后一行和最右列如果剩余区域不足patch_size直接丢弃。这里有两个参数值得改如果原始图是4500x4500patch_size可以降到896如果裂缝更细step需要减小到patch_size的80%左右增加重叠率。切完的patch还要检查——把那些完全不含路面的patch剔除比如纯天空、纯植被否则训练时背景类别污染严重。3.2 VOC/COCO转YOLO格式转换脚本与坐标校验如果你拿到的数据集是VOC或COCO格式转YOLO是绕不过去的一步。VOC转YOLO的核心是读xml里的bndbox坐标换成归一化的中心点坐标和宽高COCO转YOLO则是解析json里的annotations字段。这里最容易犯的错误是忘记检查坐标是否越界尤其是切patch后重新标注的数据经常出现x_max超出图片宽度一个像素的情况。import xml.etree.ElementTree as ET import os def voc_to_yolo(xml_file, out_txt, class_names): tree ET.parse(xml_file) root tree.getroot() size root.find(size) img_w int(size.find(width).text) img_h int(size.find(height).text) lines [] for obj in root.iter(object): name obj.find(name).text if name not in class_names: continue class_id class_names.index(name) box obj.find(bndbox) x1 float(box.find(xmin).text) y1 float(box.find(ymin).text) x2 float(box.find(xmax).text) y2 float(box.find(ymax).text) # 坐标裁剪防止越界导致训练崩溃 x1 max(0, min(x1, img_w - 1)) x2 max(0, min(x2, img_w - 1)) y1 max(0, min(y1, img_h - 1)) y2 max(0, min(y2, img_h - 1)) if x2 - x1 1 or y2 - y1 1: continue x_center ((x1 x2) / 2) / img_w y_center ((y1 y2) / 2) / img_h width (x2 - x1) / img_w height (y2 - y1) / img_h lines.append(f{class_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}) with open(out_txt, w) as f: f.write(\n.join(lines))这个脚本里最重要的三个细节一是坐标裁剪标注框偶尔超出边界不裁剪的话归一化数值可能大于1训练时YOLO会直接忽略甚至报错二是过滤掉宽度或高度小于1像素的框这种框在切patch后经常出现属于无效标注三是class_names的顺序必须和训练配置里的类别文件一致否则类别错位等于白训。3.3 训练集划分与切patch小目标检测的第一步数据集划分用最简单的随机抽样就行但要注意按patch来源分组避免同一张原始大图切出的patch一部分进train一部分进val导致数据泄露——模型在验证集上的精度虚高部署后立刻打回原形。做法是先把原始大图编号再按大图维度划分同一张大图的所有patch只能出现在一个集合里。import os import random from collections import defaultdict def split_by_source(patch_dir, val_ratio0.15, test_ratio0.05): patch_files [f for f in os.listdir(patch_dir) if f.endswith(.jpg)] source_map defaultdict(list) for f in patch_files: # 假设patch命名格式是 source_{patch_id}.jpg source f.rsplit(_, 1)[0] source_map[source].append(f) sources list(source_map.keys()) random.shuffle(sources) n len(sources) test_n int(n * test_ratio) val_n int(n * val_ratio) test_sources set(sources[:test_n]) val_sources set(sources[test_n:test_n val_n]) train_files, val_files, test_files [], [], [] for source, files in source_map.items(): for f in files: if source in test_sources: test_files.append(f) elif source in val_sources: val_files.append(f) else: train_files.append(f) return train_files, val_files, test_files这个划分逻辑的关键是按来源分组再随机抽而不是直接对patch文件随机抽。实际使用中如果原始图只有30张5%的测试集只有1到2张原始图数量太少可以把test_ratio提到10%到15%或者干脆不做test集只分train和val。小样本场景下验证集的作用是挑最优权重测试集留到最后一轮做终极评估样本太少反而让测试结果方差增大。3.4 用YOLOv8训练航拍病害模型命令与必调参数数据准备好之后训练命令本身不复杂难在参数怎么设。航拍病害和常规目标检测最大的区别是目标小所以imgsz一定要往上提。我在实际项目里用1024或1280作为默认输入尺寸而不是YOLOv8默认的640。下面是完整训练流程pip install ultralytics # 训练前先把数据集文件组织成YOLO标准结构 # data/ # images/train/ # images/val/ # labels/train/ # labels/val/ yolo detect train modelyolov8s.yaml dataroad_disease.yaml epochs100 imgsz1280 batch8 lr00.001 mosaic0.9 close_mosaic10road_disease.yaml内容如下注意nc要和标注脚本里的class_names长度一致path: ./data train: images/train val: images/val nc: 5 names: [transverse_crack, longitudinal_crack, alligator_crack, pothole, patch_area]参数说明epochs设100但实际训练中如果val的mAP50在30轮以后不再上升可以直接early stopimgsz设为1280意味着显存压力大8G显存跑yolov8s需要batch降到4或者改用yolov8nlr0设0.001而不是默认的0.01是因为航拍病害数据和ImageNet预训练分布差异很大学习率太大会把预训练权重冲毁mosaic设为0.9加close_mosaic10的意思是最开始用mosaic增强合成大图最后10轮关掉mosaic让模型在接近真实分布的输入上收敛这是从YOLOv8官方技巧里继承的做法对细长裂缝特别有用。4. 从训练到落地模型选型、推理策略与评价指标4.1 模型选型n/s/m/l怎么选航拍路面病害识别部署在哪里直接决定选什么模型。如果是在地面站后处理用RTX 3060以上的显卡跑选yolov8m最均衡——精度明显高于s速度也在可接受范围。如果要上无人机机载边缘设备比如Jetson Orin Nanoyolov8n是唯一现实的选择但n模型的细长裂缝检测能力下降明显需要用后面讲的难例挖掘来补。YOLOv8系列各尺寸对比大致如下模型参数量输入1280时典型显存推理速度RTX3060适用场景yolov8n约3.2M2-3GB约15ms机载边缘设备yolov8s约11.2M4-6GB约22ms地面站快速处理yolov8m约25.9M8-10GB约40ms地面站高精度yolov8l约43.7M12-16GB约70ms离线批量处理我的经验是不要一上来就用l或x模型航拍病害数据通常只有几千张大模型在小数据上过拟合很严重mAP反而可能低于s模型。先用yolov8s把数据流程跑通拿到一个baseline再往上加容量。4.2 推理部署滑窗、NMS与阈值训练完的模型在部署时面临的第一个问题是输入尺寸模型是在1280x1280的patch上训练的但实际航拍原图是6000x4000甚至更大。直接把原图resize到1280会丢失所有小目标信息必须做滑窗推理把大图切成1280x1280的patch每个patch送进模型推理再做NMS合并。import torch from ultralytics import YOLO model YOLO(best.pt) def slide_infer(image_path, patch_size1280, step1152, conf0.25): import cv2 import numpy as np img cv2.imread(image_path) h, w img.shape[:2] detections [] for y in range(0, h, step): for x in range(0, w, step): y_end min(y patch_size, h) x_end min(x patch_size, w) patch img[y:y_end, x:x_end] results model(patch, confconf, imgszpatch_size, verboseFalse) boxes results[0].boxes for box in boxes: x1, y1, x2, y2 box.xyxy[0].cpu().numpy() detections.append([x x1, y y1, x x2, y y2, float(box.conf[0]), int(box.cls[0])]) return detections这个滑窗推理的step设成1152保留10%重叠是因为边缘cut-off的裂缝需要重叠区域来弥补。检测结果合并时如果直接用OpenCV的NMS类注意IOU阈值在航拍小目标场景下要下调到0.3到0.4——裂缝是细长物体两个框的交并比天然偏小阈值太高会导致同一裂缝重复出现多次。置信度阈值conf设0.25是低漏检取向适合病害巡检场景误检多一点可以靠人工复核兜底。4.3 评价指标别只盯着mAP50目标检测评价指标里mAP50和mAP50-95是最常被引用的两个数字但航拍病害识别有个特殊性漏检的代价远大于误检。路面养护决策里漏掉一条裂缝意味着这段路可能错过灌缝时机误检最多让人工复核多看一眼。所以我的评价习惯是额外关注两个指标一是recallconf0.1也就是低置信度下的召回率它反映模型是否已经学到该病害的特征只是分数被压低二是小目标AP在YOLO的验证结果里按面积区间看AP_S如果AP_S远低于AP_M说明小目标问题是主要瓶颈优先加大imgsz而不是堆模型容量。还有个航拍场景特有的指标patch级别的漏检率。把一个patch里所有真实病害框和预测框做匹配如果某个gt框没有被任何一个预测框覆盖超过50%算漏检。比单纯mAP更贴合实际巡检需求。5. 航拍路面病害数据集的5个常见坑翻车现场与后悔药5.1 小目标漏检原图缩放毁掉细长裂缝现象模型在验证集上mAP50有0.75但拿到整张航拍原图去推理横向裂缝一条都检不出来。原因验证集里的图片已经是切好的patch模型见了不陌生直接推理时如果用了默认的imgsz640整张6000x4000的大图被缩成640x640裂缝宽度从20个像素变成2个像素特征彻底消失。这个坑的本质是训练和推理的输入尺度不一致。解决推理时要么用上面说的滑窗推理并保持patch_size等于训练imgsz要么把推理imgsz直接设成和训练一致的1280。另外在训练阶段就引入随机缩放增强让模型见过64%到120%尺度的目标能显著提高对尺度变化的鲁棒性。5.2 类别不均衡网裂样本少导致的训后失真现象训练日志里总损失一直在降但混淆矩阵显示网状裂缝几乎永远被预测成横向裂缝坑槽类别精度高但召回率很低。原因数据集中横向裂缝3000个实例网状裂缝只有200个模型在训练过程中被多数类主导少数类的梯度被淹没。这是做目标检测数据集处理时最常见的不均衡问题。解决第一优先做数据层面的重采样对少类别所在的patch做复制增强让每类实例数差距控制在3倍以内。第二在yaml里给每个类别设置loss权重比如网裂权重设为3.0。第三使用copy-paste增强——把网裂区域从别的patch挖出来贴到背景干净的路面上。这里注意航拍路面背景相对均匀不像COCO那样会被复杂背景坑copy-paste是有效的。5.3 标注歧义同一病害两种框法现象loss在30轮之后降到0.08左右就完全不降mAP50震荡在0.6到0.7怎么也上不去。原因抽查标注数据后发现同一个横向裂缝一半标注员框了整条裂缝另一半只框了中间最明显的三分之一段。目标检测任务中gt框的不一致直接让模型学习目标变得模糊——同一位置有的样本要回归大框有的要回归小框。解决建立标注规范文档附参考示例图两组裂缝统一外接矩形标注完成后做一致性抽检标注员交叉验证。还有一个技巧训练时把gt框和预测框的匹配阈值从默认0.5降低到0.45可以在标注不完全一致时减轻loss波动。5.4 预处理翻车All labels empty 的三种诱因现象train跑第一个epoch直接报错提示某个batch里所有labels为空训练直接中断。原因三种情况最常见。一是VOC转YOLO时class_names列表和yaml里的names顺序不一致类别对不上所有标注被跳过二是切patch时如果病害正好被切碎patch里剩余的gt框面积太小被我前面脚本里的x2 - x1 1过滤掉三是坐标归一化时除错了尺寸——用了patch的高宽却除以了原图的高宽导致归一化坐标全在1以上。解决在训练前跑一个校验脚本统计每个txt文件的行数如果空文件比例超过5%回头检查转换逻辑再对每个label文件做范围检查确认所有坐标都在0到1之间。这个校验脚本值得保留每次换数据集都用得上。import os def validate_labels(label_dir, class_num5): empty_files [] out_of_range [] for f in os.listdir(label_dir): if not f.endswith(.txt): continue path os.path.join(label_dir, f) with open(path) as fh: lines fh.readlines() if len(lines) 0: empty_files.append(f) continue for line in lines: parts line.strip().split() if len(parts) ! 5: continue cls int(parts[0]) coords [float(p) for p in parts[1:]] if cls class_num or any(c 0 or c 1 for c in coords): out_of_range.append(f) break print(fempty labels: {len(empty_files)}) print(fout of range: {len(out_of_range)}) return empty_files, out_of_range5.5 迁移学习翻车COCO权重微调掉点现象用yolov8s.pt的预训练权重开始微调epochs调到150mAP50最终只有0.55而从头训练相同的epoch却到了0.65。原因目标检测模型微调崩了是经典问题。COCO的预训练权重学的是自然图像特征——轮子、人、动物路面病害是纹理和边缘主导的弱纹理目标预训练特征不仅不帮助反而把模型的低层特征往自然图像的统计方向拉。学习率默认0.01对域差异过大的迁移来说太高了。解决两种策略都值得试。一是降低学习率lr0从0.01降到0.0005到0.001让预训练权重缓慢适应新域二是先冻结backbone训练检测头50轮再解冻全部层微调。冻结backbone在航拍数据量很小低于1000张时尤其有效可以避免低层特征被小数据破坏。6. 数据集的二次价值难例挖掘、伪标签与泛化验证6.1 难例挖掘让模型吃它最怕的那批patch训练完第一版模型后我把val集里所有漏检的patch单独挑出来统计它们的共同特征。最常见的两个类别是阴影下的横向裂缝和浅色修补区。做法是把这些patch复制三份加进训练集并把置信度低于0.1的预测当伪负样本——如果模型对某个patch完全没把握这个patch本身值得人工再看一眼。难例挖掘不是一次性的每轮训练后都做一轮通常两到三轮之后小目标AP能涨3到5个点。这个方案的价值在于数据集的标注成本已经付过难例挖掘是边际成本最低的收益来源。6.2 伪标签半监督给未标注航拍影像续命如果你的数据集标注量不够还有一种常见做法是半监督伪标签。用训练好的模型对未标注的航拍patch做预测只保留置信度超过0.7的检测框当成伪gt加入训练集。操作上需要注意两点第一伪标签只用于类别平衡和背景学习不用于最终权重选择的val集第二如果某个patch里伪标签数量异常多比如超过20个框这个patch大概率是误检集中地直接丢掉。半监督能把数据量有效扩到1.5到2倍但前提是你已经有一版精度还说得过去的模型否则伪标签的噪声会把模型带偏。6.3 泛化验证用同路段不同光照做留出测试一个我后来养成的习惯正式评估前把数据里同一路段不同时间、不同光照、不同方向的图片单独留出一组不参与任何训练和调参只做最终测试。航拍路面检测最怕的就是模型只在特定光照下有效留出验证能暴露这个问题。第一次做的时候我发现留出光照组和随机划分组的mAP差距高达0.15这个数字让我再也不敢只用随机划分的结果对外汇报。如果手里的数据集没有不同光照的样本还有一个更简单的验证方法对val集做亮度扰动把图像亮度随机乘0.7和1.3再看模型mAP的下降幅度。下降超过20%说明模型过拟合了当前亮度分布需要加大亮度增强系数。这个检查只花十分钟但能帮你判断数据集的泛化边界在哪。希望帮到你。本文还有配套的精品资源点击获取