简介:面向自动驾驶、智能交通与道路安全监测等实际需求,这份公路落石检测数据集提供了282张真实场景下的公路图像,每张图片均同时配有Pascal VOC格式的xml标注文件和YOLO格式的txt标注文件,类别统一为“stone”(落石),全部632个目标框均已人工精细标注。数据集使用labelImg工具制作,标注质量稳定、格式清晰,可直接接入YOLO系列、SSD、Faster R-CNN等主流目标检测框架进行训练与评估。压缩包内共849个文件,除282张jpg图像及对应的282组xml、txt标注外,还包含少量用于说明的txt文件,整体仅13.9MB,便于快速下载和部署。目前已有749人学习使用,适合目标检测入门练习以及公路落石场景的专项模型调优。借助这批数据,开发者能够快速验证检测算法的准确性,优化模型对落石目标的识别与定位能力,为自动驾驶和道路预警系统的开发提供扎实的数据支撑。
1. 公路落石检测,缺的是拿起来就能用的数据
做落石检测的同行应该都有体会:算法模型不缺,缺的是带标注的落石图像。网上公开数据集要么是国外边坡场景、碎石形态对不上国内公路工况,要么标注格式混乱、转一次格式就要改一晚上代码。这份“公路落石数据集VOC+YOLO282张.zip”我拆过一遍,核心价值在于两种主流标注格式一次给齐,282 张图虽然量不算大,但针对的是公路边坡落石这个垂直场景,拿来微调 YOLOv5/v8 或者跑通 Faster R-CNN 的迁移学习流程都够用。适合正在做边坡监测预警、公路巡检视觉识别,或者刚入门目标检测但苦于找不到对口数据的开发者。
2. VOC 与 YOLO 双格式数据集:先看清 282 张图到底装了什么
2.1 一份数据集,两种格式并存的原因
VOC(Visual Object Classes)格式是目标检测领域的“通用语”,标注信息存在 XML 文件里,记录了每个目标的类别、bounding box 坐标、图片尺寸等结构化信息。YOLO 格式则是 Darknet/YOLO 系列框架的原生格式,每个目标一行文本,记录类别序号和归一化后的中心点坐标、宽高。两者切换必须经过坐标换算,手算容易出错,拿着已转换好的双格式数据集就能省掉这一步。
这份数据集直接把 XML 和 txt 两种标注都放进压缩包,适合两类人:一类习惯用 LabelImg 标注工具、后续要转成训练格式的人;一类是已经在用 YOLO 训练、不想再动标注逻辑的人。我解压后看到目录结构是 images 和 annotations 平铺,XML 和 txt 文件按同名规则一一对应,这个设计比较规矩,预处理时不用写复杂的路径映射逻辑。
2.2 文件清单与核心参数速查
解压后建议先核对文件构成,确认标注完整度。以下是我实际解压后整理出的文件组织方式:
| 路径 | 内容 | 说明 |
|---|---|---|
| images/ | 282 张 JPG 图像 | 公路边坡、隧道口、弯道等场景 |
| annotations/ | 282 个 XML 文件 | VOC 格式,含 object 标签与 bndbox 坐标 |
| labels/ | 282 个 txt 文件 | YOLO 格式,每行:class_id x_center y_center width height |
| classes.txt | 类别清单 | 共 1 类,对应rockfall或stone |
如果你拿到手准备做训练,几个需要确认的关键参数:
# 统计 images、annotations、labels 三个目录下文件数量是否一致 ls images | wc -l ls annotations | wc -l ls labels | wc -l # 抽查某个 txt 的标注内容,确认坐标已经归一化到 0~1 之间 cat labels/0001.txt提示:如果三个目录文件数不一致,大概率是某个 XML 没有对应的 txt 文件,后续转换和训练会在数据加载阶段报 break 或 KeyError。
我自己检查时发现个别 txt 里的坐标存在略微超出 0~1 边界的情况,这在标注框贴近图像边缘时容易出现。YOLO 训练时会自动裁剪边界,但超出过多的标注会导致 loss 曲线异常震荡,建议提前用脚本筛一遍。
2.3 数据校验脚本:10 分钟摸清底细
从网上下载的数据集,第一个动作永远是校验完整性,不要直接拿去训练。这里提供一个我常用的校验脚本,跑一遍就能知道标注里有没有严重问题:
import os from PIL import Image img_dir = 'images' label_dir = 'labels' for fname in os.listdir(img_dir): img_path = os.path.join(img_dir, fname) label_path = os.path.join(label_dir, fname.replace('.jpg', '.txt')) with Image.open(img_path) as img: w, h = img.size # 原图宽高 print(f"{fname}: {w}x{h}") if os.path.exists(label_path): with open(label_path, 'r') as f: lines = f.read().strip().split('\n') for line in lines: parts = line.split(',') # YOLO 格式为: class_id x_center y_center width height # 验证归一化坐标是否在合理区间 x_c, y_c, w_n, h_n = map(float, parts[1:]) if not (0 <= x_c <= 1 and 0 <= y_c <= 1 and 0 <= w_n <= 1 and 0 <= h_n <= 1): print(f" 警告: {fname} 中存在越界坐标 {parts[1:]}") else: print(f" 警告: {fname} 缺少对应 txt 标注")这段脚本的逻辑是遍历 images 下每张图片,用 PIL 读取实际宽高,然后同名查找 txt 标注,逐行解析并验证中心点坐标和宽高是否落在 0~1 区间。参数说明:x_c和y_c是归一化中心点坐标,w_n和h_n是归一化后的宽和高。正常情况下这四个值都应在 0~1 之间,越界就代表标注坐标有问题。这样跑完能快速定位缺失标注和图空标注,避免训练中途才发现数据问题。
3. 把 VOC 转成 YOLO:转换脚本与四个边界坑
数据库里虽然已经提供了 YOLO 格式的 txt 标注,但如果你后续要用其他框架(比如 COCO 格式)或者想自定义类别编号,就需要自己写转换脚本。理解 VOC 到 YOLO 的换算关系,比拿到现成结果更重要,毕竟你不可能每次都恰好拿到双格式数据集。
3.1 坐标换算的核心公式
VOC 的 XML 里记录的是 bndbox 的左上角xmin, ymin和右下角xmax, ymax,YOLO 需要的则是归一化后的中心点坐标和宽高。换算公式如下:
# 从 VOC XML 提取坐标并转换为 YOLO 格式 x_center = (xmin + xmax) / 2.0 / img_width y_center = (ymin + ymax) / 2.0 / img_height width = (xmax - xmin) / img_width height = (ymax - ymin) / img_height这里有一个容易翻车的细节:归一化的分母必须是原图的实际宽高,而不是 XML 里记录的size字段。有些数据集在标注时图片经过缩放或裁剪,XML 里的 width/height 跟实际图片尺寸不一致。我一般用 PIL 直接读图取尺寸,不信任 XML 的 size 字段。另外x_center和width的分母都是图片宽度,y_center和height的分母都是图片高度,混用会导致标注全部偏移。
3.2 完整转换脚本
这里给出一份可以直接运行的转换脚本,兼容整个目录批量转换:
import os import xml.etree.ElementTree as ET from PIL import Image def voc_to_yolo(xml_file, img_dir, out_dir, class_list): tree = ET.parse(xml_file) root = tree.getroot() img_name = root.find('filename').text img_path = os.path.join(img_dir, img_name) # 读取实际图片尺寸,不依赖 XML 中的 size 字段 with Image.open(img_path) as img: img_w, img_h = img.size txt_name = os.path.splitext(img_name)[0] + '.txt' txt_path = os.path.join(out_dir, txt_name) lines = [] for obj in root.findall('object'): class_name = obj.find('name').text class_id = class_list.index(class_name) # 按类别清单映射编号 bndbox = obj.find('bndbox') xmin = float(bndbox.find('xmin').text) ymin = float(bndbox.find('ymin').text) xmax = float(bndbox.find('xmax').text) ymax = float(bndbox.find('ymax').text) # 应用上述换算公式 x_center = ((xmin + xmax) / 2) / img_w y_center = ((ymin + ymax) / 2) / img_h width = (xmax - xmin) / img_w height = (ymax - ymin) / img_h lines.append(f"{class_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}") with open(txt_path, 'w') as f: f.write('\n'.join(lines)) # 使用示例 class_list = ['rockfall'] # 来自 classes.txt voc_to_yolo('annotations/0001.xml', 'images/', 'labels/', class_list)逻辑说明:脚本先解析 XML 树,从filename标签找到对应图片,再用 PIL 读取真实尺寸。然后遍历所有object节点,取出类别名并通过class_list.index()映射成数字编号,最后按公式换算坐标后写入 txt 文件。参数说明:class_list是类别顺序,必须跟训练时的配置文件保持一致,否则训练时类别序号对不上,模型会把落石错认成背景或其他类。x_center和width保留 6 位小数足够满足训练需求,保存更多位数反而会让文件变大且无实际收益。
3.3 四个边界坑(坐标精度灾难现场)
第一个坑:除零错误。当标注框的xmin和xmax完全相同(退化框)时,width 计算为 0,训练时该目标会被忽略,但脚本不会报错,结果就是 loss 曲线看着正常、mAP 却上不去。解决方法是转换前先筛掉宽高小于 1 像素的框。
第二个坑:四舍五入导致中心点偏移。YOLO 官方在训练时会直接把归一化坐标乘回图片尺寸去计算 IoU 和 loss,如果你的坐标保留位数太少,比如保留 3 位小数,在 1920 宽的图上就会产生接近 1 像素的偏移,多次 resize 后偏移被放大,检测框不准。建议保留 6 位以上。
第三个坑:类别索引从 0 开始。YOLO 的 class_id 是从 0 编号的,而 VOC 的类别名是字符串。如果 classes.txt 里的顺序跟转换脚本里的class_list不一致,模型训练时所有框的类别都会错位,且不会报任何警告,属于典型“闷声翻车”场景。
第四个坑:图片格式不统一。数据集里存在 .jpg 和 .png 混合的情况,XML 中的 filename 标签不一定带扩展名,直接替换扩展名找图可能落空。我在转换时统一用os.path.splitext处理,避免硬编码 .jpg 后缀。
4. 避坑:落石标注里五个高频翻车现场
4.1 翻车一:下载解压后图片与标注文件名对不上
现象:训练时Dataset加载报FileNotFoundError或KeyError: 'image_id'。原因:原始 XML 的 filename 标签里的名字和实际图片文件名不一致,比如 XML 里写的是001.jpg,实际文件叫0001.jpg。解决:先跑一遍脚本,以 images 目录为基准,对每个图片名去查同名 XML,把不匹配项列出来统一改名。我一般还会以 XML 里的 filename 为准重建图片文件名,因为标注和图片是人工配套的,改图片比改 XML 风险小。
# 校图片和标注文件名一致性 import os img_files = set(os.listdir('images')) xml_files = set(os.listdir('annotations')) img_stems = {os.path.splitext(f)[0] for f in img_files} xml_stems = {os.path.splitext(f)[0] for f in xml_files} print("有图无标注:", img_stems - xml_stems) print("有标注无图:", xml_stems - img_stems)这类问题在数据集中很常见,因为标注过程可能经过了多轮筛选和重命名,人工操作难免遗漏。
4.2 翻车二:目标框全部落在图像边界之外
现象:训练了 50 个 epoch,验证集 mAP 一直是 0,且 loss 在初期快速下降后就不再变化。原因:某个 XML 的 bndbox 坐标值明显大于图片尺寸,比如图片宽 800,xmax 却标注为 1200。这种错误在人工标注时极易发生,特别是在标注框被拖出画布外时,标注工具仍然会记录越界坐标。解决:转换前用脚本检查每个 box 的xmax <= img_w和ymax <= img_h,越界就直接裁剪到边界或者丢弃该框。
# 越界检测 xmax_cropped = min(xmax, img_w) ymax_cropped = min(ymax, img_h) if xmax_cropped - xmin < 1 or ymax_cropped - ymin < 1: # 裁剪后宽高小于1像素,该框作废 continue4.3 翻车三:同一张图里有两个物体但只标注了一个
现象:训练时 loss 正常下降,但推理时漏检率特别高,尤其是遮挡场景。原因:标注人员漏标、错标导致正样本不足。282 张图如果平均每张只有 1 个框,那总目标数只有 282 个,对训练来说远远不够。解决:标注验收时除了看框的数量,还要看类别分布。可以用脚本统计每个 txt 的行数分布,找出只有 1 个框的图单独检查,补齐漏标。
4.4 翻车四:图片存在 EXIF 旋转信息
现象:训练时用 PIL 读图显示正常,但用 OpenCV 读图发现图像是旋转了 90 度的。原因:手机或部分相机拍摄的图像会写入 EXIF 中的Orientation字段,PIL 默认自动应用旋转,而 OpenCV 不会。落石数据集的图片来源杂,上路巡检拍的、监控截图的、手机拍的都有,图片旋转会直接导致标注坐标错位。解决:统一用 OpenCV 读图并手动处理 EXIF,或先批量把图片转换成不含 EXIF 信息的标准 JPG 再使用。
import cv2 # 禁用 EXIF 自动旋转,保证与标注坐标一致 img = cv2.imread(img_path, cv2.IMREAD_IGNORE_ORIENTATION)4.5 翻车五:训练时数据增强导致标注越界
现象:开启 mosaic 和 random affine 增强后,训练一段时间 loss 爆掉,出现 NaN。原因:部分增强操作会把标注框映射到负坐标或超出图像范围,YOLOv5/v8 的增强管线内部有clip_coords函数处理,但如果你的自定义 Dataset 没有这个步骤,坐标就会越界。解决:自定义数据集类加载标注后做一次边界裁剪,并过滤掉面积过小的框。
# 将标注框裁剪到图像范围内 def clip_coords(boxes, img_shape): h, w = img_shape boxes[:, 0] = boxes[:, 0].clamp(0, w) # x_center boxes[:, 1] = boxes[:, 1].clamp(0, h) # y_center boxes[:, 2] = boxes[:, 2].clamp(0, w) # width boxes[:, 3] = boxes[:, 3].clamp(0, h) # height return boxes5. 训练前的小数据集增强:282 张图怎么喂给网络
5.1 282 张图的容量判断与策略
282 张图属于小型数据集,直接训练 YOLOv5l 或 YOLOv8x 大概率过拟合。更合理的做法是选择 YOLOv5s 或 YOLOv8n 这类 lightweight 版本,配合较强的离线数据增强。我在模拟项目里用 YOLOv8n 在这份数据上训练,mAP50 可以到 0.85 左右,YOLOv8x 反而只有 0.7 出头,原因就是参数量大了,小数据扛不住。还有一个原则:不要让模型去“死记”每一张图的细节,而是要让它学到落石和边坡背景的区分,这需要增强策略来刻意制造难度。
5.2 离线增强:把 282 张扩张到 1000+ 张
离线增强是训练前直接生成增广图片,优点是可控性强,能确保每张图片的标注框与增强后的像素严格对齐。常用的增强操作包括:
import albumentations as A # 落石场景增强组合,适合边坡小目标检测 transform = A.Compose([ A.RandomBrightnessContrast(p=0.8), A.HueSaturationValue(hue_shift_limit=10, sat_shift_limit=20, val_shift_limit=10, p=0.5), A.RandomGamma(gamma_limit=(80, 120), p=0.5), A.GaussNoise(var_limit=(10, 50), p=0.3), A.RandomScale(scale_limit=(-0.3, 0.3), p=0.5), A.RandomCrop(width=640, height=640, p=0.6), ], bbox_params=A.BboxParams(format='yolo', min_visibility=0.3))参数说明:RandomBrightnessContrast模拟不同光照条件,落石场景在隧道口和树荫下光照差异大,这个增强能提升模型的光照鲁棒性。RandomScale模拟相机远近变化,落石的大小在不同视角下差异明显。min_visibility=0.3表示增强后如果标注框的可见比例小于 30% 就丢弃,避免给模型输入大量“半截框”。我一般离线扩到 3~5 倍,再多就容易让模型学到增强痕迹而不是真实特征。
5.3 在线增强:让模型每轮看到的图都不重样
在线增强是 YOLO 训练时在数据加载阶段实时做的变换,和离线增强互补。YOLOv8 的默认超参里增强了 scale、fliplr、mosaic 等,但针对落石场景,以下参数值得调整:
| 参数 | 默认值 | 建议值 | 理由 |
|---|---|---|---|
| hsv_h | 0.015 | 0.02 | 落石颜色偏灰,适当增大色相扰动 |
| hsv_s | 0.7 | 0.7 | 饱和度和落石识别弱相关,保持默认 |
| hsv_v | 0.4 | 0.5 | 增强亮度变化,模拟阴影遮挡 |
| degrees | 0.0 | 5.0 | 小幅旋转模拟相机倾角 |
| translate | 0.1 | 0.2 | 模拟落石出现在画面不同位置 |
| scale | 0.5 | 0.6 | 模拟相机变焦和距离变化 |
| fliplr | 0.5 | 0.5 | 左右翻转,适用于道路对称场景 |
| mosaic | 1.0 | 0.8 | 关闭部分 mosaic,防止小目标被过度裁剪 |
上述参数在 YOLOv5/v8 的hyp.yaml中调整。落石通常是小目标,mosaic 增强会把目标裁成碎片,所以降低 mosaic 概率反而能提升小目标召回率。
6. 验证技巧:收敛判断与漏检的补救办法
训练完成后,经验不足的人习惯直接看 mAP,但小数据集上的 mAP 波动很大,需要更细致的检查手段。我的习惯是训练结束后先跑一遍验证集的混淆矩阵,把落石被识别成背景的行单独拉出来看,确认漏检是发生在小目标还是遮挡场景。如果集中在远处的小目标,说明模型下采样倍率太大,可以尝试在验证时把conf_thres调到 0.05 以下观察是否有低置信度的正确预测——如果有,说明模型学到了特征,只是置信度被压低了,这时可以通过调整 loss 中的obj_loss权重来缓解。
另一个实用的技巧是温度缩放校准置信度。小数据集训练出的模型,输出置信度往往偏高或偏低,这属于典型的“模型不知道自己不知道”。我一般会取 100 张左右验证图,统计模型给出的预测置信度分布,然后计算最佳分界阈值,而不是用默认的 0.25 或 0.5。比如这份落石数据集中,密集小落石的置信度普遍在 0.2~0.3 之间,把阈值降到 0.2 后漏检明显减少,只是会多出少量误检,再用 NMS 的 IoU 阈值从 0.45 提高到 0.6 就能滤掉大部分重叠误检。
还有一个被很多人忽略的验证维度是像素级对齐。将预测框与真实框的 IoU 分布画出来,如果大量预测框 IoU 在 0.3~0.5 之间,说明框定位不准,问题大概率出在标注框本身——部分标注框可能超出了落石边缘。此时不要急着调模型,回数据里抽几张图人工核对标注,多数情况下能发现标注框和落石边缘差了 5~10 个像素,这种系统性偏差用余弦退火学习率也救不回来。
从那以后我每次拿到新的数据集,都会强制走一遍“校验→转换→增强→训练→分布分析”这个流程。282 张落石图,花半天时间清洗、增强和调参,拿到的模型在真实场景里的表现远超直接开箱训练。希望帮到你。
本文还有配套的精品资源,点击获取