简介这是一份面向公路落石场景的目标检测数据集服务于计算机视觉与智能交通领域的开发者、研究者和算法工程师用于训练、验证和对比落石识别模型提升道路监控与自动驾驶的安全预警能力。压缩包共八百四十九个文件以道路图片及其对应的两种标注文件为主体整体大小约为十三点九兆字节标注类别仅设“落石”一类共六百三十二个目标框。数据集中包含二百八十二张公路场景图片每张图片均同时附有可扩展标记语言标注和文本标注两种格式分别对应Pascal VOC与YOLO使用灵活、便于扩展。这一数据集聚焦真实公路落石情况能支撑小尺寸目标与复杂背景下的检测实验也可用于模型鲁棒性评估与参数调优减少数据采集与标注成本。目前已有七百四十八人学习下载适合相关方向的研究与开发者直接使用。1. 公路落石检测为什么难282 张图的 VOCYOLO 数据集够用吗做一个公路落石目标检测项目手里拿到一份“VOCYOLO”282张图像的数据集第一反应基本都是“才282张能练出什么”这种反应其实把目标检测理解为从零训练了。落石检测的难点从来不在“图多图少”而在“目标小、背景杂、正负样本不平衡”。公路影像里的落石往往只有几十个像素宽和沥青裂缝、护栏阴影、隧道口光线变化长得很像模型很容易把纹理误判成石头。VOC和YOLO双格式是这类数据集最常见的组织方式VOC的XML方便做精细校验和二次筛选YOLO的txt直接喂给训练管线282张图的规模配合预训练权重和数据增强足够把一个单类落石检测器训练到能用的程度。这套数据适合公路巡检、边坡监测、地质灾害预警系统的视觉前端也适合刚接触YOLO的人拿真实场景练一次完整流程。本文会从数据集结构、格式转换、训练参数、踩坑记录一路拆到部署验证。2. 先把数据拆开看VOC 与 YOLO 两种格式在公路落石数据集里的实际布局拿到zip多数人第一件事是解压后直接翻图片。我建议反过来先看目录结构因为这份数据能不能直接喂给YOLO取决于目录和标注文件是否按约定组织。双格式看着方便实际上也意味着你有两份标注需要核对如果两份不一致训练出来的模型效果会非常迷惑。2.1 两种格式的文件构成JPEG、XML、TXT 怎么对应VOC格式是早期目标检测的标准目录里至少有JPEGImages、Annotations、ImageSets三个部分。JPEGImages放jpg原图Annotations放同名xmlImageSets/Main放train.txt、val.txt这样的划分文件。YOLO格式更简单images目录放图labels目录放同名txt每张图对应一个同名txt没有目标的图用空txt占位。282张图的双格式数据集解压后通常看到两套目录并存也有一半情况是只有一个目录但每个xml旁边多了一个txt。不管哪种第一步都是用命令行把数量查清楚# 统计三类文件数量排查解压是否完整 find . -type f -name *.jpg | wc -l find . -type f -name *.xml | wc -l find . -type f -name *.txt | wc -l如果jpg是282而xml和txt少于282说明有图片没被标注。这种情况在公路落石数据里并不罕见多半是打包时把负样本图混进了数据集。负样本图是没有任何落石的公路背景图作用是让模型知道“这段路面没有石头”而标注遗漏则是脏数据会让模型在该学习特征的地方学到空白。区分方法很简单把缺失标注的图片挑出来用看图软件翻一遍没有落石的是负样本有落石没框的是脏数据后者在训练前必须处理。xml和txt的对应关系也要检查。VOC的xml里记录的是绝对像素坐标YOLO的txt是归一化比例坐标如果两份标注都由同一份原始标注生成数值上应该能互相转换。我会抽一个文件对照着看。假设一张图宽640、高480xml里的落石框是xmin156、ymin88、xmax203、ymax141那么对应txt里的中心点x应该是(156203)/2/6400.280859中心点y是(88141)/2/4800.178906宽是(203-156)/6400.083594。如果对不上说明两份标注版本不同这时候要以VOC的xml为准重新转换因为xml保存的信息更完整txt可能是某次批处理出错的产物。YOLO格式txt每行代表一个目标字段顺序固定为“类别id 中心点x 中心点y 框宽 框高”。训练时YOLO用文件名把jpg和txt配对所以文件名必须完全一致连后缀都不能有差异。文件名对不齐是小数据集项目里最常见的低级错误后面训练时表现为图片数量对不上标注数量框架却不一定报错。2.2 落石标注粒度单类或双类以及两类之间的样本平衡公路落石数据集最常见的标注是单类类别名叫rock或stone。偶尔会看到把落石按尺寸拆成两类比如rock和gravel或者falling_rock和debris。这类拆分在学术上是合理的但在282张图的规模下并不推荐。原因是类别一旦拆分每个类别的目标数量可能只剩几百个训练时类别之间的loss权重很难平衡模型会把小碎石学成背景的一部分。我在处理这类数据时会先统计每个类别的目标数量如果少数类的目标数不足多数类的十分之一就直接把标签合并成单类。落石标注的框粒度也直接影响训练效果。落石在公路影像中的真实尺寸差异很大近景的落石能占图的四分之一远景的落石可能只有16x16像素。如果标注时把框画得比目标大一圈模型学到的目标边界就是模糊的推理时框会漂如果框太小又把落石的一部分纹理切在框外特征不完整。检查标注粒度的最快方法是批量统计所有标注框的面积看宽高分布集中在哪里。把每张图的标注框数值取出来之后如果发现一堆宽度0.002、高度0.003的框在640分辨率下对应1x2像素这种框基本是废标模型学不到任何特征。如果最大的一组接近1.0说明存在占满整张图的特写落石。两种极端框并存时YOLO的默认锚框很难同时覆盖后面训练参数章节会专门说。2.3 统计标注框分布282 张图里到底有多少个目标282张图不等于282个目标。单张公路图里落石可能一个都没有也可能一张图里有几十块碎石。训练前必须知道总目标数和框的尺寸分布这直接影响分辨率策略和锚框配置。用一段Python脚本统计import glob total 0 per_image [] sizes [] for f in glob.glob(labels/*.txt): n 0 for line in open(f): parts line.split() if len(parts) 5: w float(parts[3]) h float(parts[4]) total 1 n 1 sizes.append((w, h)) per_image.append((f, n)) print(总目标数:, total) print(图片数量:, len(per_image)) print(平均每图目标数:, total / len(per_image)) print(目标最多的图:, max(per_image, keylambda x: x[1])) print(空标注图数量:, sum(1 for _, n in per_image if n 0))这段脚本会输出三个关键数字总目标数、平均每图目标数、空标注图数量。如果平均每图目标数小于0.5说明大部分图是负样本训练时正样本严重不足如果单张图目标数上百模型需要同时处理密集小目标空标注图数量如果超过50张训练时就要注意负样本的分布避免模型偏向把所有目标都预测成背景。框的尺寸分布也能从这里得到把所有宽高按归一化值排序如果最低的一组是0.003这种量级意味着在640分辨率下只有2像素大小这种目标已低于正常人眼可辨认的极限标注很可能有问题。如果最高的一组接近1.0说明有落石特写照和远景落石完全不在一个尺度上。这两种极端情况并存时YOLO从预训练权重里带来的锚框往往不能覆盖全部目标需要靠提高输入分辨率来缓解。2.4 train/val 划分按目标数量分层而不是按图片数量分层282张图做8比2划分等于225张训练、57张验证。这里面有个红线先看两边目标数量的分布是否一致。常见翻车是随机划分后验证集里只有大落石训练集里只有小落石模型在验证集上把大落石检测得不错小落石全漏mAP就上不去。我一般会按“目标数量”而不是“图片数量”来划分先把所有图片按单张目标数排序再分层抽样保证验证集里大目标、小目标、负样本的比例和全集接近。分层划分的思路是把图片按目标数分桶再从每个桶里均匀抽20%进验证集用一小段脚本就能实现import random from collections import defaultdict random.seed(42) counts {} for f in glob.glob(labels/*.txt): n sum(1 for line in open(f) if len(line.split()) 5) counts[f] n buckets defaultdict(list) for f, n in counts.items(): if n 0: bucket empty elif n 5: bucket small elif n 20: bucket medium else: bucket large buckets[bucket].append(f) val [] for bucket, files in buckets.items(): k max(1, int(len(files) * 0.2)) val.extend(random.sample(files, k))这段代码把图片按单张目标数分成“空、小、中、大”四个桶再从每个桶里按20%抽样保证验证集覆盖各种目标密度。特别提醒验证集里要放几张完全没有落石的负样本图用来观察误报率。这个习惯在公路场景格外重要因为模型最终要被部署到真实道路上那里最多的画面恰恰是“没有落石”。3. 把 VOC 转成 YOLO 格式转换脚本与四个边界坑现在的数据是双格式理论上可以直接用YOLO目录但公路落石数据集在实际下载后经常出现一种状况YOLO目录里的txt和VOC目录里的xml不一致比如txt里少了一个框或类别名对不上。凡是要长期用的数据我习惯统一以VOC的xml为唯一事实来源重新生成一份干净的YOLO标注。这样做的代价是一次转换收益是后续训练不会因为标注版本混乱而翻车。3.1 转换思路XML 解析、坐标归一化、类别映射转换的本质是把xml里的绝对坐标变成YOLO需要的归一化中心坐标。五步走解析xml读图片宽高读每个object的name和bndbox计算中心点坐标和宽高并除以宽高写入txt。下面是经过改造的脚本已经兼容了单类别和多类别也加了坐标合法性过滤import os import glob import xml.etree.ElementTree as ET CLASSES {rock: 0, gravel: 1} # 类别名到id的映射 def convert_annotation(xml_file, out_dir): tree ET.parse(xml_file) root tree.getroot() # 从xml读图片尺寸而不是用PIL去读图转换速度快很多 size root.find(size) width int(size.find(width).text) height int(size.find(height).text) out_lines [] for obj in root.findall(object): name obj.find(name).text if name not in CLASSES: continue cls_id CLASSES[name] 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) # 过滤非法框宽高为0或坐标倒挂 if xmax xmin or ymax ymin: continue cx (xmin xmax) / 2.0 / width cy (ymin ymax) / 2.0 / height w (xmax - xmin) / width h (ymax - ymin) / height out_lines.append(f{cls_id} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) base os.path.basename(xml_file).replace(.xml, .txt) with open(os.path.join(out_dir, base), w) as f: f.write(\n.join(out_lines)) def convert_all(xml_dir, out_dir): os.makedirs(out_dir, exist_okTrue) for xml_file in glob.glob(os.path.join(xml_dir, *.xml)): convert_annotation(xml_file, out_dir) if __name__ __main__: convert_all(Annotations, labels)脚本里有一个容易被忽略的设计过滤条件写在坐标计算之前而不是之后。原因是xmax xmin时计算出的w和h会成为负数或0YOLO训练框架大多不会主动报错而是把这个框当成无效框跳过但不同版本处理方式不一致在源头过滤是最稳的。类别映射用字典而不是列表是因为VOC xml里的name是字符串如果某个xml里出现了“Rock”这种大小写不一样的写法字典会抛KeyError这比训练时发现类别错位要早得多。3.2 转换时必须处理的三类脏数据difficult、遮挡目标和重复框VOC格式里有一个difficult字段表示目标是否难以辨认。很多转换脚本会忽略它但这在公路落石数据集里是个隐患。遮挡严重、远到只能看到轮廓、被车辆挡住一半的落石如果被标成difficult1转换时应该直接过滤。这不是丢掉信息而是避免模型把“看不清的石头”学成和“清晰石头”一样的特征。过滤方法很简单在遍历object时读一下字段difficult obj.find(difficult) if difficult is not None and int(difficult.text) 1: continue重复框是另一类常见脏数据。同一个落石在xml里被标注了两遍框位置完全相同或几乎重叠这通常是标注工具手滑。转换成YOLO后同一个类别的两个框完全重合训练时看起来是一个目标被双重计权mAP计算也会被扰动。处理办法是在转换后做一次去重按四舍五入后的坐标精度判断seen set() kept [] for line in out_lines: key tuple(round(float(x), 3) for x in line.split()[1:]) if key not in seen: seen.add(key) kept.append(line)这个去重按小数点后三位精度判断在640分辨率下相当于约0.6像素以内的框视为重复。小数点后三位不能设太小否则两个实际不同的目标可能被误判为重复也不能设太大否则重复框过滤不干净。3.3 转换后的质量检查数值校验和可视化抽查转换完成并不是终点。最有效的检查是两步先跑一个数值合法性脚本看有没有越界和异常值再画图抽查三到五张看框的位置是否贴住目标。数值校验脚本直接遍历所有txtpython - EOF import glob for f in glob.glob(labels/*.txt): with open(f) as fh: for i, line in enumerate(fh, 1): parts line.strip().split() if len(parts) ! 5: print(f{f}:{i} 字段数错误({len(parts)})) continue try: vals list(map(float, parts[1:])) except ValueError: print(f{f}:{i} 非数字) continue if any(v 0 or v 1 for v in vals): print(f{f}:{i} 坐标越界: {parts}) if vals[2] 0 or vals[3] 0: print(f{f}:{i} 宽高非法) EOF出现任何一行输出都要处理。坐标越界的常见来源是标注框拖出图片边缘在转换时用clip能把框拉回边界但如果是xmax小于xmin这种倒挂框就不能clip而要丢弃。宽高非法指的是w或h等于0这种情况出现在标注工具生成的退化框里同样要丢。可视化抽查用OpenCV画框的脚本前面给过这里补充一个观察维度不仅看框在不在目标上还要看框是否与目标轮廓贴合。落石的标注标准一般要求框紧贴可见外轮廓不包含阴影。如果抽查发现阴影区域被大量框进去说明打包者采用的标注标准和你自己的部署目标不一致此时应在转换时统一把框收缩2%到5%。这个操作有代价但能消除阴影带来的特征污染在训练前把标准对齐。3.4 批量验证转换是否可重复会不会破坏原目录一个容易忽略的细节是转换脚本的输出目录如果落在原目录里重复执行会叠加或覆盖。我的习惯是把YOLO格式输出到一个全新目录比如dataset_yolo原始VOC目录保持不动。这样重复转换时只要删掉旧输出目录重新跑即可不会污染原始数据。归档时也建议保留一份转换脚本、类别字典和运行时间记录因为这些决定了整个训练实验的可复现性。小数据集项目最怕“这次效果好但说不清好在哪里”转换这一步就把版本固定下来后面才不会乱。4. 用 YOLO 训练公路落石模型最小命令与 5 个关键参数数据准备完成进入训练。这里以当前主流的YOLOv8为例但命令和参数对v5也几乎通用。公路落石数据集最常见的使用路径就是YOLO系列预训练模型再微调不推荐自己从零构造网络结构直接用ultralytics提供的预训练权重即可。4.1 安装与目录约定给 YOLO 准备一份干净的工程目录训练第一步是确认框架环境。ultralytics包可以通过pip安装但版本差异要注意v8和v5的配置项有些兼容有些不兼容。我的建议是把训练环境固定在一个requirements.txt里避免几个月后重跑时发现行为变了。环境验证pip install ultralytics python -c import ultralytics; print(ultralytics.__version__)环境能跑通后把数据组织成YOLO官方约定的目录布局。这里的约定不是随便放的ultralytics的YOLO版本会自动根据后缀名搜索images和labelsrockfall_dataset/ ├── train/ │ ├── images/ # 训练原图 │ └── labels/ # 训练标注 └── val/ ├── images/ └── labels/把前面转换出的282张图和标注按划分复制到上述目录里下一步确认每个文件名对找有没有缺labels的jpg、缺jpg的labels。用bash循环检查# 找出训练集里缺少对应txt的jpg for f in train/images/*.jpg; do [ -f train/labels/$(basename $f .jpg).txt ] || echo missing label: $f done这条命令检查同名txt是否存在如果输出为空说明图片和标注对齐了。对val目录同样跑一遍。这一步看起来笨但能拦下那些解压时丢失文件的意外。4.2 写好 data.yaml五个字段决定训练能否启动ultralytics训练时会读取一个yaml文件字段不多但顺序和路径经常出问题。这里给出一个经过验证的写法path: /home/user/datasets/rockfall # 数据集根目录建议绝对路径 train: train/images # 相对path的训练图片目录 val: val/images # 相对path的验证图片目录 nc: 1 # 类别数 names: 0: rockpath字段是坑最多的一个。YOLOv8在读取train和val时会先在当前工作目录找找不到再按path拼接最后才尝试把它当绝对路径。我通常直接写绝对路径并加一个ls验证python - EOF import yaml cfg yaml.safe_load(open(rockfall.yaml)) print(cfg[path], cfg[train], cfg[val]) EOF输出确认路径存在且是期望值就行。nc必须和names的长度一致多一个少一个都会直接报错。names的作用是把txt里的数字id映射成可视化时的标签如果检测类别只有rocknames里写错成stone也不影响数值但在混淆矩阵和推理框显示上会错位所以这里的校验要严格。4.3 训练命令5 个参数在小数据集上的边界最小训练命令如下yolo detect train \ datarockfall.yaml \ modelyolov8n.pt \ imgsz640 \ batch16 \ epochs100 \ mosaic0.5 \ patience30imgsz是第一个要动的参数。落石目标小640下很多目标不足16像素模型很难提取特征。显存允许就提到1280尤其当统计显示最小框只有0.01的宽度时。提到1280的代价是batch要往下调12G显存的卡可以把batch从16降到8。这个权衡比加大模型更实际模型参数量从n换到s或m对落石检测的收益远不如分辨率提升直接。batch在小数据集上决定batch normalization的稳定性。282张图batch16时一个epoch只有14个stepBN统计量在这么少的step里容易抖动。如果发现训练loss正常但验证集表现有周期性波动把batch降到8或升到32看看收敛是否变稳。注意batch太小反而会让BN的均值方差估计不准所以一般不推荐batch2这种极端值。epochs要结合早停来看。小数据集的过拟合起点往往在80到150轮之间设300配合patience20在验证集20个epoch不再提升时自动停。很多人手动设了100结果模型还没收敛就停了或者不设patience硬生生跑过拟合区间。建议epochs设大、patience设小让早停自己判断。mosaic是YOLO里最有效但最容易被滥用的增强。公路落石场景下开满1.0会让模型看到太多四图拼接的边界推理时碰到正常整幅图反而不适应。我建议设0.5把另外一半增强概率留给自己的离线增强管线也就是下面要写的复制粘贴、亮度扰动它们比mosaic更贴近落石场景。anchors这个参数YOLOv8已经改成自动学习用户层面不需要在命令行里显式指定。v5时代还有锚框超参可以手工填但我两次调整的收益都不明显。真正影响小目标准确率的是模型结构里下采样倍率而YOLO系列不允许轻易改这个。我的结论是不要折腾anchors把精力放在imgsz和输入分辨率上更有效。这是我在落石数据集上反复对比后的血泪经验。4.4 数据增强与负样本小数据集最值得投入的环节282张图在训练中大概只有两三万个目标如果不做增强模型学到的落石纹理特征非常有限。我一般做两类增强第一类是亮度、对比度、颜色扰动第二类是复制粘贴。亮度扰动因为落石在不同日照下的观感差异大用albumentations几行代码就能接进预处理import albumentations as A train_transform A.Compose([ A.RandomBrightnessContrast(brightness_limit0.2, contrast_limit0.2, p0.6), A.HueSaturationValue(hue_shift_limit10, sat_shift_limit20, val_shift_limit20, p0.3), A.RandomGamma(gamma_limit(80, 120), p0.4), A.Rotate(limit10, border_mode0, p0.3), A.RandomSizedBBoxSafeCrop(width640, height640, p0.3), ])这里的RandomSizedBBoxSafeCrop值得多说一句它在随机裁剪的同时保证所有标注框不被裁掉对密集落石图特别有用相当于让模型从局部开始学习判断落石而不是每次看整幅公路的全貌。Rotate的limit设在10度因为公路相机基本平视旋转超过15度会引入不存在的视角。border_mode0表示旋转后填充黑色这比填充白色更容易让模型忽略边缘像素。复制粘贴增强的实现前面给过这里补充参数经验粘贴时不要做缩放变换因为落石的尺度多样性已经够大了缩放会让模型学到错误尺度要随机翻转粘贴位置但不能让两个粘贴框重叠太多否则在框密集区域会互相遮挡增强沦为噪声。负样本的处理是另一个重点。把几十张完全没有落石的公路图放进训练集让模型看到“这里什么都没有”的样子。YOLO训练时对负样本的处理是空txt的图片不参与正样本回归它们会被当作背景参与类别loss。所以负样本过滤开关一般不要打开让它们自然进入训练即可。如果总图数不足可以用前面复制粘贴后的合成图小批量填进去。4.5 训练中看什么loss 曲线和验证集表现的对应关系训练过程不要只盯终端打印的数字。把训练日志里的box_loss、cls_loss、dfl_loss画出曲线小数据集上有几条规律值得记住box_loss下降很慢但稳定说明数据质量可以cls_loss如果在前30轮就降到接近0说明落石和背景的区分对模型太简单后续要注意过拟合val侧的loss如果某个epoch后反弹而train还在下降说明已经过拟合早停会在那里卡住。我一般会同时记录验证集上大小目标的AP曲线这样能分辨模型是在整体变好还是仅大目标变好。训练结束后留一份包含data.yaml、训练命令、随机种子、增强参数和训练日志的记录。这个文件花不了多少时间但在调参对比时价值巨大否则过了两周根本说不清当前best.pt是在什么配置下出来的。我的习惯是把这些信息写进一个train_config.yaml存到训练输出目录和best.pt放一起部署时也能追溯模型来源。5. 公路落石数据集避坑训练中最容易翻车的 4 个排查点前面的流程走完模型未必直接能用。训练这类小规模真实场景数据集翻车点高度集中。这一章按现象、原因、解决的顺序记录几个高频问题都是我实际处理时沉淀下来的新手照着查能省很多时间。5.1 现象train loss 正常下降val mAP 始终不到 0.3最常见的原因有两个验证集和训练集的图片拍摄场景不一致或验证集里的标注质量比训练集差。公路落石数据往往按拍摄路段或时间采集如果打包时把某一整条路的图像全部划进val而训练集是另一条路的图两项分布完全不同val mAP会很低。解决方法是回到第2.4节的划分逻辑按目标密度和场景分层再抽样确保val覆盖多样场景。另外打开val里最差的几张预测图如果发现模型看起来没错但标注框偏了说明val标注本身有问题需要修正标注而不是提高模型。如果验证集没问题再检查训练集的多样性。282张图如果都来自同一段路、同一个相机角度模型会把路段背景学进去val里一旦出现新路段mAP就崩。此时优先做亮度扰动和随机crop增强而不是急着换更大的模型架构。5.2 现象训练中途 loss 变 NaN 或突然暴涨这类问题的根源在我经验里80%出在标注数据。自查流程固定先跑数值校验脚本看有没有越界坐标、非法宽高、非数字字符再检查图片文件有没有0字节或截断最后才考虑学习率。前面给过的校验脚本可以直接用注意它要跑在训练实际用的labels目录上而不是某些中间目录。还有一个容易踩的坑是txt文件末尾多了空行或空格导致解析出错。我在转换脚本里已经用strip处理了但如果你用的转换工具没处理训练就会随机崩。这种崩的规律是前几个epoch正常到某个batch忽然NaN再训练又恢复实际上是那个脏样本被重复采样到才触发。5.3 现象护栏、路面裂缝被识别成落石误报是落石检测里的头号问题。模型无法区分落石和护栏底座、沥青裂缝、桥面伸缩缝是因为这些物体的边缘纹理和落石在某些尺度下很像。常见对策分三个层次第一扩充背景负样本图把没有落石但包含护栏、裂缝的图加进训练集这条最直接。第二检查标注是否包含了太多阴影如果标注框把阴影算进去模型会自动学“深色区域等于落石”误报就会集中在阴影边缘。第三推理时调高置信度阈值代价是漏检变多不推荐作为第一选择。彻底解决还是要让训练数据覆盖这些容易混淆的背景YOLO的这种误判本质上是一个样本覆盖问题不是模型结构问题。5.4 现象训练集准确率很高换了新视频后基本失灵过拟合的症状。282张图训练出的模型如果不去做增强几乎必然把训练图像的特定光照、拍摄角度、公路色调记住。解决方式是扩大数据多样性而不是增加数据量亮度扰动、随机翻转、轻微旋转、随机crop四种增强足以让小数据集的特征不再绑定到具体拍摄条件。我在落石项目里最后采用的是albumentations的那套参数亮度扰动概率设在0.6因为落石检测对颜色很敏感开太大容易把灰石头变成白石头模型就学歪了。增强后如果单张图出现两个几乎一样的合成框说明增强策略本身有问题需要回到标注层面去查。5.5 现象类别多了以后混淆矩阵很乱rock 和 gravel 分不开如果数据本身是多类别标注小数据集上常见的现象是类别不均衡。282张图里可能rock有4000个框而gravel只有200个框模型会把所有框都预测成rock混淆矩阵里gravel那一列几乎全是0。解决办法是类别加权loss或者把gravel框做更多复制粘贴增强让它数量接近rock。但这两种做法都只能缓解真正彻底的方式是回到标注在两个类别边界模糊时不如并成单类rock训练等模型在更大数据上能分清了再拆类。这个建议对小规模数据集格外实用因为目标检测的价值在于把落石框出来至于它是大石头还是小碎石交给下游规则判断就够了。6. 模型训练完别急着部署验证阈值与时间一致性的两个技巧训练完的best.pt只是中点。小数据集模型在真实公路场景下单帧检测结果往往有明显抖动部署前需要再做两件事。第一件事是验证时看混淆矩阵而不是只看mAP。用一条命令输出验证集上的混淆矩阵yolo detect val \ datarockfall.yaml \ modelruns/train/exp/weights/best.pt \ imgsz640 \ conf0.15把conf设成期望的部署阈值比如单帧场景设0.15或0.2。混淆矩阵里“背景被预测成落石”那一格的数量直接对应误报率。如果误报集中在背景回去补负样本如果漏检集中在极小目标回去把imgsz从640提到1280。这些调整依据在混淆矩阵上一目了然。第二件事是给模型加时间维度的抑制逻辑。连续视频帧里同一块落石的位置应当是缓变或静止的模型偶尔漏检一帧并不代表目标消失。部署时常见做法是维护一个滑动窗口当某个位置的落石检测框在连续3帧里至少出现2次才产生一次告警只出现一次则忽略。这样能明显压制单帧误报同时保住真正的持续目标。这类逻辑和YOLO本身解耦用后处理实现即可调整阈值时不需要重新训练。我的习惯是每次训练完固定保存三样东西清洗后的数据划分、训练配置、验证阈值记录。下一轮迭代时先看这些记录再决定是改imgsz还是改数据。这个习惯救过我好几次避免在同一个坑里反复踩。希望这些从数据拆解到部署验证的细节能帮你把公路落石目标检测项目更快跑通。本文还有配套的精品资源点击获取
