简介:面向智能交通与目标检测开发者,专门提供公路落石场景的标注数据集,支持Pascal VOC与YOLO两种主流格式,适用于训练落石识别模型、验证检测算法及自动驾驶风险预警研究。压缩包共849个文件,包含283张jpg图像、282个xml标注文件与284个txt标注文件,整体约13.9MB,其中xml用于Pascal VOC训练,txt供YOLO系列模型直接读取,结构清晰便于按需调用。数据集聚焦单一“stone”类别,共标注632个落石实例,由labelImg工具完成,边界框信息完整。目前已有749人学习下载。使用时可配合主流检测框架快速构建训练流程,免去自行采集与标注的时间成本;同时也可作为针对落石这一高频公路风险的专用样本库,辅助提升模型在实际场景下的召回率与鲁棒性。
1. 只有282张的公路落石数据集VOC+YOLO,能训练出能用的检测模型吗
看到“公路落石数据集VOC+YOLO282张.zip”这个标题,第一个反应通常是:282张,这么点数据,能拿来干嘛?五年前我也这么想,直到在某个模拟项目X里用两百多张现场抽帧做落石检测,跑出了能用的模型。结论是:小样本能不能练,不只看数量,看标注密度、类别分布和场景覆盖。落石检测和通用目标检测不一样——场景单一、目标形态相对稳定、干扰物少,它恰恰是小样本目标检测里最容易出效果的一类。
这套数据集的实用价值在于它同时给了VOC和YOLO两种标注,意味着你不用再花大半天写格式转换脚本,解压之后可以直接进训练链路。适合三类人:做边坡监测或公路巡检方案验证的开发者、想学目标检测但手里没有好数据的入门者、需要做小样本可行性评估的算法工程师。接下来我会把数据体检、VOC转YOLO、训练调参和坑位一条条讲清楚。
2. VOC与YOLO双格式拆解:XML里的落石框和txt里的落石框差在哪
2.1 VOC的XML里到底存了什么
VOC格式的标注文件是XML,每一个图像对应一个同名的XML文件。我一般会先打开一个标注文件看结构,而不是直接跑脚本。一个典型的VOC标注文件包含这么几层信息:
<annotation> <folder>JPEGImages</folder> <filename>rock_001.jpg</filename> <size> <width>1920</width> <height>1080</height> <depth>3</depth> </size> <object> <name>rock</name> <pose>Unspecified</pose> <truncated>0</truncated> <difficult>0</difficult> <bndbox> <xmin>523</xmin> <ymin>401</ymin> <xmax>618</xmax> <ymax>488</ymax> </bndbox> </object> </annotation>关键信息就三块:filename对应哪张图,size记录原始宽高,object下的bndbox记录落石框的绝对像素坐标。这里最容易翻车的点是xmin、ymin、xmax、ymax的顺序,一旦有人把ymin和ymax写反,后续转换出来的标注全是错的。另一个容易被忽略的是truncated和difficult标签:落石在图像边缘被截断时,truncated标记为1;目标太小难以确认时,difficult标记为1。这两个标签在训练时应该按需求过滤,否则会往模型里灌噪声样本。
拿到一个VOC数据集,我习惯先统计XML里出现过的所有类别名。落石数据集的类别名可能叫什么都有:rock、stone、falling_rock、rock_fall,甚至可能混入person、car这种误标注。类别名不统一是VOC转YOLO时最常见的坑之一,因为你没法确定哪个名字该映射到哪个类别id。
2.2 YOLO的txt标注为什么更适合落石这类小目标
YOLO格式的标注是纯文本,每行对应一个目标:
0 0.2971 0.4116 0.0495 0.0806一行五列,依次是类别id、归一化后的中心点x、中心点y、宽、高。所有坐标都除以图像原宽高,所以数值都在0到1之间。这个设计的妙处在于:当图像从1920x1080缩放到640x640时,标注不需要做任何换算,模型训练时直接读归一化坐标就能用。
对落石这种目标来说,YOLO格式还有一个潜在优势——小目标框的数值精度。落石在1080p画面里往往只有几十到一两百像素宽,归一化之后只有0.05左右,float精度下的损失计算更容易收敛。VOC的绝对像素坐标在缩放后会引入误差,虽然影响一般不大,但小目标场景里能不引入误差就不引入。
两者对比还有一个实操差异:VOC标注文件不带你标注类别清单,类别是从XML里动态读出来的;YOLO格式则依赖一个classes.txt文件,每个类别名的顺序就是类别id,顺序一变,整个训练就废了。所以我在任何项目里处理双格式数据集,第一件事就是固定类别顺序,并且把这份顺序写进训练配置,不能被脚本自动扫描结果改变。
| 维度 | VOC XML | YOLO txt |
|---|---|---|
| 坐标体系 | 绝对像素坐标 | 相对归一化坐标 |
| 类别表达 | 类别名,写在name标签里 | 整数类别id,需配合classes.txt |
| 是否需图片宽高 | 需要,size标签里给出 | 不需要,归一化后与分辨率无关 |
| 多目标存储 | 多个object标签 | 每行一个目标 |
| 扩展信息 | 有truncated/difficult | 无,纯检测框 |
从训练链路看,YOLO格式直接进训练器,VOC格式进不了;从标注可读性看,VOC格式可以用标注工具直接打开检查。所以这套数据集给双格式不是重复劳动,而是让“先用VOC检查标注质量,再转YOLO开训练”成为最顺的路径。
3. 数据体检与VOC转YOLO:先查三类问题,再跑转换脚本
3.1 用Python统计类别、实例数与图片尺寸
很多人拿到数据集解压完就开始训练,这是小样本项目最大的浪费。只有282张图,任何标注错误都会被模型放大。我这几年带小样本项目,第一步永远是写十几行脚本做数据体检,绝不跳过。
import os import xml.etree.ElementTree as ET from collections import Counter # 按实际解压路径修改 voc_root = "./VOCdevkit/VOC2023" anno_dir = os.path.join(voc_root, "Annotations") cat_counter = Counter() size_counter = Counter() instance_total = 0 problem_files = [] for xml_name in os.listdir(anno_dir): if not xml_name.endswith(".xml"): continue xml_path = os.path.join(anno_dir, xml_name) try: tree = ET.parse(xml_path) root = tree.getroot() except ET.ParseError: problem_files.append(xml_name + " -> XML解析失败") continue # 读取图片宽高 size_node = root.find("size") w = int(size_node.find("width").text) h = int(size_node.find("height").text) size_counter[(w, h)] += 1 # 遍历所有目标 for obj in root.iter("object"): name = obj.find("name").text cat_counter[name] += 1 instance_total += 1 box = obj.find("bndbox") xmin = float(box.find("xmin").text) ymin = float(box.find("ymin").text) xmax = float(box.find("xmax").text) ymax = float(box.find("ymax").text) # 检查坐标合法性:宽高必须为正 if xmin >= xmax or ymin >= ymax: problem_files.append(xml_name + " -> 标注框畸形") # 检查坐标是否超出图像范围 if xmin < 0 or ymin < 0 or xmax > w or ymax > h: problem_files.append(xml_name + " -> 坐标越界") print("类别分布:", dict(cat_counter)) print("实例总数:", instance_total) print("图像尺寸:", dict(size_counter)) print("问题标注:", problem_files if problem_files else "无")这段体检脚本做了四件事:检查XML能否正常解析、统计每个类别的实例数、统计图片尺寸分布、检查标注框的合法性和越界情况。跑完之后你会对数据集有非常直观的判断:282张图里落石实例有多少、是不是集中在少数几张图、图片分辨率是否统一、有没有标注错误。
参数方面有两点要注意。第一,类别名统计结果决定了后续classes.txt的内容,如果出现多个不同类别名表示同一事物,必须合并。第二,图片尺寸分布很重要——如果混杂了1920x1080和720x576两种分辨率,训练时统一缩放到640会引起部分小目标更小,需要在转换时单独考虑。
3.2 VOC转YOLO的转换脚本与边界坑
做完体检,接下来就是把VOC标注转成YOLO标注。这个转换看起来是线性的数学换算,实际操作中有一堆边界坑。我写转换脚本时会把坐标裁切、畸形框过滤、类别顺序固定这些逻辑全部做进去。
import os import xml.etree.ElementTree as ET from pathlib import Path voc_root = Path("./VOCdevkit/VOC2023") save_root = Path("./falling_rock") # 目标YOLO数据集根目录 image_dir = save_root / "images" label_dir = save_root / "labels" image_dir.mkdir(parents=True, exist_ok=True) label_dir.mkdir(parents=True, exist_ok=True) # 类别顺序必须固定,顺序决定了YOLO的类别id class_names = ["rock"] # 以实际体检结果为准 cls2id = {name: i for i, name in enumerate(class_names)} valid_images = [] for xml_path in sorted((voc_root / "Annotations").glob("*.xml")): tree = ET.parse(xml_path) root = tree.getroot() img_name = root.find("filename").text src_img = voc_root / "JPEGImages" / img_name if not src_img.exists(): print("图片缺失:", img_name) continue w = int(root.find("size").find("width").text) h = int(root.find("size").find("height").text) lines = [] skipped = 0 for obj in root.iter("object"): cls = obj.find("name").text if cls not in cls2id: print("未知类别:", cls, "已跳过") continue box = obj.find("bndbox") xmin = float(box.find("xmin").text) ymin = float(box.find("ymin").text) xmax = float(box.find("xmax").text) ymax = float(box.find("ymax").text) # 越界坐标裁切到图像边缘,避免归一化出现负数 xmin = max(0.0, min(xmin, w - 1)) ymin = max(0.0, min(ymin, h - 1)) xmax = max(0.0, min(xmax, w - 1)) ymax = max(0.0, min(ymax, h - 1)) # 裁切后如果框没宽度了,直接丢弃 if xmax <= xmin or ymax <= ymin: skipped += 1 continue # 转YOLO格式:class_id cx cy w h cx = ((xmin + xmax) / 2) / w cy = ((ymin + ymax) / 2) / h bw = (xmax - xmin) / w bh = (ymax - ymin) / h lines.append(f"{cls2id[cls]} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}") if lines: # 生成同名txt标签文件 out_txt = label_dir / (xml_path.stem + ".txt") out_txt.write_text("\n".join(lines) + "\n", encoding="utf-8") # 复制对应图片到images目录 (image_dir / img_name).write_bytes(src_img.read_bytes()) valid_images.append(img_name) else: print("无有效标注:", xml_path.stem) if skipped: print(f"{xml_path.stem}: 跳过{skipped}个畸形框") print("转换完成,有效图片数:", len(valid_images))脚本的核心逻辑是把VOC的绝对坐标转成归一化的中心点加宽高。这里有三个必须处理的边界。第一个是坐标越界:VOC标注里可能出现xmax大于图片宽度的情况,直接归一化会产生大于1的数值,YOLO训练时这种标签会导致损失计算异常甚至NaN。第二个是需要裁切后重新判定框是否有效:如果越界非常严重,裁切后宽度变成0,这种框必须丢弃而不是保留。第三个是类别映射:脚本里用class_names定义类别顺序,这个顺序和生成的txt文件里的id一一对应,之后写YOLO训练配置文件data.yaml时,classes列表必须和这里完全一致。
转换完成后,我每次还会再验证一次:随机挑几个txt文件,把归一化坐标乘回图片宽高,画框确认是否和原图落石位置吻合。这个习惯帮我避过好几次“脚本看起来没问题但输出全错”的翻车事故。
4. YOLO训练落石检测:目录组织、训练命令与三个必调参数
4.1 数据集目录组织与训练命令
转换完成后,数据集要组织成YOLO训练的标准目录结构。常见做法是images和labels两个主目录,各自下面再按train、val、test划分子目录。
falling_rock/ ├── images/ │ ├── train/ │ │ ├── rock_001.jpg │ │ └── ... │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ │ ├── rock_001.txt │ │ └── ... │ ├── val/ │ └── test/ └── falling_rock.yaml注意划分的时候图片和标签要保持文件名一一对应。我一般会把图片文件名列表按视频来源分组后再划分,而不是随机打乱——如果同一段视频的连续帧既进了训练集又进了验证集,验证集的指标就会虚高,这个坑后面详细说。
对应的训练配置文件falling_rock.yaml内容是这样的:
path: ./falling_rock train: images/train val: images/val test: images/test names: 0: rocknames的顺序和索引必须和转换脚本里的class_names一致,这是YOLO训练里最容易阴沟翻船的地方。如果转换时rock是id 0,这里也必须是0,任何错位都会导致训练时把背景当落石、把落石当背景,而且指标看起来很怪但不容易定位问题。
训练命令按Ultralytics系列YOLO的常见做法是这样的:
yolo detect train \ data=falling_rock.yaml \ model=yolov8s.pt \ epochs=300 \ imgsz=640 \ batch=16 \ patience=60 \ lr0=0.005 \ lrf=0.01 \ mosaic=1.0 \ close_mosaic=10 \ fliplr=0.3 \ scale=0.54.2 参数含义与损失观察
逐个说这里面的关键参数,这部分是调参的核心。epochs=300不是随便拍的:282张的小数据集,模型很容易在50个epoch内收敛,但落石是小目标,前期学轮廓后期学细节,给足epochs配合早停才靠谱。patience=60表示验证集指标连续60个epoch不提升就停止训练,小数据集的防止过拟合大杀器。lr0初始学习率从默认的0.01降到0.005,因为落石数据量少,学习率过高会导致前期损失剧烈振荡,模型学不到稳定的特征。
mosaic=1.0和close_mosaic=10是一对组合。mosaic把四张图拼成一张训练,显著增加小目标样本量,但mosaic生成的拼接图里目标边界常常被切断,所以最后10个epoch关掉mosaic,让模型在干净的原始图上精调。fliplr=0.3水平翻转概率被刻意压低,因为落石的纹理特征有方向性,翻转太多会让模型把纹理方向也学进去,反而干扰泛化。scale=0.5控制随机缩放的幅度,落石尺寸变化本来就大,scale给0.5可以让模型见到更多尺度的落石。
训练过程中不要只盯着mAP看。我习惯看三个曲线的走向:训练损失是否稳步下降、验证损失是否在某个epoch后开始回升、验证集的recall是否在缓慢上升。最需要警惕的是验证损失在100轮后持续上升而训练损失还在降,这是典型的过拟合信号,正确做法是提前把patience减小,或者增加数据增强强度。
命令跑起来之后,训练日志里还会打印每个类别的精度、召回率、mAP50和mAP50-95。对落石这种小目标,我更看重recall而不是precision——漏报一块落石的代价远高于误报一次,落石检测宁可多报警也不能漏掉。所以如果看到precision很高但recall只有0.7左右,优先检查是不是验证集里的小目标框太多了。
5. 避坑清单:落石数据集的5个常见坑与排查方法
5.1 训练中断后一切归零,恢复训练恢复了个寂寞
现象:训练过程中断,重新用同样的命令跑,损失从初始值开始降,之前100轮的训练白费了。
原因:没有在命令里指定resume,YOLO重新读的是预训练权重,而不是你中途保存的last.pt。
解决:中断后使用yolo detect train resume model=./runs/detect/train/weights/last.pt恢复训练。这里有个细节:resume时data参数会从上次训练的配置里自动读取,不需要重新指定,你只需要确保数据集文件路径没变。
5.2 验证集mAP很高,现场视频里漏检到怀疑人生
现象:训练时mAP50达到0.9以上,拿到真实场景视频里一测,漏检一大片。
原因:数据划分没按视频来源切分。公路落石数据集往往是从连续视频里抽帧标注的,相邻帧背景几乎一样、落石状态几乎一样。随机划分会把同一段视频的帧同时分到训练集和验证集,验证集等于开卷考试,指标自然虚高。
解决:划分数据集之前先看文件名或目录结构,找到视频来源标识,严格按视频段划分。如果一个视频抽了40帧,这40帧要么全进训练集、要么全进验证集,绝不能拆开。
5.3 训练没几个epoch损失就变NaN,输出全是nan
现象:训练日志里loss突然变成nan,之后每次验证都是nan,整个训练废掉。
原因:绝大多数情况下是标注坐标越界。某些落石框在图像边缘,VOC标注没有裁切,转换脚本直接归一化产生了大于1的坐标值。少数情况是学习率太高导致梯度爆炸。
解决:如果转换脚本里没有做坐标裁切,回到第3章的脚本重新转一次。做了裁切还是出现nan,把lr0降到0.001再试。两个都不行,检查是否有空的txt文件——全空标签文件某些增强策略下会产生无目标训练样本,也会触发异常。
5.4 模型把护栏、山体阴影当成了落石,误报特别多
现象:precision掉得厉害,验证集里大量假阳性框落在护栏、排水沟、固定阴影位置。
原因:281张正样本里可能没有一张负样本图像,模型没见过“没有落石的路面”长什么样。YOLO训练时负样本参与度低,模型对背景的判断容易过度自信。
解决:从原始视频里抽几十张完全没有落石的帧,放到images/train里,不配标签文件。这些负样本会告诉模型“这里没有东西可以框”。我通常会在小样本检测项目里保持大约1比5的负正样本比例。
5.5 close_mosaic忘记开,小落石越训越丢
现象:训练过程正常,损失也在降,但小目标(宽度小于32像素)的recall始终上不去。
原因:训练时mosaic增强一直开着,模型大量时间在学拼接图的特征;而且mosaic把图像缩小四倍后,小落石在合成图里只剩几个像素,梯度贡献被大目标淹没。
解决:把close_mosaic从0改成10或者更大,让最后一段训练完全用原始尺度精调。同时把imgsz从640调到768,分辨率提高后小目标的特征图响应更强。这两招对落石检测的recall提升通常能到5到15个百分点。
6. 小样本进阶:让282张落石模型更稳的三个技巧
6.1 用预训练权重冻结backbone,先训检测头
282张图别从头训练backbone,那是数据富翁干的事。正确顺序是:加载预训练权重,冻结前三层,只训练检测头10到20个epoch,再解冻整个网络低学习率微调。冻结可以通过训练参数里的freeze字段控制,常用做法设freeze=10。这样做的原因是落石的纹理和边缘信息在浅层特征里和通用物体有大量共享,不需要重新学。
6.2 多尺度推理和测试时增强
训练是640输入,推理时试试960分辨率。落石在原始1080p画面里本来就小,640缩放下可能只剩十几个像素,检测头很难发力。把推理分辨率提到960后,小目标的特征图响应面积变大,recall能肉眼可见地提升。对单张图片推理耽搁的几十毫秒,在边坡监测这种非实时巡检场景下完全能接受。
6.3 负样本挖潜和难例回灌
这是我最想强调的一个技巧。训练完成后,用模型去跑原始视频里那些没有落石的帧,如果模型输出了误检框,把这些帧加入训练集作为负样本;再用模型跑置信度在0.3到0.6之间、标注里接近但没有完全对准的落石框,手动修正后作为难例回灌训练集。两轮迭代之后,误报和小目标漏检问题基本都能压下去。一个月前我做模拟项目X的落石检测时,就是用这个办法把现场误报从每百帧7次压到了2次以内,效果比调三天参数都明显。
我现在的习惯是,任何小样本数据集到手都先体检再进管线,转换脚本必须带坐标裁切,训练时紧紧盯住recall而不是只看mAP。这套流程从数据拆包到出模型,两小时之内能跑通第一版,后面的每一点提升都建立在能复现的步骤上。落石检测这种垂直场景,标注贵、数据难攒,能把现有的一百多两百张数据榨干,比到处找更多数据更现实。希望帮到你。
本文还有配套的精品资源,点击获取