ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

翻越围栏检测:数据集预处理与YOLOv8训练调优指南

翻越围栏检测:数据集预处理与YOLOv8训练调优指南 简介面向翻越围栏/栏杆行为检测的目标检测数据集包含1680张真实场景图片采用Pascal VOC与YOLO双格式标注适合安全监控、智慧园区、周界防范等场景下训练行人越界识别模型。类别聚焦“climbing”与“person”其中climbing标注框2060个、person标注框394个总框数2454个全部由labelImg手工绘制矩形框完成标注精准且规则统一可直接用于YOLOv5/YOLOv8、Faster R-CNN等常见算法训练。压缩包共2000个文件以1680个XML标注文件、320个TXT文件为主整体约100.25MBVOC与YOLO格式可分别配合主流检测框架直接读取免去自行转换标注格式的麻烦。已有596人学习/下载。数据集内含部分增强图片可提升模型对角度、光照变化的鲁棒性既适合目标检测入门者练习从数据准备到模型评估的完整流程也适合算法工程师作为微调或基准测试的数据补充。1. 翻越围栏检测为什么这张数据集比想象中更需要预处理在安防与园区管理的视觉项目里翻越围栏/栏杆这类行为检测难点从来不是模型结构而是数据分布。一个监控画面中人可能只占几十个像素围栏区域又存在大量遮挡和透视畸变直接拿通用目标检测权重去跑误检率通常会高到没法上线。这份数据集包含1680张图片VOC与YOLO双格式标注类别只有climbing和person两类其中climbing框数2060、person框数394总框数2454。单看数字不大但它覆盖了攀爬瞬间与人员共存的场景适合用来微调YOLOv8/v11这类单阶段检测器也适合做行为识别的前置检测模块。需要先说清楚数据集中含部分增强图片且官方声明不保证训练精度所以下文会重点讲如何清洗、合并与增强把这份数据的价值真正榨出来。2. 认识数据形态VOC与YOLO双格式的结构差异与转换陷阱拿到压缩包后第一件事不是解压就训练而是先把目录结构摸清楚。压缩包内除了若干txt说明文件核心内容是jpg图片、VOC格式的xml文件和YOLO格式的txt文件。很多下载资源只给单一格式这份同时给两种省去了转换步骤但也埋了一个坑如果直接混合使用两个格式的标注去训练类别id对不上、坐标归一化方式不同会直接导致训练发散。2.1 双格式标注的字段映射关系先看VOC格式的xml文件。标注工具是labelImg所以xml结构是标准Pascal VOC风格核心字段如下annotation folderimages/folder filenameimg_0001.jpg/filename size width1920/width height1080/height depth3/depth /size object nameclimbing/name bndbox xmin312/xmin ymin456/ymin xmax598/xmax ymax732/ymax /bndbox /object /annotation而对应的YOLO格式txt文件中同一标注会写成0 0.2370 0.5500 0.1489 0.2556这里0代表climbing的类别id后面四个值是归一化后的中心点x、中心点y、宽度w、高度h。注意labelImg在保存YOLO格式时自动把像素坐标除以图片宽高做了归一化但如果后续用脚本自己写标注生成逻辑容易忘记除以宽高导致边界框跑出[0,1]区间训练时loss直接NaN。2.2 用脚本校验标注一致性下载的数据虽然标注数量写的是1680个xml和1680个txt但并不能保证每个xml和txt都同名字对应。常见的坑是某些图片只有xml没有txt或者txt是空的。训练前必须跑一遍校验脚本把不匹配的样本直接剔除或重新生成缺失格式。import os from pathlib import Path img_dir Path(images) xml_dir Path(annotations/xml) txt_dir Path(annotations/yolo) img_files list(img_dir.glob(*.jpg)) xml_files list(xml_dir.glob(*.xml)) txt_files list(txt_dir.glob(*.txt)) img_stems {f.stem for f in img_files} xml_stems {f.stem for f in xml_files} txt_stems {f.stem for f in txt_files} missing_xml img_stems - xml_stems missing_txt img_stems - txt_stems extra_xml xml_stems - img_stems extra_txt txt_stems - img_stems print(f缺失xml: {len(missing_xml)}, 缺失txt: {len(missing_txt)}) print(f多余xml: {len(extra_xml)}, 多余txt: {len(extra_txt)}) # 剔除无效样本 valid_stems img_stems xml_stems txt_stems print(f有效样本: {len(valid_stems)})这段脚本的意义在于用三个目录的stem集合求交集得到真正同时具备图片、xml、txt三个文件的样本。实际使用中我遇到过部分下载包中txt文件内容为空但文件存在这不会被上述逻辑发现所以还需要进一步检查每个txt内容是否非空且数值合法。for stem in valid_stems: txt_file txt_dir / f{stem}.txt lines txt_file.read_text().strip().splitlines() for line in lines: parts line.split() if len(parts) ! 5: print(f非法行: {txt_file} - {line}) continue cls_id, cx, cy, w, h map(float, parts) if not (0 cx 1 and 0 cy 1 and 0 w 1 and 0 h 1): print(f越界坐标: {txt_file} - {line})2.3 类别id与类别名对齐摘要里写了标注类别名称是[climbing,person]对应YOLO格式中0climbing, 1person。如果后续要在YOLOv8里训练配置文件data.yaml必须严格一致train: /path/to/train/images val: /path/to/val/images nc: 2 names: [climbing, person]这里的nc必须是2顺序不能乱。很多人会把names写成[person, climbing]导致原本的person框被当成climbing训练损失函数虽然下降但推理时结果完全错位。一个稳妥做法是训练前先随机抽3张图用训练好的权重跑一次预测或直接加载标注可视化。3. 数据增强策略针对稀疏类别与攀爬姿态的增广实操数据集中person框只有394个climbing框有2060个类别不平衡很明显。如果直接训练模型会倾向于把所有目标都预测为climbingperson的召回率会很难看。这里需要用针对性增强来平衡类别分布而不是简单随机翻转。3.1 基于Albumentations的定制增强管线我一般用Albumentations库做离线增强因为它在bbox变换上比torchvision的transforms更稳不会出现bbox和图像不同步的问题。针对翻越围栏场景有几种增强特别有效随机裁剪模拟不同镜头焦距、随机亮度对比度模拟早晚光线变化、随机灰度模拟夜间监控以及水平和垂直翻转垂直翻转需要谨慎因为真实监控很少倒置安装但可以增强模型对极端视角的鲁棒性。import albumentations as A import cv2 import numpy as np transform A.Compose([ A.RandomCrop(width640, height640, p0.5), A.HorizontalFlip(p0.5), A.RandomBrightnessContrast(brightness_limit0.3, contrast_limit0.3, p0.7), A.RandomGamma(gamma_limit(80, 120), p0.5), A.MotionBlur(blur_limit5, p0.3), # 模拟人快速翻越时的动态模糊 ], bbox_paramsA.BboxParams(formatyolo, label_fields[class_labels]))注意formatyolo表示输入的bbox是归一化后的cx, cy, w, h格式。label_fields必须传递类别标签列表否则增强器不知道每个框对应哪个类别。执行增强时要把图片读成BGR或RGB的numpy数组然后调用transform。image cv2.imread(images/img_0001.jpg) image cv2.cvtColor(image, cv2.COLOR_BGR2RGB) with open(annotations/yolo/img_0001.txt) as f: bboxes [] class_labels [] for line in f: parts line.strip().split() bboxes.append([float(x) for x in parts[1:]]) class_labels.append(int(parts[0])) augmented transform(imageimage, bboxesbboxes, class_labelsclass_labels) aug_img augmented[image] aug_bboxes augmented[bboxes] aug_labels augmented[class_labels]这里有个关键参数RandomCrop(width640, height640)会改变图片尺寸导致bbox坐标重新归一化Albumentations会自动处理。但如果原本标注框就靠近图片边缘裁剪后框可能被切掉一半此时增强器默认会丢弃超出边界的框。对于翻越场景围栏通常位于画面中下部裁剪时建议把裁剪区域中心偏向中下方不要让围栏区域被切掉。3.2 类别不平衡的过采样策略climbing框是person框的5倍多但climbing对象本身是person的翻越状态语义上两者高度相关。一个有效的做法不是直接复制person图片而是对包含person的图片做更强的增强让每个person样本产生多个变体。我写过一个小脚本统计每个txt文件中person类别的数量如果数量为0把该图片加入过采样列表做多组不同的增强然后合并到原数据集。from collections import Counter rare_images [] for stem in valid_stems: txt_file txt_dir / f{stem}.txt labels [] for line in txt_file.read_text().strip().splitlines(): labels.append(int(line.split()[0])) counter Counter(labels) if counter.get(1, 0) 0: # person类id1 rare_images.append(stem) print(f缺少person标注的图片: {len(rare_images)})对于这些图片用上一节的transform生成4个增强版本写入新的jpg和txt文件。生成后整个数据集中person框的占比会明显提升接近climbing的1:2而不是1:5。3.3 混合增强Mosaic与Copy-Paste在YOLOv8训练时内置的mosaic增强已经默认开启它会把4张图拼在一起。但mosaic有个副作用当图片中包含person小目标时拼接后目标更小person的检测难度进一步加大。因此我的习惯是训练前2/3的epoch开启mosaic后1/3关闭或者直接设置mosaic0.0改用copy-paste增强。Copy-paste就是把一张图中的person实例粘贴到另一张图中这个操作对翻越场景特别有用因为很多监控画面中确实存在多个人、有人翻越有人旁观的情况。Albumentations没有内置copy-paste需要自己实现。基本思路是从一张图中裁剪出person框区域随机旋转缩放后粘贴到另一张图的任意位置同时更新粘贴目标图的txt标注。需要注意遮挡关系——如果粘贴的围栏区域有大量铁栏杆纹理简单粘贴会导致语义冲突。所以我会限制粘贴位置避开原图中的climbing框区域并且给粘贴区域做一点高斯模糊或噪声扰动让它更真实。4. YOLOv8/v11训练与参数调优如何从1680张图中拿到可用模型数据集本身只有1680张含增强图片直接训大模型很容易过拟合。我建议先跑YOLOv8n或YOLOv11n如果精度不够再往上一档。这里把从环境到训练结束的完整参数链讲清楚。4.1 数据集目录划分与YAML配置解压后把图片和标注整理成以下结构训练脚本才不会报错dataset/ images/ train/ # 约1300张 val/ # 约380张 labels/ train/ val/划分时注意不能随机划分要保证同一个场景的连续帧要么全在train要么全在val。翻越数据集中可能包含从视频抽帧的序列连续帧之间相似度极高如果随机划分val集会泄露训练信息验证指标虚高。我一般会先按文件名前缀分组再按组划分。from sklearn.model_selection import GroupShuffleSplit import numpy as np stems list(valid_stems) groups [s.split(_)[0] for s in stems] # 假设前缀是场景Id gss GroupShuffleSplit(n_splits1, test_size0.22, random_state42) train_idx, val_idx next(gss.split(stems, groupsgroups)) train_stems [stems[i] for i in train_idx] val_stems [stems[i] for i in val_idx]然后为YOLOv8准备data.yamlpath: /absolute/path/to/dataset train: images/train val: images/val nc: 2 names: 0: climbing 1: person4.2 关键训练参数与损失函数调整用YOLOv8命令训练yolo train datadataset/data.yaml modelyolov8n.pt epochs100 imgsz640 batch16 lr00.01 lrf0.01 momentum0.937 weight_decay0.0005 warmup_epochs3 box7.5 cls0.5 dfl1.5 hsv_h0.015 hsv_s0.7 hsv_v0.4 mosaic1.0参数说明box7.5是边框回归损失的权重系数cls0.5是分类损失的权重dfl1.5是分布焦点损失的权重。当数据集类别不均衡时我通常把cls提升到0.7甚至1.0让模型更重视person类的分类错误。mosaic1.0默认开启但如果你发现val精度在第80个epoch后明显波动可以改成mosaic0.0再训20个epoch做微调。另一个重要参数是close_mosaicYOLOv8默认在最后10个epoch关闭mosaic这个不要改动它能让模型适应真实图像的分布。imgsz640对于监控场景可能不够因为监控原图往往大于1920×1080翻越的人很小。如果显存够直接imgsz1280训练小目标召回率会显著提升。注意这会让训练时间加倍且对AUC等指标的提升不一定值回成本。更推荐的做法是保持640训练然后在推理时用imgsz1280做测试时增强test-time augmentation。4.3 增强图片带来的过拟合风险与早停策略数据集中含增强图片这意味着某些图片是原始图的水平翻转或亮度变化版本。如果增强图片和原图同时被分到训练集模型会更容易记住这些变体导致在真实场景的泛化能力下降。我习惯在训练前先跑一个重复图片检测脚本找出感知哈希相似的图片对把其中一张归入val而不是train。from PIL import Image import imagehash def phash_distance(path1, path2): h1 imagehash.phash(Image.open(path1)) h2 imagehash.phash(Image.open(path2)) return h1 - h2 # 按phash距离阈值筛选阈值5视为相似图片训练时用早停来控制过拟合。YOLOv8自带patience参数例如patience20表示val损失连续20个epoch不下降就停止。对于1680张图的小数据集我通常会设置patience30因为这种小数据的val损失波动本身就大早停太激进会提前终止训练浪费掉后续可能出现的精度提升。5. 推理部署与场景适配把模型塞进监控流的最终拦截环节训练完的模型如果不能以正确的帧率跑在监控流或边缘设备上前面所有数据功夫都白费。翻越围栏检测的部署通常分两类一是RTSP实时流分析二是本地视频文件批处理。这里给出一个基于YOLOv8的轻量推理脚本它同时处理帧间隔和置信度过滤。5.1 实时流推理与关键帧策略import cv2 from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) cap cv2.VideoCapture(rtsp://your_camera_stream) fps cap.get(cv2.CAP_PROP_FPS) frame_interval max(1, int(fps / 5)) # 每秒处理5帧 frame_id 0 while cap.isOpened(): ret, frame cap.read() if not ret: break if frame_id % frame_interval ! 0: frame_id 1 continue results model.predict(frame, imgsz640, conf0.45, iou0.5, device0) annotated results[0].plot() cv2.imshow(fence_detection, annotated) if cv2.waitKey(1) 0xFF ord(q): break frame_id 1这里的关键参数是conf0.45。如果翻越行为中有大量遮挡或远距离小目标0.45会漏掉不少真实climbing框。但降到0.25又会带来大量误检。实际项目中我会分两级处理第一级用低置信度conf0.2做全量检测第二级只对climbing类别做轨迹追踪利用多帧一致性来过滤误检。追踪可以用ByteTrack或简单的IoU匹配这里不展开。5.2 围栏区域ROI限制固定摄像头下用一个ROI多边形把围栏区域框出来只检测ROI内的目标能大幅降低误检。YOLO检测出所有person和climbing后判断候选框中心点是否位于ROI内import numpy as np roi_polygon np.array([[560, 240], [1320, 200], [1500, 760], [420, 720]], dtypenp.int32) def in_roi(x1, y1, x2, y2): cx, cy (x1 x2) / 2, (y1 y2) / 2 return cv2.pointPolygonTest(roi_polygon, (cx, cy), False) 0 for box in boxes: if in_roi(*box): # 执行翻越判断逻辑 passROI的设置不需要精确到像素只要把围栏两侧的缓冲区域都框进去即可。注意如果摄像头有云台转动ROI会失效需要启用动态ROI或重新标定。5.3 模型导出与Jetson/OpenVINO加速训练完的pt权重可以直接导出为TensorRT引擎或OpenVINO的IR格式部署在Jetson Nano或Intel NUC上。导出命令yolo export modelruns/detect/train/weights/best.pt formatengine device0 halfTrue imgsz640其中halfTrue启用FP16推理实测在Jetson Orin Nano上YOLOv8n从FP32的30ms降到FP16的18ms帧率从33fps提升到55fps精度损失通常小于0.5%。如果目标是x86平台的CPU导出为ONNX后用OpenVINO的benchmark工具测试yolo export modelbest.pt formatonnx dynamicTrue benchmark_app -m best.onnx -i test.jpg -d CPU -api asyncdynamicTrue允许输入shape可变但会牺牲部分推理速度如果摄像头分辨率固定关闭dynamic改为imgsz640可获得更多优化空间。6. 验证数据质量的一个实用技巧标注可视化与误检热力图最后聊一个我拆这类数据集时必做的验证技巧把训练集的所有标注画到图上肉眼扫一遍标注质量。很多数据集下载下来标注数量虽然正确但边界框严重偏离目标或遗漏了某个类别如果直接训练模型会学坏。写一个批量可视化脚本把每张图的检测框画出来并保存为视频或拼接图。from PIL import Image, ImageDraw from pathlib import Path def draw_yolo_boxes(image_path, txt_path, out_path): img Image.open(image_path).convert(RGB) draw ImageDraw.Draw(img) w, h img.size with open(txt_path) as f: for line in f: cls_id, cx, cy, bw, bh map(float, line.split()) x1 (cx - bw / 2) * w y1 (cy - bh / 2) * h x2 (cx bw / 2) * w y2 (cy bh / 2) * h color red if int(cls_id) 0 else blue draw.rectangle([x1, y1, x2, y2], outlinecolor, width3) img.save(out_path)把生成的图片按2×4拼成蒙太奇快速翻一遍重点看两类错误一是climbing框是否框住了完整的人体在围栏上方的动作还是只框了身体局部二是person框是否漏掉了一些站立在围栏附近的人。对于已经确认的错误标注直接用labelImg打开修正或者写脚本剔除这些样本。通过这个验证流程往往能剔除掉约3%5%的坏标注对最终模型精度的提升比多训50个epoch还明显。此外如果训练完成后想量化模型在哪里容易误检可以跑一批val图片把所有预测框与真实框对比把置信度高于0.7但IoU低于0.3的预测框单独画出来并叠加成热力图看误检集中出现的区域。如果是围栏反光区域导致误检可以用上一节的ROI逻辑直接屏蔽。如果是远处小人被误检为climbing则考虑适当降低imgsz训练因为大分辨率下小目标更容易被激活但也更容易产生噪声。本文还有配套的精品资源点击获取
返回列表