简介:这份航拍路面病害识别数据集面向从事目标检测与缺陷检测的深度学习开发者、科研人员及学生,提供可直接投入训练的标注数据,解决路面裂缝与坑槽等病害样本获取难、标注成本高的问题。资源包共约2000个文件,以1999个txt标签文件和1个yaml类别配置文件为主,压缩包大小约194MB,txt标签对应YOLO格式的边界框标注,yaml文件则指定了类别名称与数据路径,便于快速接入训练流程。数据集包含3151张航拍图像,覆盖Longitudinal crack、Transverse crack、Alligator crack、Oblique crack、Repair、Block crack、Pothole共7个类别,并同时提供VOC格式的xml标签,兼容YOLO系列、Faster R-CNN、SSD等主流检测模型。图片与标签已预先划分为训练集、验证集和测试集,省去手动切分环节,可直接用于算法训练与性能评估。目前已有390人学习下载,适合需要快速验证模型或开展路面病害检测研究的中高级开发者参考使用。
1. 航拍路面病害识别数据集:为什么它和普通目标检测数据集不是一回事
做道路巡检的团队这两年都在往无人机上迁。理由很直接:一条 20 公里的城市主干道,人工巡检加拍照归档要一整天,无人机飞两个架次、四十分钟就能覆盖,还能拿到正射影像。但真正把项目推下去的人很快会撞到同一堵墙——航拍路面病害识别数据集和随手能下到的通用目标检测数据集,压根不是一个物种。
普通目标检测数据集里,一张图里目标占几百像素、背景干净、光照均匀,模型随便训训 mAP 就上去了。航拍路面病害完全反过来:裂缝在 4000×3000 的原图里可能只有 3 到 8 个像素宽,坑槽和阴影、水渍、修补痕迹混在一起,同一类病害在不同光照、不同飞行高度下形态差异巨大。你拿 COCO 预训练权重直接 finetune,第一轮验证就会看到模型把车道线当成裂缝、把井盖当成坑槽。
这篇笔记面向三类人:正在做道路巡检自动化、想用无人机影像做病害检测的算法工程师;手里已经有一批航拍图但不知道怎么标注和切分的团队;以及评估这个方向值不值得投入的技术负责人。我会把数据集的构成逻辑、标注规范、切图策略、训练参数和几个必踩的坑讲清楚,让你拿到一批原始航拍图之后能直接开工,而不是在数据准备阶段耗掉两个月。
2. 航拍路面病害数据集到底长什么样:类别、标注与采集边界
2.1 病害类别怎么定:别照搬公路养护规范
公路养护行业有成熟的病害分类标准,比如裂缝、坑槽、车辙、沉陷、松散、泛油这几大类,往下还能细分横向裂缝、纵向裂缝、网状裂缝。但做数据集时不能直接照搬这套分类,原因是标注成本和模型可分性。
我一般会把类别收敛到 4 到 6 类,按「视觉上可分 + 养护上可决策」两个标准来切:
| 类别 | 视觉特征 | 是否建议单列 | 说明 |
|---|---|---|---|
| 横向裂缝 | 垂直于行车方向,线状 | 是 | 与纵向裂缝形态差异大,分开训更稳 |
| 纵向裂缝 | 平行于行车方向,线状 | 是 | 常与车道线混淆,需重点标注负样本 |
| 网状裂缝 | 龟裂成块,面积状 | 是 | 边界模糊,标注框要略大于实际范围 |
| 坑槽 | 不规则暗色凹陷 | 是 | 与阴影、水渍易混,需多光照样本 |
| 修补 | 规则矩形,颜色偏深或偏浅 | 是 | 养护决策上属于已处理,但模型必须能识别 |
| 车辙/沉陷 | 带状变形,无明显边界 | 否 | 航拍视角下几乎不可分,建议合并或放弃 |
把车辙和沉陷砍掉是血泪经验。这两个类别在正射影像里没有清晰边界,标注员之间的一致性极低,训出来的模型置信度永远在 0.3 上下晃,纯属给评估指标添乱。如果你的养护业务必须要这两类,正确做法是走三维重建或者多视角,而不是硬塞进二维检测数据集。
2.2 标注规范:框怎么画、负样本怎么留
目标检测标注看似简单,航拍场景下有几个必须写进标注手册的规则,否则不同标注员产出的框会互相打架。
第一条,裂缝类用旋转框还是水平框。裂缝是细长目标,水平外接框会引入大量背景,导致模型学到一堆无关纹理。如果框架支持旋转框(比如 mmrotate 那一套),优先用旋转框;如果只能用水平框,那就在切图阶段把图切小,让裂缝在局部图里尽量接近水平或垂直,减少框内冗余。
第二条,框的松紧度。坑槽、网状裂缝这类面积目标,框要贴紧实际病害边界,外扩不超过 5 个像素;裂缝类因为太细,框宽至少给到 4 到 6 个像素,否则标注框本身比目标还窄,训练时正样本都采不到。
第三条,负样本必须显式标注。车道线、井盖、水渍、树影、标线箭头,这些是航拍路面检测最主要的误检来源。我一般会单独建一个hard_negative类别或者干脆用空标注图,把这几类场景各收集几百张,混进训练集。没有负样本的数据集,模型上线后误检率能高到你怀疑人生。
2.3 采集边界:高度、重叠率与光照
数据集的质量上限在采集阶段就定了。常见做法是固定飞行高度在 60 到 120 米之间,这个区间既能保证裂缝可见,又不至于单张图覆盖面积太小。重叠率建议航向 75%、旁向 65% 以上,方便后续做正射拼接,也方便从同一区域裁出多尺度样本。
光照上要刻意制造差异:晴天正午、阴天、清晨斜射光各占一定比例。只采晴天正午的数据集,模型一到阴天就集体翻车,这是航拍检测最典型的玄学问题。如果条件允许,同一路段在不同日期各飞一次,把时间维度也纳入数据集。
3. 从原始航拍图到可训练数据集:切图、划分与格式转换
3.1 大图切小图:滑窗参数怎么设
航拍原图动辄 5000×4000 以上,直接送进网络要么显存爆掉,要么被 resize 到 640 后裂缝彻底消失。标准做法是滑窗切图,但窗口大小和重叠率有讲究。
import cv2 import os import numpy as np def sliding_window_crop(image_path, save_dir, patch_size=1024, overlap=200): """ 对航拍大图做滑窗切图 patch_size: 切图边长,建议 1024 或 1280 overlap: 相邻窗口重叠像素,建议 patch_size 的 15%~25% """ img = cv2.imread(image_path) h, w = img.shape[:2] stride = patch_size - overlap base = os.path.splitext(os.path.basename(image_path))[0] count = 0 for y in range(0, h, stride): for x in range(0, w, stride): # 边界处理:保证窗口不越界 x2 = min(x + patch_size, w) y2 = min(y + patch_size, h) x1 = max(x2 - patch_size, 0) y1 = max(y2 - patch_size, 0) patch = img[y1:y2, x1:x2] # 过滤掉纯黑或纯白的无效块 if patch.std() < 5: continue out_name = f"{base}_{x1}_{y1}.jpg" cv2.imwrite(os.path.join(save_dir, out_name), patch) count += 1 return count这段代码的关键在三个参数。patch_size设 1024 是权衡结果:太小则单图目标数量少、训练效率低,太大则裂缝在 resize 后丢失。overlap设 200 像素是为了保证跨窗口的裂缝不被切断,同时给后续 NMS 合并留余地。patch.std() < 5这个过滤是防止把纯色区域也切出来,那些图对训练毫无贡献还拖慢速度。
切图之后,标注坐标必须同步做偏移。如果你先标注大图再切图,一定要写脚本把标注框按窗口位置平移,否则框和图像对不上,训练时 loss 直接不收敛。
3.2 数据集划分:别用随机划分
通用数据集习惯用 8:1:1 随机划分,但航拍路面数据必须按路段划分。原因很简单:同一路段相邻帧的图像高度相似,随机划分会让训练集和验证集出现近乎重复的样本,验证 mAP 虚高,上线后打回原形。
正确做法是按飞行架次或路段 ID 分组,训练集、验证集、测试集各占不同路段。如果数据量足够,再在训练集内部按光照条件分层采样,保证每个 batch 里晴天、阴天样本都有。
# 按路段前缀分组划分的目录结构示例 dataset/ ├── images/ │ ├── train/ # 路段 A、B、C 的切图 │ ├── val/ # 路段 D 的切图 │ └── test/ # 路段 E 的切图 └── labels/ ├── train/ ├── val/ └── test/3.3 转成 YOLO 格式:四个边界坑
主流框架里 YOLO 系列的标注格式最省事,一行一个目标:class_id cx cy w h,全部归一化到 0 到 1。转换脚本本身不难,坑在边界。
def voc_to_yolo(bbox, img_w, img_h): """ bbox: [xmin, ymin, xmax, ymax] 绝对坐标 返回归一化的 (cx, cy, w, h) """ xmin, ymin, xmax, ymax = bbox # 坑1:坐标越界,必须裁剪 xmin = max(0, min(xmin, img_w - 1)) xmax = max(0, min(xmax, img_w - 1)) ymin = max(0, min(ymin, img_h - 1)) ymax = max(0, min(ymax, img_h - 1)) w = xmax - xmin h = ymax - ymin # 坑2:宽高为0的退化框直接丢弃 if w <= 0 or h <= 0: return None cx = (xmin + xmax) / 2.0 / img_w cy = (ymin + ymax) / 2.0 / img_h nw = w / img_w nh = h / img_h # 坑3:归一化后仍可能因浮点误差略微越界 cx = min(max(cx, 0.0), 1.0) cy = min(max(cy, 0.0), 1.0) nw = min(max(nw, 0.0), 1.0) nh = min(max(nh, 0.0), 1.0) # 坑4:极小目标(面积占比过低)建议过滤,否则训练噪声大 if nw * nh < 1e-5: return None return cx, cy, nw, nh四个坑分别是坐标越界、退化框、浮点越界和极小目标。其中极小目标过滤要谨慎,如果你的业务就是要检测细裂缝,阈值不能设太高,否则正样本被大量丢弃。我一般会把面积阈值设成可配置项,训练前统计一下被过滤的框占比,超过 10% 就要回头检查标注质量。
4. 训练参数与评估:航拍小目标检测的调参重点
4.1 输入分辨率与 anchor 设置
航拍小目标检测最核心的参数是输入分辨率。YOLOv8 默认 640,对裂缝来说远远不够,建议直接上 1280 甚至 1536。代价是显存和推理时间翻倍,但这是必要投入。如果显存吃紧,用梯度累积或者混合精度顶一顶,别降分辨率。
anchor 方面,如果用的是 anchor-based 框架,必须重新聚类。COCO 的 anchor 尺寸是为自然图像设计的,和航拍裂缝的细长形态完全不匹配。用 k-means 在你的训练集标注上重新聚一遍,通常能得到一批又细又长的 anchor,召回率会明显改善。anchor-free 框架(如 YOLOv8、FCOS)省了这一步,但要注意正样本分配策略,小目标容易被分配到过少的正样本。
4.2 数据增强:哪些能用哪些会翻车
航拍场景下增强要克制。翻转、旋转 90 度、亮度对比度扰动、轻微高斯噪声,这些是安全的。但有几类增强会直接破坏语义:
- 随机裁剪:可能把裂缝裁断,标注框和图像对不上,慎用。
- Mosaic:YOLO 系列默认开,但航拍图拼接后会出现不自然的边界,模型可能学到拼接缝特征,建议降低概率或关闭。
- 透视变换:航拍本身已经是俯视,再做透视会让病害形态失真,不建议。
我一般会把 Mosaic 概率降到 0.3 以下,同时开启 HSV 扰动来模拟不同光照。如果数据集本身光照多样性够,增强可以更保守。
4.3 评估指标:mAP 之外还要看什么
mAP@0.5 是标配,但航拍路面检测光看这个不够。裂缝这类细长目标,IoU 稍微偏一点就掉到 0.5 以下,mAP@0.5 会低估模型能力。建议同时看 mAP@0.5:0.95 和按类别拆分的 AP,重点关注坑槽和网状裂缝这两类的表现。
更重要的是误检率。养护业务里,把正常路面报成病害的代价很高,会浪费大量人工复核。评估时单独统计 hard_negative 测试集上的误检数量,这个指标比 mAP 更能反映上线后的实际体验。
5. 避坑指南:航拍路面病害数据集最常见的五个翻车点
现象一:训练 loss 正常下降,但验证 mAP 始终在 0.2 以下。原因通常是标注框和图像不对齐,尤其是先标大图后切图的流程,坐标偏移没做对。解决方法是随机抽 20 张切图,用脚本把标注框画到图上肉眼检查,对不上的回头修转换脚本。
现象二:模型把车道线、标线全部识别成裂缝。原因是训练集里缺少这类负样本,模型没见过就默认它们也是病害。解决方法是在标注阶段显式收集车道线、井盖、水渍、树影这几类场景,作为负样本或单独类别混入训练集,数量至少几百张。
现象三:同一路段验证集 mAP 很高,换一条路就崩。这是随机划分导致的样本泄漏。解决方法是按路段或架次分组划分数据集,确保验证集的路段在训练集中完全没出现过。如果数据量不够,至少保证同一路段的相邻帧不跨集。
现象四:模型对细裂缝几乎无响应,召回率极低。两个可能原因:输入分辨率太低,或者 anchor 尺寸不匹配。先把输入提到 1280 以上试一轮,如果还不行就重新聚类 anchor。另外检查一下极小目标过滤阈值是不是设太高,把真正的细裂缝也滤掉了。
现象五:推理时同一处病害被重复检测出多个框。滑窗切图带来的边界效应,裂缝跨窗口时每个窗口都检出一段。解决方法是在推理后做跨窗口 NMS 合并,或者切图时加大重叠率并在合并阶段用 IoU 阈值去重。这个后处理逻辑必须写进推理管线,否则输出结果没法用。
6. 让数据集真正值钱:主动学习与难例回流
数据集做完不是终点。航拍路面病害的分布随季节、路段、养护进度一直在变,静态数据集训出来的模型半年后就会退化。我现在的习惯是给推理管线加一个难例回流机制:模型在线推理时,把置信度在 0.3 到 0.6 之间的检测结果自动存下来,人工复核后把确认的正样本和误检的负样本一起回流到训练集,每季度重训一次。
def collect_hard_examples(detections, image, save_dir, low=0.3, high=0.6): """ 收集置信度处于灰色区间的检测结果,供人工复核 detections: [(cls, conf, x1, y1, x2, y2), ...] """ for i, (cls, conf, x1, y1, x2, y2) in enumerate(detections): if low <= conf <= high: crop = image[int(y1):int(y2), int(x1):int(x2)] if crop.size == 0: continue # 文件名带上类别和置信度,方便复核时排序 name = f"{cls}_{conf:.2f}_{i}.jpg" cv2.imwrite(os.path.join(save_dir, name), crop)这个机制的价值在于,它让数据集从一次性产物变成持续迭代的资产。灰色区间的样本信息量最大,模型最不确定的地方,往往就是数据分布变化最剧烈的地方。坚持回流几个季度,你会发现模型在新路段上的冷启动表现明显好于只训一次的版本。
另一个值得投入的方向是多尺度训练。航拍飞行高度不固定,同一处病害在不同架次里尺度差异很大。训练时随机在 1024 到 1536 之间切换输入尺寸,能让模型对尺度变化更鲁棒。代价是训练时间增加,但比重新采集多高度数据便宜得多。
最后说个我自己的习惯:每做完一个版本的数据集,我都会留一份冻结测试集,只增不改,所有模型版本都在它上面跑一遍。这样横向对比才有意义,不然每次调完参数换个测试集,指标涨了跌了都是玄学。数据集这件事,前期多花两周把标注规范和划分逻辑定死,后面省下的是两个月的返工。希望帮到你。
本文还有配套的精品资源,点击获取