ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

YOLO人体行为检测数据集实战:从标签格式校验到模型训练排错

YOLO人体行为检测数据集实战:从标签格式校验到模型训练排错 简介面向目标检测初学者、YOLO实践者及需要人体行为数据的计算机视觉课程设计这份数据集提供了1000张真实场景下的人体行为图片标注覆盖VOC、COCO、YOLO三种格式分别对应xml、json、txt可直接用于YOLO系列模型训练与评测。压缩包共2000个文件以xml和txt标签文件为主体配套py数据划分脚本、yaml模型配置以及多份环境搭建和训练教程文档整体大小约68.55MB结构清晰易于检索。目前已有515人学习下载。除了数据本身资料还额外提供了Windows与Linux双版本的环境搭建和训练案例教程讲解从环境配置、yaml参数调整到执行训练的完整流程配套的三个数据集划分脚本可按需生成训练集、验证集和测试集省去格式转换与手动整理的时间。对想尽快跑通人体行为检测流程的人而言从数据准备到模型训练都有现成路径可参考能显著降低入门门槛。1. YOLO人体行为检测数据集拿到手先别急着开训如果你正在做安防场景的打架斗殴识别、门店客流动线分析或者体测场景里的引体向上计数大概率会遇到同一个尴尬通用目标检测模型能把人框出来但框出来之后“人在干什么”完全答不上来。YOLO人体行为检测数据集(含1000张图片)的价值就在这儿——人和行为绑在一起标注同时给到VOC、COCO、YOLO三种格式标签、划分脚本和训练教程拿到手就能直接进入YOLO训练流程。我的判断是单场景原型验证够用直接部署到真实业务还需要二次标注和扩充。这篇整理会沿着“格式校验→数据划分→训练调参→排错→实测验证”这条路径完整走一遍新手按步骤执行熟手可以直接跳到避坑章节查参数边界。2. VOC、COCO和YOLO三种标签格式从目录读到框坐标第一关先过格式同一个标注框为什么要同时给VOC、COCO、YOLO三种格式这不是冗余而是三个生态读取标签的方式完全不同。VOC是“一张图配一个XML文件”COCO是“整个数据集汇总成一个JSON”YOLO是“一张图配一个同名txt”。很多人直接把压缩包里的标签丢给训练脚本结果训练中期发现坐标错位、类别ID错位返工成本比省下的时间高得多。这一章先把三种格式的读取逻辑和最容易出问题的地方讲清楚。2.1 VOC格式XML、绝对坐标和size一致性检查VOC格式来自PASCAL VOC挑战赛目录约定一般是Annotations放XML文件、JPEGImages放原图。每个XML里最关键的是object节点name存行为类别名比如standing、walking、falling这类动作标签bndbox里是xmin、ymin、xmax、ymax四个绝对像素坐标文件头部的size节点写图像的width、height、depth。一个常见坑是XML里声明的尺寸和实际图片尺寸不一致。图像被批量压缩过、标注工具自动改写了尺寸但XML没同步更新都会出现这种情况。这种错位在转YOLO归一化坐标时会直接污染所有框。所以我拿到VOC部分后第一件事是跑尺寸一致性检查import os import xml.etree.ElementTree as ET from PIL import Image voc_dir VOC/Annotations img_dir VOC/JPEGImages for xml_name in os.listdir(voc_dir): if not xml_name.endswith(.xml): continue xml_path os.path.join(voc_dir, xml_name) img_path os.path.join(img_dir, xml_name.replace(.xml, .jpg)) tree ET.parse(xml_path) root tree.getroot() size root.find(size) xml_w int(size.find(width).text) xml_h int(size.find(height).text) with Image.open(img_path) as img: real_w, real_h img.size if xml_w ! real_w or xml_h ! real_h: print(f[尺寸不一致] {xml_name}: XML {xml_w}x{xml_h}, 实际 {real_w}x{real_h})这段代码把每张图片的实际尺寸和XML声明尺寸做比对不一致就打印。输出为空说明VOC标签底座干净有输出就把对应XML的size改掉或回到标注工具里重新导出不要在脏底座上做后续转换。另一个容易忽略的点是图片扩展名数据集里如果混用jpg、jpeg、png上面用replace(.xml, .jpg)会漏检png图建议用os.path.splitext取主文件名再拼真实图片路径。2.2 COCO格式JSON三件套和bbox的单位陷阱COCO格式把所有标注汇总到一个JSON文件里核心是images、annotations、categories三个数组。images里每个元素有id、file_name、width、heightannotations里每个元素有id、image_id、category_id、bbox、area、iscrowdcategories里是id和name的对应关系。最容易翻车的点是COCO的bbox格式是[x, y, width, height]不是[x1, y1, x2, y2]更不是中心点到中心点的写法。我见过不止一个同事在转YOLO时把COCO的bbox当成center_x, center_y, width, height训练出来的框整体向右下偏移在验证集上mAP直接掉到零点几。另外category_id通常是连续整数VOC里是字符串类名转换时要先建立类名到数字的映射表而不是随手按读取顺序分配否则同一个JSON前一次转换和后一次转换得到不同的编号排查时非常痛苦。想快速核对JSON里的bbox是否越界可以这样import json with open(COCO/annotations.json, r) as f: coco json.load(f) img_map {img[id]: img for img in coco[images]} for ann in coco[annotations]: img img_map.get(ann[image_id]) if not img: continue x, y, w, h ann[bbox] if x 0 or y 0 or x w img[width] or y h img[height]: print(f越界标注 image_id{ann[image_id]}, bbox{ann[bbox]})bbox超出图像边界时YOLO训练会产生大量错位正样本模型很难收敛。即便只有几个像素的越界经过随机裁剪和缩放增强也会被放大。这个检查脚本十几行能挡掉后面一堆看着莫名其妙的问题。2.3 YOLO格式txt归一化坐标与类别ID映射YOLO格式每张图片对应一个同名txt。每一行代表一个目标格式是“class_id x_center y_center width height”。注意四个坐标值全部归一化到0到1之间中心点x和y分别除以图像宽度和高度框的宽和高也分别除以图像宽度和高度。也就是说YOLO的width是一个比例值不是像素宽度。这个格式看起来简单但坑在类别ID的对应关系。同一个数据集用YOLOv5训练和用YOLOv8训练data yaml里定义的names顺序必须和txt里的class_id完全一致否则“人摔倒”这个标签会被网络当作“人下蹲”来学训练过程还不会报任何错。拿到txt后我会先统计每个类别的框数量分布import os label_dir YOLO/labels class_count {} for name in os.listdir(label_dir): if not name.endswith(.txt): continue with open(os.path.join(label_dir, name), r) as f: for line in f: parts line.strip().split() if len(parts) ! 5: print(f格式异常: {name}: {line.strip()}) continue cls parts[0] class_count[cls] class_count.get(cls, 0) 1 for cls in sorted(class_count): print(f类别ID {cls}: {class_count[cls]} 个框)统计输出能同时完成两件事检查txt行格式是否有缺列或空行以及看类别分布是否均衡。1000张图片如果有六七个行为类别单个类别分到几百个框算正常但低于50框的类别基本只能靠预训练权重里的先验撑着纯数据训练很难保证检测效果。2.4 用IoU交叉验证三份标签是否真的来自同一批框同一个数据集里三种格式是标注工具导出的正常情况应该是同一批框。但导出工具版本差异、人工修改漏改、脚本四舍五入都可能让三份标签互相矛盾。训练前用交并比IoU做一次三方交叉验证成本最低。做法是选同一个bbox从VOC的XML里读出绝对坐标从YOLO的txt里结合图片宽高还原出绝对坐标两者算IoU。全部样本平均IoU应当接近1.0最低不能低于0.95。手动写代码如下import os import xml.etree.ElementTree as ET from PIL import Image def voc_box(xml_path): root ET.parse(xml_path).getroot() boxes [] for obj in root.findall(object): b obj.find(bndbox) boxes.append([ int(float(b.find(xmin).text)), int(float(b.find(ymin).text)), int(float(b.find(xmax).text)), int(float(b.find(ymax).text)), ]) return boxes def yolo_box(txt_path, img_w, img_h): boxes [] with open(txt_path, r) as f: for line in f: parts line.strip().split() if len(parts) ! 5: continue _, x_c, y_c, w, h parts x_c, y_c, w, h float(x_c), float(y_c), float(w), float(h) x1 (x_c - w / 2) * img_w y1 (y_c - h / 2) * img_h x2 (x_c w / 2) * img_w y2 (y_c h / 2) * img_h boxes.append([x1, y1, x2, y2]) return boxes def iou(b1, b2): x1, y1 max(b1[0], b2[0]), max(b1[1], b2[1]) x2, y2 min(b1[2], b2[2]), min(b1[3], b2[3]) inter max(0, x2 - x1) * max(0, y2 - y1) area1 (b1[2] - b1[0]) * (b1[3] - b1[1]) area2 (b2[2] - b2[0]) * (b2[3] - b2[1]) union area1 area2 - inter return inter / union if union 0 else 0 total, n 0, 0 for name in os.listdir(VOC/Annotations): if not name.endswith(.xml): continue xml_path os.path.join(VOC/Annotations, name) txt_path os.path.join(YOLO/labels, name.replace(.xml, .txt)) img Image.open(os.path.join(VOC/JPEGImages, name.replace(.xml, .jpg))) vb voc_box(xml_path)[0] yb yolo_box(txt_path, *img.size)[0] score iou(vb, yb) total score n 1 if score 0.95: print(f{name} IoU{score:.4f}) print(f平均IoU{total / n:.4f})这段脚本只取了每个文件的第一框做对比实际应用时应该遍历全部框并做匹配。重点不是代码本身而是平均IoU能到多少。如果低于0.95说明三份标签不是同一份数据导出的。这时候必须指定其中一份作为唯一基准比如一律以YOLO标签为准其他格式只用于可视化核对不要混着用。3. 划分脚本的落地细节训练、验证、测试集不能随手shuffle数据划分看起来是最简单的环节实际上人体行为检测数据里藏着一个容易被忽略的陷阱同一个人、同一个摄像头、连续动作产生的图片具有高度相关性。如果随机划分时没有按场景或按人物做分组模型很容易在验证集上得到虚高的mAP部署到新场景立刻打回原形。这一章讲清楚划分脚本应该怎么用以及划分完要检查什么。3.1 先按场景分再按图片分人体行为数据为什么不能随机切假设1000张图片里有一个人在不同时间段的走路、跑步、蹲起拍摄背景是同一个工位摄像头。如果直接按图片随机切训练集和验证集里都会出现这个人的大量相似帧。模型学到的是“这个背景这个人”不是“这个动作”。验证集上来看什么都准换一个摄像头、换一个人、背景换掉之后精度断崖式下跌。正确的做法是先记录每张图片的来源场景或人员编号把同一个人的同一个连续动作片段作为一个不可分割的单元再对单元做划分。RAR包里虽然带了划分脚本但脚本默认是按文件名列表均分行为本身可能没有按场景聚类。所以我拿到划分脚本后第一步不是直接跑而是看图片命名是否包含人物编号或时间戳。常见命名如personA_walk_001.jpg可以按前缀切分避免同一个人横跨训练集和验证集。3.2 一个带固定随机种子的通用划分脚本不管原脚本怎么写的我自己通常会在项目里保留一份固定随机种子的划分脚本既能复现结果也能随时调整比例。下面这个脚本按文件名前缀分组后划分保证同一个主体的图片只进入一个集合import os import random from collections import defaultdict from shutil import copy2 random.seed(42) img_dir images label_dir labels train_ratio, val_ratio 0.6, 0.2 def get_group(name): # 根据命名风格取前缀例如 personA_walk_001.jpg - personA_walk parts name.rsplit(_, 1) return parts[0] if len(parts) 1 else name groups defaultdict(list) for fname in os.listdir(img_dir): if not fname.lower().endswith((.jpg, .jpeg, .png)): continue groups[get_group(fname)].append(fname) group_names list(groups.keys()) random.shuffle(group_names) n_train int(len(group_names) * train_ratio) n_val int(len(group_names) * val_ratio) train_groups set(group_names[:n_train]) val_groups set(group_names[n_train:n_train n_val]) test_groups set(group_names[n_train n_val:]) def split_and_copy(groups, dest): os.makedirs(f{dest}/images, exist_okTrue) os.makedirs(f{dest}/labels, exist_okTrue) for g in groups: for fname in groups[g]: stem os.path.splitext(fname)[0] copy2(os.path.join(img_dir, fname), f{dest}/images/{fname}) label_path os.path.join(label_dir, stem .txt) if os.path.exists(label_path): copy2(label_path, f{dest}/labels/{stem}.txt) split_and_copy(train_groups, train) split_and_copy(val_groups, val) split_and_copy(test_groups, test) print(ftrain: {sum(len(groups[g]) for g in train_groups)} 张) print(fval: {sum(len(groups[g]) for g in val_groups)} 张) print(ftest: {sum(len(groups[g]) for g in test_groups)} 张)核心逻辑是先按前缀聚合成组再对组做随机打乱和划分最后把图片和同名的txt标签一起复制到目标目录。random.seed(42)保证每次运行结果一致方便复现训练实验。train_ratio和val_ratio分别控制训练集和验证集比例剩余全部进测试集如果只做YOLO训练不评测可以令val_ratio0.3、test_ratio0但建议永远留出测试集后面用到。注意这个脚本默认图片和label在平级目录如果数据集里labels按VOC/Annotations或COCO形式组织要先统一成images/和labels/两个平级目录。转换格式时把YOLO标签当基准即可VOC和COCO标签仅做归档。3.3 划分完必查的三个校验点划分完成后不要急着训先用下面三个规则自查能避免大半天无效训练。第一训练集和验证集不能有同名的图片文件。直接对比两个目录里的文件名集合交集为空才算通过。第二验证集和测试集必须覆盖所有行为类别尤其样本少的类别。有些类别一共只有20个框如果随机划分后验证集里一个都没有训练时mAP曲线看起来正常但实际那个类别完全没被评估到。第三抽查几张验证集图片的标注框确认没有空标签文件混进来。空txt会导致YOLO训练时该图片被跳过或产生负样本警告虽然不影响进程但会减少有效训练数据量。我一般还会额外看一眼划分后的图片尺寸分布。如果数据集里图像长宽比差异很大比如有1080p横图和720p竖图混在一起训练时统一resize到640×640会导致人体被严重拉伸变形行为类别的判别会更难。这种情况要么在yaml配置里加rectTrue自动分组要么先把异常尺寸的图过滤掉。4. YOLO训练全流程从环境配置到一条可复用的训练命令格式校验和划分都做好之后才轮到真正训练。这一章以当前最常见的YOLOv5和YOLOv8两条路线为例覆盖环境安装、数据集配置文件、训练命令和关键超参。新老版本命令有差异但核心配置思路一致照着改就行。4.1 环境与预训练模型选型训练环境最常见的选择是YOLOv8加ultralytics一张显存8G以上的卡就能跑。安装时建议用conda独立环境避免和已有项目冲突。命令行如下conda create -n yolo python3.10 -y conda activate yolo pip install ultralytics如果已经下载过YOLO预训练模型比如yolov8n.pt可以直接从本地指定权重没有的话ultralytics会自动下载对应的预训练权重。这里建议优先用yolov8n.pt或yolov5s.pt起步这两个模型体量小1000张图片的数据量完全喂得动。第一次跑通流程后想提升精度再换yolov8m.pt或yolov5m.pt。预训练模型下载可能受网络环境影响失败收到超时或连接错误提示时手动把.pt文件下载后放到当前目录训练命令里的model路径直接写文件名即可。不要从网盘随便找来源不明的权重优先从官方发布渠道获取。4.2 数据集YAML怎么写才不踩类别ID的坑无论是YOLOv5还是YOLOv8训练前都要一个数据配置文件里面写清楚图片路径、标签路径和类别名称列表。以本数据集的YOLO标签为准我习惯放在项目根目录下写成behavior.yamlpath: ./ train: train/images val: val/images test: test/images names: 0: standing 1: walking 2: running 3: falling 4: sitting这里最关键的约束是names字典的索引顺序必须和txt标签里的class_id一一对应。如果数据集自带的classes.txt或训练教程里给出了类别顺序直接按那个顺序写没有的话打开任意一个标签txt看第一列数字的最大值就能确定类别数。path字段是相对当前运行的根目录YOLOv8里如果设置成./那么train: train/images就代表根目录下的train/images目录这个写法比写绝对路径更稳换机器不用改配置。4.3 训练命令与关键超参数说明YOLOv8执行训练的命令非常短yolo train databehavior.yaml modelyolov8n.pt epochs100 batch16 imgsz640 device0YOLOv5则是python train.py --data behavior.yaml --weights yolov5s.pt --epochs 100 --batch-size 16 --img 640 --device 0两者的参数含义基本一致data指向数据集yamlmodel或weights指定预训练权重epochs是训练轮数batch是每批图片数imgsz或img是输入尺寸device指定用第几张GPU。对1000张图片来说100个epoch、batch 16、imgsz 640这三个值是稳妥起点。训练刚开始那几十个轮次里loss不降反升是正常现象因为预训练权重是在COCO上学的通用物体特征迁移到行为类别后需要先丢弃一部分旧知识。通常到第20到30个epoch会看到明显下降。如果到40个epoch还在持续上升说明学习率可能过大或者标签有系统性错位参考第五章排查。4.4 imgsz、batch、epoch与早停的搭配这几个参数互相牵制不是越大越好。imgsz越大保留的细节越多但显存占用按平方增长batch也必须同步调小。1000张图片数据集里人物主体宽度如果只占画面的1/3640的输入尺寸其实只能给到约200像素的宽度行为细节会被吞掉一部分。显存够用的情况下建议用imgsz960试一轮对比。batch大小影响BN层的统计量。人体行为检测的标签中人物大小变化很大batch太小的时候BN统计不稳定容易训练到一半loss抖动甚至NaN。8G显存跑YOLOv8n时batch 16是底线batch 8能跑但稳定性明显变差。epoch不是越大越好。1000张图片反复喂给模型到80个epoch以后基本进入过拟合区。YOLOv8默认开了早停连续50个epoch验证集mAP没有提升就自动停YOLOv5需要自己加--patience 50参数。这里不要迷信教程里的固定epoch数以早停和验证集mAP曲线为准。训练过程中的关键参数可以参考下表按需调整参数推荐区间作用与注意事项imgsz6401280数值越大细节越多显存和训练时间同步上升batch832影响BN稳定性显存不足时先用batch 8跑通lr0.0010.01迁移学习场景默认0.01即可震荡时降到0.001patience3050验证集mAP连续不涨即停止训练mosaic默认开数据增强主要来源但某些动作姿态会被切碎5. 人体行为检测训练排错五个真实踩坑记录这一章记录我实际踩过的问题。每一条都有现象、原因和解决办法碰到类似情况可以直接照做。5.1 loss在几十个epoch后不降反升现象训练到第30个epoch左右训练loss开始回头上涨验证loss同步上升mAP没有改善。原因最常见的是学习率设置过大模型在损失曲面边缘来回震荡无法收敛到谷底。另一种情况是数据增强强度太高比如mosaic把所有人物撕碎重拼行为姿态的上下文被破坏网络学不到稳定特征。解决先把学习率降到十分之一重跑YOLOv8里用lr0.001指定YOLOv5用--lr 0.001。如果学习率降下来仍然不收敛打开关闭mosaic增强对比一次命令里加mosaic0.0确认增强是否干扰了行为语义。5.2 验证mAP始终为0但loss正常现象训练loss一直在降但验证集上每个类别的mAP都是0没有任何框被正确匹配。原因九成是验证集标签的类别ID和模型输出类别ID错位。比如训练时yaml里的names顺序是standing、walking、running但验证集是另一套txt里面的class_id 0代表walking。网络输出的是standing验证时按walking去匹配自然全错。解决把训练集和验证集的所有txt重新用同一个脚本清洗一遍按统一映射表重写class_id。清洗完再跑一次yolo val这个坑排掉之后mAP会立刻跳到合理区间。5.3 行为类别样本数悬殊导致漏检现象某个行为类别在训练集里只有二三十个框训练结束后这个类别的召回率极低几乎检测不到。原因1000张图片的标注分布天然不均匀常见动作比如走路可能占600个框冷门动作比如摔倒可能只有20个框。YOLO的损失函数按类别累计冷门类别的梯度被热门类别主导网络倾向于把所有目标都预测成热门类别。解决先增加冷门类别的图像数量能补标注就补这是最直接的路径。不能补的在训练时调大冷门类别的loss权重YOLOv8可以选择给loss_ot0.05这类参数或者直接使用类别权重文件。更现实的做法是把冷门行为合并成一个大类比如把摔倒、倒地、抽搐合并成“异常姿态”降低类别粒度来换取可用性。5.4 训练中期loss突然NaN或BN崩溃现象训练到某个epoch时loss突然变成NaN或验证集的BN统计量剧烈抖动精度断崖式下跌。原因常见诱因有三个——学习率起步太大导致梯度爆炸训练集里出现异常标签比如某个txt包含负数坐标或大于1的归一化值batch太小加上输入图中包含纯黑图像BN统计量崩坏。解决先检查标签里有没有非法坐标数值小于0或大于1的直接删掉对应行。然后调小学习率用lr0.001重跑。如果还是崩把batch从8提升到16同时关掉mosaic试试。BN崩溃的情况下最简单的后悔药是恢复到一个早期稳定epoch的权重比如第20个epoch的pt文件调低学习率续训练。5.5 验证结果虚高部署后效果打回原形现象验证集mAP有0.85但换一个摄像头、换一个人测试检出率掉到不到一半。原因划分脚本没有按人物或场景分组同一个人的相似画面横跨训练集和验证集。模型学的不是“摔倒”这个动作的共性而是训练集里那个人的外观和背景。解决回到第三章按人物前缀重新划分数据让验证集完全由训练集没见过的人或场景组成。训练后不要只看总体mAP按人物ID分别统计单个场景的mAP。这一条最花时间但也是人体行为检测项目能不能落地的分水岭不做这一层的验证训练的数值没有任何意义。6. 进阶验证技巧与数据集扩容建议6.1 混淆矩阵不要只看对角线YOLO训练结束会输出归一化混淆矩阵很多人只看对角线的数字大就认为模型好。实际要关注的是矩阵里的最大错误位置walking被预测成running还是standing被预测成sitting。这两个错误背后是完全不同的原因。前者是动作姿态相近需要更高分辨率的输入后者是语义场景相近需要更多区分性的上下文特征。如果某个错误对占全部错误的一半以上优先针对那对类别补数据比盲目增加整体数据量更有效。6.2 小批量冒烟训练正式训练前用20%的数据跑30个epoch直接在验证集上看mAP趋势。这一步能提前暴露标签错位、路径配置错误和环境问题几分钟就能跑完。我会在项目里固定保留一个smoke.yaml只指向训练集前200张图和对应验证集专门用来做冒烟测试。等冒烟测试的mAP曲线呈现正常的先降后升趋势再启动全量训练能省掉大量无效等待时间。6.3 从1000张走向自有场景数据这套数据集的瓶颈在于场景单一、人物有限只能作为起点。真实项目里我会继续收集目标场景的半小时视频抽帧到5到10帧每秒用训练好的模型先生成一批伪标签人工修正错检漏检后回注数据集。这种自举扩数据的方式能让数据量在两周内翻倍代价是人工校注的时间。如果预算允许优先补充的是冷门行为类别和极端视角这两类数据对提升模型泛化能力边际效应最大。现在的固定流程是任何新数据进来先跑一遍三种格式的IoU交叉验证再按人物分组划分最后才进训练。这套习惯保住了我很多次大规模返工。希望帮到你。本文还有配套的精品资源点击获取
返回列表