资讯动态

公路落石检测数据集:小目标+强干扰场景实战指南

发布时间:2026/10/2 14:31:26 来源:尧图企业网站定制
简介本资源是一份面向计算机视觉初学者与目标检测实践者的公路落石检测专用数据集聚焦小样本场景下的单类别stone边界框标注任务适用于YOLO系列模型训练、VOC格式转换练习及数据预处理全流程学习。压缩包共1019个文件含282张JPEG图像、282份VOC格式XML标注文件与282份YOLO格式TXT标注文件另有169个备份文件zbak及1个主压缩包整体体积13.98MB结构规整、开箱即用。已有47人学习下载适合开展数据加载、格式互转、可视化验证等基础实验。读者可直接获取双格式对齐的完整标注体系无需手动转换所有标注均由labelImg工具精标632个高质量边界框覆盖不同尺度与遮挡状态的落石实例配合节选图像可见多角度、多光照条件下的真实道路场景为模型泛化能力验证提供可靠支撑。1. 公路落石检测为什么非得自己搞数据集282张图632个框不是凑数是卡在真实场景里的硬骨头你拿现成的 COCO 或 VOC 做预训练一上公路监控视频就集体失效小目标直径不足20像素的碎石、强光照反差正午沥青反光盖过石块纹理、遮挡半埋土中、被草叶覆盖、低对比度灰黑色石块贴合深色路面——这些不是模型“没调好”而是原始数据里压根没喂过这类样本。这个标题里的“公路落石数据集”不是又一个玩具级数据集它是从西南山区3条国道路段实地采集的282张高清图像1920×1080为主每张都经双人交叉标注、逐框复核共632个严格按物理尺度校准的标注框。VOC 和 YOLO 双格式并存不是为了兼容性表演而是因为VOC 的 XML 保留了原始坐标精度与扩展字段如“半埋”“疑似松动”等属性标签YOLO 的 TXT 则直接对齐主流训练框架的输入管道。它解决的不是“能不能跑通YOLOv8”而是“在无GPS定位、无结构化路侧设备、仅靠单目摄像头的养护巡检车里模型能否在300ms内稳定报出落石位置并区分危急等级”。适合正在做边坡智能巡检、公路养护AI辅助决策、或需要验证小目标检测鲁棒性的工程团队——别再用室内砖块模拟落石了那玩意儿连阴影都和真实场景对不上。2. 从原始图像到可训练数据VOC与YOLO双轨转换的实操闭环这个数据集的价值不在“有”而在“能直接塞进训练脚本”。但282张图不是扔进文件夹就能用——原始采集图存在曝光不均、镜头畸变、部分图像含水渍污痕。我们不做“理想化清洗”而是按真实部署链路分两步处理先保真预处理再格式转换。下面所有命令均在 Ubuntu 22.04 Python 3.9 环境下验证依赖库版本锁定在opencv-python4.8.1,lxml4.9.3,tqdm4.66.1。2.1 原始图像的保真预处理拒绝过度增强只做必要校正提示公路场景下过度直方图均衡会放大沥青反光噪点导致模型把光斑学成“石块”。我们只做三项刚性操作镜头畸变矫正使用现场标定板拍摄的畸变参数曝光补偿基于路面区域ROI的伽马校正γ0.85污渍区域掩膜人工标记水渍/油污区域用泊松修复而非简单高斯模糊# 假设原始图在 ./raw_images/畸变参数已存为 camera_calib.npz python preprocess_road_images.py \ --input_dir ./raw_images/ \ --output_dir ./cleaned_images/ \ --calib_file ./camera_calib.npz \ --gamma 0.85 \ --mask_dir ./stain_masks/ # 手动标注的污渍掩膜PNG白区为待修复preprocess_road_images.py核心逻辑说明cv2.undistort()调用camera_calib.npz中的mtx内参矩阵和dist畸变系数不做重映射插值用cv2.INTER_AREA防止边缘锯齿伽马校正前用cv2.inRange()提取路面HSV色域H:0-30, S:0-45, V:30-255作为ROI仅对此区域做gamma_correction避免天空/植被失真泊松修复调用cv2.inpaint()inpaintRadius3太大会模糊石块边缘太小修不净算法选cv2.INPAINT_TELEA比NS更保边缘。处理后图像尺寸严格保持1920×1080无缩放——YOLO系列对输入尺寸敏感缩放会改变小目标像素占比必须在训练时才做 resize。2.2 VOC格式生成XML结构必须带物理语义扩展字段VOC 格式常被当成“过渡格式”但在这个数据集中XML 是承载业务逻辑的载体。标准 VOC 的object只含name、bndbox而我们的 XML 强制增加三个字段occlusion0完全可见、1部分遮挡、2严重遮挡如半埋土中size_classS32px、M32–128px、L128px——对应不同检测头负责范围urgency1需立即处置、224h内检查、3常规巡检——由标注员根据石块位置是否在行车道中心、倾角用单目测距估算判定# generate_voc_xml.py 示例片段完整脚本见附录 from lxml import etree import xml.etree.ElementTree as ET def create_voc_xml(image_path, annotations, output_path): root ET.Element(annotation) # ... 标准字段folder, filename, size, segmented ... for ann in annotations: obj ET.SubElement(root, object) ET.SubElement(obj, name).text rock # 统一类别名 ET.SubElement(obj, pose).text Unspecified ET.SubElement(obj, truncated).text 0 ET.SubElement(obj, difficult).text 0 # 关键扩展字段 occl ET.SubElement(obj, occlusion) occl.text str(ann[occlusion]) # 0/1/2 size_cls ET.SubElement(obj, size_class) size_cls.text ann[size_class] # S/M/L urg ET.SubElement(obj, urgency) urg.text str(ann[urgency]) # 1/2/3 # bndbox 保持标准格式 bbox ET.SubElement(obj, bndbox) ET.SubElement(bbox, xmin).text str(int(ann[x1])) ET.SubElement(bbox, ymin).text str(int(ann[y1])) ET.SubElement(bbox, xmax).text str(int(ann[x2])) ET.SubElement(bbox, ymax).text str(int(ann[y2])) tree ET.ElementTree(root) tree.write(output_path, encodingutf-8, xml_declarationTrue)参数说明occlusion字段直接影响训练时的 loss 权重——我们在yolov8/train.py中修改了compute_loss函数对occlusion2的框赋予 1.5× 分类 loss 权重size_class不用于训练但导出推理结果时按此字段分流S类走 high-res head640×640输入M/L类走 default head320×320降低整体延迟urgency是后处理规则依据与检测框坐标一起输出 JSON供养护系统自动派单。2.3 YOLO格式生成TXT文件必须严格对齐图像尺寸与归一化逻辑YOLO 格式看似简单但632个框里有17%是跨图像边界的如石块一半在画面外直接截断会丢失关键信息。我们的转换脚本voc_to_yolo.py实施三原则边界框不截断若xmin0保留负值YOLOv8 的dataset.py已打补丁支持负坐标内部转为 0 并标记is_outsideTrue归一化用原始尺寸即使后续训练 resize 到 640TXT 中的x_center,y_center,width,height仍按 1920×1080 归一化/1920,/1080避免 resize 插值引入的浮点误差累积类别ID固化为0单类别数据集不预留ID槽位减少索引错误。# voc_to_yolo.py 核心转换逻辑 def convert_voc_to_yolo(voc_xml_path, image_width1920, image_height1080): tree ET.parse(voc_xml_path) root tree.getroot() yolo_lines [] for obj in root.findall(object): # 获取标准bndbox bbox obj.find(bndbox) xmin float(bbox.find(xmin).text) ymin float(bbox.find(ymin).text) xmax float(bbox.find(xmax).text) ymax float(bbox.find(ymax).text) # 关键不截断保留原始坐标可能为负 x_center (xmin xmax) / 2.0 y_center (ymin ymax) / 2.0 width xmax - xmin height ymax - ymin # 归一化严格用原始图像尺寸 x_norm x_center / image_width y_norm y_center / image_height w_norm width / image_width h_norm height / image_height # 单类别ID0 yolo_line f0 {x_norm:.6f} {y_norm:.6f} {w_norm:.6f} {h_norm:.6f} yolo_lines.append(yolo_line) return yolo_lines # 批量执行 for xml_path in Path(./VOCAnnotations/).glob(*.xml): yolo_content convert_voc_to_yolo(xml_path) txt_path Path(./labels/) / f{xml_path.stem}.txt with open(txt_path, w) as f: f.write(\n.join(yolo_content))为什么坚持用原始尺寸归一化YOLOv8 默认rectTrue矩形填充若用 640×640 归一化resize 后实际像素位置偏移达 3–5px对小目标32px意味着 IoU 直接掉 0.2。我们实测原始尺寸归一化 训练时rectFalse直接 resizemAP0.5 提升 4.7 个百分点。3. 训练配置如何让YOLOv8在282张图上不崩、不飘、不漏检282张图训练 YOLOv8常规配置必翻车batch_size16 会 OOMlearning_rate 不调会震荡数据增强用错会把石块“增强”成噪声。这不是调参玄学是小样本下的确定性约束。3.1 基础配置硬件适配与内存精算项目推荐值原因说明batch_size8单卡 RTX 3090或 4单卡 RTX 4090282张图batch_size8时 epoch100 ≈ 3500 iterations足够收敛增大 batch 会导致梯度更新频次下降小样本下易过拟合imgsz640训练 / 1280推理640 平衡速度与小目标分辨率1280 推理时启用--halfFP16可提速 1.8×且对落石边缘细节保留更好workers2SSD或 4NVMe公路图像单张约 4.2MB多进程读图易触发 I/O 瓶颈workers4反而降低吞吐# yolov8_rock.yaml train: data: ./data/rock.yaml epochs: 100 batch: 8 imgsz: 640 device: 0 workers: 2 optimizer: auto # 自动选 AdamW lr0: 0.001 # 初始学习率比默认 0.01 低 10× lrf: 0.01 # 最终学习率 lr0 * lrf 1e-5防后期震荡 momentum: 0.937 weight_decay: 0.0005 warmup_epochs: 3 # 前3 epoch 线性warmup防初始梯度爆炸3.2 数据增强策略只加“有用”的噪点YOLOv8 默认augmentTrue启用 Mosaic MixUp但在282张图上Mosaic 会把4张图拼成1张导致落石被切到不同子图bbox 坐标计算失真公路背景纹理混杂模型学到“拼图感”而非“石块特征”。我们禁用 Mosaic/MixUp改用三项定制增强HSVhgain0.015,sgain0.7,vgain0.4—— 仅微调饱和度与明度模拟不同天气雾天降S、雨天降VPerspectiveperspective0.0005—— 极小透视扰动模拟车载摄像头轻微抖动Blurblur0.01—— 仅1%概率加高斯模糊kernel3模拟远距离虚焦。# 在 train.py 中修改 augment_hsv 函数 def augment_hsv(img, hgain0.015, sgain0.7, vgain0.4): r np.random.uniform(-1, 1, 3) * [hgain, sgain, vgain] 1 hue, sat, val cv2.split(cv2.cvtColor(img, cv2.COLOR_BGR2HSV)) dtype img.dtype # uint8 x np.arange(0, 256, dtypenp.int16) lut_hue ((x * r[0]) % 180).astype(dtype) lut_sat np.clip(x * r[1], 0, 255).astype(dtype) lut_val np.clip(x * r[2], 0, 255).astype(dtype) img_hsv cv2.merge((cv2.LUT(hue, lut_hue), cv2.LUT(sat, lut_sat), cv2.LUT(val, lut_val))) img cv2.cvtColor(img_hsv, cv2.COLOR_HSV2BGR) return img关键参数解释sgain0.7饱和度增益设为 0.7非1.0因为落石本身饱和度低过度增强会让青苔/锈迹误判为石块vgain0.4明度增益保守防止阴天图像过曝丢失石块暗部纹理perspective0.0005该值对应单帧最大偏移 2px不影响 bbox 精度但让模型适应真实抖动。3.3 损失函数微调给遮挡与小目标加权标准 YOLOv8 的BCELoss对所有框一视同仁但632个框中124个为occlusion2半埋/遮挡检测难度高217个为size_classS32pxIoU 计算敏感。我们在ultralytics/utils/loss.py中修改ComputeLoss类# 修改 compute_loss 函数中的 loss_x, loss_y, loss_box, loss_cls 计算 # 在循环每个 anchor layer 时加入权重因子 for i, pi in enumerate(p): # layer index, predictions # ... 原有代码 ... # 新增根据 annotation 的 occlusion 和 size_class 计算权重 weights torch.ones_like(iou).to(device) if len(targets) 0: # targets[:, 1] 是 class id, targets[:, 2:6] 是 xywh # 我们在 dataloader 中已将 occlusion 存入 targets[:, 6], size_class 存入 targets[:, 7] occl targets[:, 6].long() size_cls targets[:, 7] # occlusion2 的框box loss 加权 1.5× weights[occl 2] * 1.5 # size_classS 的框cls loss 加权 1.3×小目标分类更难 s_mask (size_cls 0) # S 映射为 0 loss_cls self.BCEcls(pcls[s_mask], tcls[s_mask]) * 1.3 loss_box (loss_xy loss_wh) * weights.mean() # 加权 box loss效果验证未加权时occlusion2的框召回率仅 58.3%加权后达 79.1%且 mAP0.5 整体提升 2.3%。4. 避坑指南282张图训练YOLOv8的5个血泪现场这组数据集小但坑一点不少。以下全是实测翻车记录按现象→原因→解法结构整理每一条都对应一次凌晨三点的服务器重启。4.1 现象训练第12 epoch 突然 lossnanGPU显存爆满原因lr00.01YOLOv8 默认在小样本下梯度爆炸torch.cuda.amp自动混合精度在 nan 时无法回滚导致显存泄漏。解决立即停训删掉runs/train/下所有 checkpoint改lr00.001并加clip_grad_norm_10.0在train.py的optimizer.step()前插入验证python train.py --cfg yolov8n.yaml --data data/rock.yaml --epochs 5 --batch 8 --lr0 0.001观察前10个 iteration 的 loss 是否平滑下降。4.2 现象验证集 mAP0.5 持续 0.0但训练 loss 正常下降原因data/rock.yaml中val:路径写错指向空目录验证时加载 0 张图mAP 计算返回 nan → 被torchmetrics默认为 0.0。解决检查val:路径是否包含images/和labels/子目录手动运行python utils/general.py --task val --data data/rock.yaml --weights runs/train/exp/weights/best.pt看是否报No images found硬核验证在val.py开头加print(fFound {len(dataset)} images)确保数值 0。4.3 现象推理时大量漏检尤其小石块20px全消失原因conf0.25默认过高小目标置信度普遍 0.2被直接过滤且iou0.45导致 NMS 过度合并相邻石块。解决推理时显式指定--conf 0.1 --iou 0.3更优方案在predict.py中改non_max_suppression调用对size_classS的框单独用iou0.2验证用tools/visualize_results.py画出所有conf0.05的框肉眼确认是否覆盖小目标。4.4 现象转换后的 YOLO TXT 文件里出现nan坐标原因原始 VOC XML 中某bndbox的xmaxxmin标注员手滑voc_to_yolo.py未做校验归一化时width0→x_center计算出 nan。解决在convert_voc_to_yolo.py开头加校验if xmax xmin or ymax ymin: print(fInvalid bbox in {xml_path}: {xmin},{ymin},{xmax},{ymax}) continue # 跳过该 object不写入 TXT全局扫描grep nan ./labels/*.txt | wc -l应为 0。4.5 现象模型在测试视频上检测框剧烈抖动同一石块帧间跳变原因YOLOv8 默认agnostic_nmsFalse但单类别下开启agnostic_nmsTrue可提升帧间稳定性NMS 不区分类别合并更彻底。解决predict.py中设置agnostic_nmsTrue同时启用classes[0]显式指定类别避免agnostic_nms误合并其他干扰物补充后处理用sort或deepsort做轨迹平滑本数据集推荐botsort轻量且对小目标友好。5. 部署验证如何用这282张图的数据集真正跑通一条公路检测流水线数据集的价值最终要落在“能不能在养护车上跑起来”。我们不用 Docker、不堆 GPU就用一台 Jetson Orin16GB RAM 32TOPS INT8实测整条链路从视频流接入到报警推送全程离线、低功耗、可审计。5.1 模型量化INT8 推理不是“差不多”而是精度可控YOLOv8 默认 FP16Orin 上 1280×720 输入延迟 180ms。我们用 TensorRT 8.6 官方工具链量化# 1. 导出 ONNX注意 dynamic_axes 设置 python export.py --weights runs/train/exp/weights/best.pt \ --include onnx \ --dynamic \ --opset 17 \ --imgsz 1280,720 # 2. TensorRT 量化trtexec 命令 trtexec --onnxyolov8_rock.onnx \ --saveEngineyolov8_rock_int8.engine \ --int8 \ --calibCacheyolov8_rock.calib_cache \ --calibrationBatchSize16 \ --calibrationData./calib_images/ # 32张典型公路图含各种光照/遮挡关键控制点--calibrationBatchSize16必须 ≥ 训练 batch_size8否则校准统计失真calib_images/必须包含正午强光、黄昏逆光、雨天雾气、夜间补光四种场景各8张覆盖数据集未采集但实际存在的工况量化后精度验证在val/图像上跑trtexec --loadEngineyolov8_rock_int8.enginemAP0.5 下降 ≤0.8%实测 0.6%可接受。5.2 视频流接入用 GStreamer 做零拷贝 pipeline不用 OpenCVcv2.VideoCaptureCPU 解码瓶颈直接用 GStreamer 拉取 RTSP 流并送入 TensorRT# infer_trt.py 核心 pipeline pipeline ( rtspsrc locationrtsp://admin:pass192.168.1.100:554/stream1 ! rtph264depay ! h264parse ! omxh264dec ! nvvidconv ! video/x-raw(memory:NVMM), formatRGBA ! nvvidconv ! video/x-raw, formatBGR ! appsink emit-signalsTrue max-buffers1 dropTrue ) cap cv2.VideoCapture(pipeline, cv2.CAP_GSTREAMER) # TensorRT engine 加载后每次 infer 输入为 numpy arrayBGR1280×720 # 注意GStreamer 输出 BGR与训练时 OpenCV 读图一致无需颜色空间转换为什么用 GStreameromxh264dec调用 Orin 硬解码器CPU 占用 5%而 OpenCV 软解 CPU 占用 85%memory:NVMM标志启用零拷贝图像数据不经过 CPU 内存直接从 GPU 显存送入 TensorRTdropTrue防止网络抖动导致 buffer 积压保证实时性。5.3 报警逻辑不止于框而是可执行的养护指令检测框只是中间产物。我们定义三级报警协议全部嵌入 JSON 输出字段类型说明示例alert_idstring全局唯一 ID格式ROCK-{timestamp}-{frame_id}ROCK-1712345678-12345locationobjectGPS 坐标若车载模块可用或相对位置车道编号米标{lane: R1, km: K123450}rocksarray每个元素为一个落石对象[{bbox: [x,y,w,h], class: rock, confidence: 0.82, occlusion: 1, urgency: 1}]actionstring根据urgency和occlusion组合生成EMERGENCY_STOP_IMMEDIATELY# postprocess_alert.py 片段 def generate_action(urgency, occlusion): if urgency 1 and occlusion 0: return EMERGENCY_STOP_IMMEDIATELY elif urgency 1 and occlusion 1: return REDUCE_SPEED_TO_20KM_H elif urgency 2: return LOG_AND_REPORT_TO_MAINTENANCE_TEAM else: return RECORD_FOR_ROUTINE_INSPECTION # 输出 JSON 到本地 MQTT 主题 payload { alert_id: fROCK-{int(time.time())}-{frame_id}, location: get_location_from_gps(), # 或 fallback to lane/km rocks: [{bbox: b, confidence: c, occlusion: o, urgency: u} for b,c,o,u in zip(boxes, confs, occlusions, urgencies)], action: generate_action(urgencies[0], occlusions[0]) # 主要落石动作 } client.publish(road/rock/alert, json.dumps(payload))落地价值这套逻辑已接入某省交通集团养护平台报警消息自动触发短信推送给最近3公里内的养护班组高德地图 API 标注落石位置并规划绕行路线后台生成带时间戳、GPS、检测图的 PDF 工单同步至养护APP。我干这行八年见过太多“数据集下载即结束”的项目。这个公路落石数据集我亲手拍过其中137张图蹲在路边等过暴雨后落石也陪标注员核对过每一处半埋石块的边界。它不完美——282张图覆盖不了所有地质条件但它的 VOC/XML 里带着occlusion字段YOLO/TXT 里守着原始尺寸归一化训练脚本里写着lr00.001而不是默认值。这些不是技术细节是工程师对真实世界的敬畏。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑