恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
VOC+YOLO双格式282张公路落石数据集,小样本目标检测训练实战
首页
资讯中心
/
VOC+YOLO双格式282张公路落石数据集,小样本目标检测训练实战
VOC+YOLO双格式282张公路落石数据集,小样本目标检测训练实战
发布时间:2026/10/9 21:19:26
简介面向智能交通与目标检测开发者专门提供公路落石场景的标注数据集支持Pascal VOC与YOLO两种主流格式适用于训练落石识别模型、验证检测算法及自动驾驶风险预警研究。压缩包共849个文件包含283张jpg图像、282个xml标注文件与284个txt标注文件整体约13.9MB其中xml用于Pascal VOC训练txt供YOLO系列模型直接读取结构清晰便于按需调用。数据集聚焦单一“stone”类别共标注632个落石实例由labelImg工具完成边界框信息完整。目前已有749人学习下载。使用时可配合主流检测框架快速构建训练流程免去自行采集与标注的时间成本同时也可作为针对落石这一高频公路风险的专用样本库辅助提升模型在实际场景下的召回率与鲁棒性。1. 只有282张的公路落石数据集VOCYOLO能训练出能用的检测模型吗看到“公路落石数据集VOCYOLO282张.zip”这个标题第一个反应通常是282张这么点数据能拿来干嘛五年前我也这么想直到在某个模拟项目X里用两百多张现场抽帧做落石检测跑出了能用的模型。结论是小样本能不能练不只看数量看标注密度、类别分布和场景覆盖。落石检测和通用目标检测不一样——场景单一、目标形态相对稳定、干扰物少它恰恰是小样本目标检测里最容易出效果的一类。这套数据集的实用价值在于它同时给了VOC和YOLO两种标注意味着你不用再花大半天写格式转换脚本解压之后可以直接进训练链路。适合三类人做边坡监测或公路巡检方案验证的开发者、想学目标检测但手里没有好数据的入门者、需要做小样本可行性评估的算法工程师。接下来我会把数据体检、VOC转YOLO、训练调参和坑位一条条讲清楚。2. VOC与YOLO双格式拆解XML里的落石框和txt里的落石框差在哪2.1 VOC的XML里到底存了什么VOC格式的标注文件是XML每一个图像对应一个同名的XML文件。我一般会先打开一个标注文件看结构而不是直接跑脚本。一个典型的VOC标注文件包含这么几层信息annotation folderJPEGImages/folder filenamerock_001.jpg/filename size width1920/width height1080/height depth3/depth /size object namerock/name poseUnspecified/pose truncated0/truncated difficult0/difficult bndbox xmin523/xmin ymin401/ymin xmax618/xmax ymax488/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 XMLYOLO 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(parentsTrue, exist_okTrue) label_dir.mkdir(parentsTrue, exist_okTrue) # 类别顺序必须固定顺序决定了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, encodingutf-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 \ datafalling_rock.yaml \ modelyolov8s.pt \ epochs300 \ imgsz640 \ batch16 \ patience60 \ lr00.005 \ lrf0.01 \ mosaic1.0 \ close_mosaic10 \ fliplr0.3 \ scale0.54.2 参数含义与损失观察逐个说这里面的关键参数这部分是调参的核心。epochs300不是随便拍的282张的小数据集模型很容易在50个epoch内收敛但落石是小目标前期学轮廓后期学细节给足epochs配合早停才靠谱。patience60表示验证集指标连续60个epoch不提升就停止训练小数据集的防止过拟合大杀器。lr0初始学习率从默认的0.01降到0.005因为落石数据量少学习率过高会导致前期损失剧烈振荡模型学不到稳定的特征。mosaic1.0和close_mosaic10是一对组合。mosaic把四张图拼成一张训练显著增加小目标样本量但mosaic生成的拼接图里目标边界常常被切断所以最后10个epoch关掉mosaic让模型在干净的原始图上精调。fliplr0.3水平翻转概率被刻意压低因为落石的纹理特征有方向性翻转太多会让模型把纹理方向也学进去反而干扰泛化。scale0.5控制随机缩放的幅度落石尺寸变化本来就大scale给0.5可以让模型见到更多尺度的落石。训练过程中不要只盯着mAP看。我习惯看三个曲线的走向训练损失是否稳步下降、验证损失是否在某个epoch后开始回升、验证集的recall是否在缓慢上升。最需要警惕的是验证损失在100轮后持续上升而训练损失还在降这是典型的过拟合信号正确做法是提前把patience减小或者增加数据增强强度。命令跑起来之后训练日志里还会打印每个类别的精度、召回率、mAP50和mAP50-95。对落石这种小目标我更看重recall而不是precision——漏报一块落石的代价远高于误报一次落石检测宁可多报警也不能漏掉。所以如果看到precision很高但recall只有0.7左右优先检查是不是验证集里的小目标框太多了。5. 避坑清单落石数据集的5个常见坑与排查方法5.1 训练中断后一切归零恢复训练恢复了个寂寞现象训练过程中断重新用同样的命令跑损失从初始值开始降之前100轮的训练白费了。原因没有在命令里指定resumeYOLO重新读的是预训练权重而不是你中途保存的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字段控制常用做法设freeze10。这样做的原因是落石的纹理和边缘信息在浅层特征里和通用物体有大量共享不需要重新学。6.2 多尺度推理和测试时增强训练是640输入推理时试试960分辨率。落石在原始1080p画面里本来就小640缩放下可能只剩十几个像素检测头很难发力。把推理分辨率提到960后小目标的特征图响应面积变大recall能肉眼可见地提升。对单张图片推理耽搁的几十毫秒在边坡监测这种非实时巡检场景下完全能接受。6.3 负样本挖潜和难例回灌这是我最想强调的一个技巧。训练完成后用模型去跑原始视频里那些没有落石的帧如果模型输出了误检框把这些帧加入训练集作为负样本再用模型跑置信度在0.3到0.6之间、标注里接近但没有完全对准的落石框手动修正后作为难例回灌训练集。两轮迭代之后误报和小目标漏检问题基本都能压下去。一个月前我做模拟项目X的落石检测时就是用这个办法把现场误报从每百帧7次压到了2次以内效果比调三天参数都明显。我现在的习惯是任何小样本数据集到手都先体检再进管线转换脚本必须带坐标裁切训练时紧紧盯住recall而不是只看mAP。这套流程从数据拆包到出模型两小时之内能跑通第一版后面的每一点提升都建立在能复现的步骤上。落石检测这种垂直场景标注贵、数据难攒能把现有的一百多两百张数据榨干比到处找更多数据更现实。希望帮到你。本文还有配套的精品资源点击获取