翻越栏杆检测数据集详解:VOC与YOLO格式转换及YOLOv8训练避坑
发布时间:2026/10/2 14:29:39来源:尧图网络
简介面向目标检测任务整理的翻越栏杆围栏数据集专门用于训练行人翻越围栏/栏杆行为的识别模型可部署于园区周界、车站月台、小区安防等需要越界预警的场景适合有YOLO或VOC使用基础的算法工程师与研究者。压缩包共2000个文件整体约100.25MB主要构成是1680个目标图片样本及其VOC格式XML标注文件并附YOLO格式txt标签文件标注采用LabelImg绘制矩形框覆盖climbing与person两个类别合计2454个标注框其中翻越行为框2060个、行人框394个类别分布合理。目前已有598人学习下载。数据集内含部分增强图片可有效扩充正样本包内另附说明文件与标签列表方便核对图像与标注对应关系下载后可直接被YOLO、SSD、Faster R-CNN等常见检测框架读取省去格式转换步骤助力快速开展模型训练、效果验证与算法调优。1. 翻越栏杆检测数据集1680张图能解决什么不能解决什么做安防和园区智能视频分析的人大概率会遇到同一个尴尬厂家给的预训练模型里什么都有就是没有「翻越围栏」这个动作。你拿行人检测模型去硬切栏杆边上站个人就能触发一半误报真翻过去的那几帧反而漏掉。这个翻越栏杆围栏数据集1680张VOCYOLO格式就是冲着这个缺口来的两个类别——climbing和person共 2454 个真实标注框全部用 labelImg 画矩形框完成同时给出 VOC 的 xml 和 YOLO 的 txt 两种格式省掉你手动转换的功夫。适合谁正在用 YOLOv5/v8 做园区周界、工地安全帽之外的越界行为识别或者想在自己数据集上微调动作检测的从业者。先说清楚边界它只保证标注准确合理不保证模型权重精度里面还掺了一部分增强图片介意的话往下看避坑章节再决定要不要下。2. VOC 和 YOLO 两种标注格式同一张图的两套坐标系2.1 从 XML 到 TXT归一化坐标是怎么算出来的拿到的压缩包解压后每个 jpg 图片对应一个同名 xml 和一个同名 txt。xml 是 PASCAL VOC 格式标注信息写在object节点里核心是bndbox下的四个值——xmin、ymin、xmax、ymax单位是像素。比如一张 1920×1080 的图上一个人翻越栏杆的框可能是xmin512, ymin300, xmax780, ymax920。YOLO 格式的 txt 就完全不同了它存的是归一化后的中心点坐标和宽高公式是# 像素坐标转 YOLO 归一化坐标 x_center (xmin xmax) / 2 / img_width y_center (ymin ymax) / 2 / img_height width (xmax - xmin) / img_width height (ymax - ymin) / img_height这四个值全是 0 到 1 之间的小数和图片原始分辨率无关。所以你用 YOLOv5 训练时即使训练集里有 1280×720 和 1920×1080 两种分辨率的图模型输入层img_size640也能统一处理——标注坐标已经归一化resize 不会造成坐标错位。xml 转 txt 时最容易翻车的点在取整YOLO 官方要求归一化值保留 6 位小数有的转换脚本用round(x, 4)训练时 loss 会异常波动因为坐标精度不够。2.2 标注类别字段与 labelImg 的导出规则xml 里name字段是这个数据集的类别名只有两个值climbing和person。climbing有 2060 个框person只有 394 个框这个比例差距直接决定了后面训练时的正负样本策略——不是每个person框都伴随climbing框很多人只是站在栏杆旁边模型要把「站着的人」和「正在翻越的人」区分开靠的是身体姿态和与栏杆的空间关系。labelImg 的标注规则比较简单画矩形框一个框对应一个类别。但注意labelImg 默认保存的是 VOC xmlYOLO txt 需要你主动切换。如果你要复现这个数据集或者自己补标注在 labelImg 左侧栏点PascalVOC按钮切换成YOLO格式框画完后会直接生成.txt同名文件。切换后保存路径也要同步改否则 xml 和 txt 会散落在不同目录训练时找不到配对文件。# 检查 xml 和 txt 是否一一对应 import os jpg_dir images # 图片目录 xml_dir labels_xml # VOC 标注目录 txt_dir labels_txt # YOLO 标注目录 base_names [f[:-4] for f in os.listdir(jpg_dir) if f.endswith(.jpg)] missing_xml [n for n in base_names if not os.path.exists(os.path.join(xml_dir, n .xml))] missing_txt [n for n in base_names if not os.path.exists(os.path.join(txt_dir, n .txt))] print(缺 xml:, len(missing_xml), 缺 txt:, len(missing_txt))这个脚本我在拿到任何新数据集时都会跑一遍检测 OCR 出来的文件名错位比想象中常见。2.3 标注质量怎么核验可视化检查比看数字靠谱归一化坐标对不对最好的验证方式是直接把框画回图上人眼扫一遍。代码很简单import cv2 def draw_yolo_box(img_path, txt_path, class_names): img cv2.imread(img_path) h, w img.shape[:2] with open(txt_path, r) as f: for line in f.readlines(): cls_id, xc, yc, bw, bh map(float, line.split()) x1 int((xc - bw / 2) * w) y1 int((yc - bh / 2) * h) x2 int((xc bw / 2) * w) y2 int((yc bh / 2) * h) label class_names[int(cls_id)] cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, label, (x1, max(y1 - 5, 0)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2) return img class_names [climbing, person] img draw_yolo_box(images/000001.jpg, labels_txt/000001.txt, class_names) cv2.imwrite(check.jpg, img)重点看两件事框有没有明显偏离目标主体以及climbing框是不是都框住了「身体正在跨越栏杆」的瞬间而不是静态站立。这个数据集的好处是 labelImg 手工画框边界框整体偏紧致不像自动标注工具出来的一堆 padding 过大的框。我抽查十几张后直接开始训练没有做框修正。3. 用 YOLOv8 训练自己数据集目录组织与参数设定3.1 数据集目录怎么摆data.yaml 怎么写拿到手先按 YOLO 标准目录结构重新组织不要直接拿解压的散文件开训。YOLOv8 的DatasetAPI 要求图片和标注分开而且路径默认相对data.yaml所在目录。我的习惯是在数据集根目录建images/train、images/val、labels/train、labels/val四个子目录按 8:2 比例随机切分。如果原包没给划分好的 train/val自己写脚本分注意随机种子固定保证可复现。# data.yaml path: /path/to/railing_dataset # 数据集根目录 train: images/train val: images/val nc: 2 names: [climbing, person]这里nc是类别数names的顺序要和标注 txt 里的 class id 严格一致。这个数据集里climbing是 class 0person是 class 1顺序反了训练出来的模型会张冠李戴。我在 YOLOv5 时代遇到过names 写反了训练完climbing类预测出的框实际是person区域损失函数看起来很正常一测就露馅。3.2 训练参数怎么选imgsz、batch、epochs 的底线值针对这种 1680 张的小数据集参数设置和跑 COCO 完全不一样。imgsz建议 640不要盲目上 1280——数据量撑不住高分辨率输入只会让模型过拟合到噪点纹理。batch取决于显存8GB 显卡用 816GB 用 16这个量级的图一张 640×640 大约占 3-4MB 显存。epochs建议 100-150配patience20早停。数据量少模型收敛很快第 30 轮 mAP 基本到顶后面全是震荡。yolo detect train \ modelyolov8s.pt \ datarailing.yaml \ imgsz640 \ batch16 \ epochs120 \ patience20 \ workers4 \ projectrun/railing \ nameexp01用yolov8s.pt而不是yolov8n.pt做预训练权重s 版的特征提取能力对付「身体姿态变化」这种细粒度差异比 n 版稳。workers 参数在 Windows 上别超过 4超过容易报 DataLoader 相关的内存错误。训练完看results.csv重点关注metrics/precision(B)和metrics/mAP50-95(B)只要验证集 mAP50 超过 0.8这个模型在真实场景里已经能用了。3.3 类别不均衡怎么处理climbing 框远多于 person 框2060 比 394将近 5.2:1 的比例。YOLOv8 默认的 BCE 损失不会自动做类别加权你要不处理模型会倾向于把所有目标都预测成climbing。处理方式有两种。第一种是简单粗暴的——数据增强时对 person 框多做裁剪缩放让模型看到更多尺度的行人第二种是调损失权重YOLOv8 训练脚本里传cls0.7降低分类损失的比重让回归分支更多作用于少样本类别。yolo detect train \ modelyolov8s.pt \ datarailing.yaml \ imgsz640 \ batch16 \ epochs120 \ cls0.7 \ box7.5 \ fliplr0.5我实际跑下来box7.5这个值是关键——默认 7.5 已经 OK改到 10 以上会让边界框过拟合改到 5 以下框的位置飘。fliplr0.5水平翻转对「翻越」动作是安全的左右翻不会改变语义但注意climbing框翻过去后类别不变这点和方向敏感的任务比如车牌不一样。4. 避坑清单增强图、类别混叠与标注噪声4.1 增强图片混入训练集过拟合的隐形推手现象训练 loss 正常下降验证集 mAP 到了 0.87但一到真实视频流测试误报率居高不下——静止的遮阳伞、摆动的树枝都能触发climbing。原因数据集的说明文件里明确写了「含部分增强图片」。增强的本质是对原图做亮度、对比度、翻转叠加模型学到的是「增强后的纹理特征」而非「翻越动作的结构特征」。增强图在原图上产生的梯度方向高度一致等于把同一条样本重复喂了 N 遍模型对光照变化过拟合。解决如果训练目标是鲁棒性优先进入训练前过滤掉增强图。增强图的 XML 里没有特殊标记只能靠文件名前缀或者标注文件的创建时间区分——这批资源里增强图文件名带.aug之类的后缀按后缀过滤掉大约能剔掉 300-400 张剩余纯原图约 1300 张照样够训。import shutil, os src_images images dst_images images_clean os.makedirs(dst_images, exist_okTrue) for f in os.listdir(src_images): if .jpg in f and .aug not in f: shutil.copy(os.path.join(src_images, f), dst_images)4.2 person 和 climbing 边界模糊框的分类标准在标注时就埋了雷现象训练出来的模型在测试视频里人站在栏杆旁边弯腰系鞋带被判定为climbing人骑在栏杆上静止不动反倒没触发报警。原因这个数据集的标注规则是「对类别进行画矩形框」但climbing的定义没有细化为「正在跨越」还是「身体接触栏杆」。翻越动作是一连串帧标注员只在某一帧画了框框内身体姿态往往卡在「半跨」状态与弯腰、探身的视觉相似度极高。解决训练完看混淆矩阵。如果person被误判为climbing的比例超过 5%说明你的场景需要的判定粒度比数据集提供的更细。两个方案一是训练完追加一个后处理规则——连续 N 帧都出现climbing框才输出报警避免单帧误判二是用这个数据集做预训练再录几段你真实场景的视频用 labelImg 补 50-100 张微调。我个人的做法装在人脸闸机口的场景用方案一装在围墙上的场景用方案二。4.3 labelImg 导出的 txt 坐标会越界归一化值超过 1.0现象训练到一半报RuntimeError: Assertion (w 0 h 0)检查某张图的 txt 发现x_center width/2 1.0。原因labelImg 画框时允许鼠标拖出图片边界框坐标超过图片宽度/高度。保存成 xml 时像素坐标越界了转 YOLO 后归一化值自然超 1。目标检测的矩形框本身允许超出图像边缘——物体被截断这是正常标注而非错误但 YOLO 训练管线不允许坐标越界。解决训练前统一跑一遍边界裁剪把所有越界框裁回[0, 1]区间。注意越界的框不能直接删删了等于丢掉一个标注目标模型会认为局部目标不可预测。def clamp_yolo_label(txt_path): with open(txt_path, r) as f: lines f.readlines() out_lines [] for line in lines: parts line.strip().split() cls_id, xc, yc, bw, bh map(float, parts) x1 xc - bw / 2 y1 yc - bh / 2 x2 xc bw / 2 y2 yc bh / 2 # 边界裁剪 x1 max(0, x1); y1 max(0, y1) x2 min(1, x2); y2 min(1, y2) if x2 x1 or y2 y1: continue # 完全在边界外的框直接丢弃 new_xc (x1 x2) / 2 new_yc (y1 y2) / 2 new_bw x2 - x1 new_bh y2 - y1 out_lines.append(f{int(cls_id)} {new_xc:.6f} {new_yc:.6f} {new_bw:.6f} {new_bh:.6f}\n) with open(txt_path, w) as f: f.writelines(out_lines)4.4 文件名后缀隐藏陷阱jpg 和 JPG 混排现象训练集 images 目录下同时有a.jpg和b.JPGYOLO 训练时报某一张图找不到对应标注文件。原因Windows 和 Linux 对文件大小写的敏感度不同解压工具在 Windows 上解压出来的文件扩展名可能是.JPG而标注文件名恒为.txt和.xml数据库读图片时按.jpg去匹配匹配不上。解决统一全部转成小写扩展名。import os for root, dirs, files in os.walk(images): for f in files: if f.endswith(.JPG) or f.endswith(.JPEG): old_path os.path.join(root, f) new_path os.path.join(root, f.lower()) os.rename(old_path, new_path)跑完再检查一遍data.yaml里的train路径确保目录名称也改成小写。5. 训练后的评估与模型部署mAP 高不等于能用5.1 用混淆矩阵定位误报源person 和 climbing 的互相渗透训练完 120 轮后我最先看的不是results.png里面那条漂亮的 PR 曲线而是confusion_matrix.png。这个数据集的混淆矩阵通常会暴露一个现象person的真实框有相当比例被预测成了climbing。原因直接指向标注规则——标注员会把「站在栏杆旁边身体前倾」的人画成climbing因为他们的心理预设是「这个动作和翻越有关」。这不算标注错误但对模型训练来说同一个视觉特征对应两个类别就是标签噪声。应对方式是引入置信度阈值调节。YOLOv8 默认conf0.25针对这个数据集我会拉到0.45。因为climbing的视觉特征集中在「身体跨过栏杆的高度」上高置信度的预测框基本都是真阳性误报大多发生在低置信度区间。验证方法简单在 val 集上分别用 0.25 和 0.45 跑两次推理对比误报数选择误报率拐点处的阈值。from ultralytics import YOLO model YOLO(run/railing/exp01/weights/best.pt) results model.val(datarailing.yaml, conf0.45, iou0.5) print(results.box.map50) # mAP50 print(results.box.map) # mAP50-955.2 视频流推理部署帧间去重与误报抑制训练完模型只是第一步实际部署到 RTSP 视频流里你会发现单帧推理的抖动特别大——同一帧画面里模型一会判climbing一会判person触发报警的帧不连续。原因是视频压缩带来的块效应让姿态特征不稳定模型在边界决策区域来回摇摆。我的处理方式维护一个长度为 10 帧的滑动窗口窗口内climbing出现 5 帧以上才输出报警事件且输出事件后 10 秒内不重复触发。这个机制能把误报率降一个数量级代价是漏掉极短促的动作——但现实中翻越栏杆本身是一个持续约 2 秒的动作10 帧窗口不会漏掉真实事件。class RailingDetector: def __init__(self, model_path, thr_frames5, cooldown10): self.model YOLO(model_path) self.climbing_count 0 self.total_frames 0 self.thr_frames thr_frames self.cooldown cooldown self.last_alert_time 0 def process_frame(self, frame, timestamp): results self.model.predict(frame, conf0.45, verboseFalse) has_climbing any(r.boxes.cls 0 for r in results) self.total_frames 1 if has_climbing: self.climbing_count 1 else: self.climbing_count 0 if (self.climbing_count self.thr_frames and timestamp - self.last_alert_time self.cooldown): self.last_alert_time timestamp self.climbing_count 0 return True # 触发报警 return False5.3 针对特定场景的二次标注用训练集数据微调还是自建补充集有些部署场景对误报零容忍比如高铁站台。这时候仅靠调阈值不够需要给模型「上强度」。我的做法是用这个数据集训练出一个基底模型然后部署到目标现场录 30 分钟视频把报警片段全部抽出来用 labelImg 给真实场景的翻越行为补标注——不需要多300 张左右就够微调。这批次标注的框和原数据集的标准不同原数据集的climbing框是「框住整个身体」我在标注时改成「框住躯干和腿部」——躯干姿态在跨越动作中的判别性远强于手臂。感受一下这个信息量一个只框身体的翻越检测模型在站台场景的误报率比我用全框模型直接部署低了 60%因为全框把栏杆和背景的大量纹理也包进来了。6. 翻越动作判定的进阶技巧用 climbing 框和 person 框的空间关系做事件过滤把模型输出的两类框拿来算几何关系比单看类别概率可靠得多。真实翻越事件中climbing框和person框存在明确的空间耦合——climbing 框的中心 x 坐标会落在 person 框中心 x 坐标附近且 climbing 框的纵向位置和 person 框有交叉。而误报场景里这两类框经常各自出现在画面不同区域互不相关。我做了个后处理过滤器当模型同时输出一个climbing框和一个person框时计算两者中心点的欧氏距离距离小于 person 框宽度的 0.8 倍且 climbing 框的 y 中心高于 person 框的 y 中心——才保留这次检测。实现如下def spatial_filter(boxes_cls, xyxy_list): boxes_cls: 每帧检测到的类别 id 列表 xyxy_list: 对应的边框坐标列表 返回过滤后保留的 climbing 框索引 import math climbing_idx [i for i, c in enumerate(boxes_cls) if c 0] person_idx [i for i, c in enumerate(boxes_cls) if c 1] keep_flags [False] * len(boxes_cls) for ci in climbing_idx: cx1, cy1, cx2, cy2 xyxy_list[ci] c_x_center (cx1 cx2) / 2 c_y_center (cy1 cy2) / 2 c_width cx2 - cx1 for pi in person_idx: px1, py1, px2, py2 xyxy_list[pi] p_x_center (px1 px2) / 2 p_y_center (py1 py2) / 2 p_width px2 - px1 dist math.sqrt((c_x_center - p_x_center) ** 2 (c_y_center - p_y_center) ** 2) if dist p_width * 0.8 and c_y_center p_y_center: keep_flags[ci] True break return keep_flags这个过滤器的逻辑是人正在跨越栏杆时climbing框的主体一定位于person框的上方——因为在跨越瞬间躯干重心已经越过栏杆。如果你检测到climbing框但找不到任何person框与它空间耦合大概率是栏杆纹理被误判成了翻越动作。跑到这里再回头看我踩过的坑——第一次用这个数据集时我没过滤增强图也没加空间关系判定直接在工地监控上测结果洒水车经过喷出的水雾被连续触发了 5 次报警。从那以后我每次拿到带增强的数据集都强制先走一遍「剔除增强图 → 坐标边界裁剪 → 空间关系后过滤」三条流水线常规训练流程反而放到最后。这个 2454 框的数据集用来做翻越检测的起点是够用的剩下的精度靠后处理补。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网