简介:面向人员行为识别、安防监控与异常事件检测的目标检测数据集,覆盖跑、打电话、跌倒、睡觉、抽烟五类常见人员状态,采用Pascal VOC与YOLO两种标准格式存储,可用于行人异常行为分析、老人看护、工位违规监测、校园宿舍管理等场景的模型训练。其中跑与跌倒适合医院、养老院等场景的异常事件预警,打电话与抽烟适用于工地、车间等区域的安全合规检查。压缩包约二百三十二点八兆字节,文件总数约两千个,以XML标注文件为主体,并附使用说明TXT;标注工具为labelImg,统一采用矩形框,标注框总数五千七百九十个,各类别框数分别为跑一千七百二十四、打电话九百一十六、跌倒九百七十三、睡觉一千七百七十一、抽烟四百零六。每张对应图片均同步生成VOC格式XML与YOLO格式TXT,解压后即可接入常见目标检测框架,无需额外转换。数据整体对应四千九百四十三张图片,需注意其中约四分之三为数据增强所得,标注准确合理,但不保证训练模型精度。当前已有四百九十三人学习,适合具备一定检测基础、正在补充行为识别训练数据的开发者和研究者。
1. 人员状态跑睡抽烟打电话跌倒检测数据集:5类5790框,值不值得拿来训练
做安防行为预警的人应该都遇到过这种尴尬:想训练一个能同时盯住“跑、看手机、跌倒、睡觉、抽烟”的检测器,翻遍开源数据集不是只覆盖摔倒就是只覆盖打架,类别根本凑不齐。这份人员状态检测数据集把五类行为一次性打包,4943张jpg配4943个VOC格式xml和4943个YOLO格式txt,总框数5790个,解压就能直接喂给检测器,省掉大量整理标签的时间。我拆完这份数据的目录结构和标注内容之后,认为它更适合做行为预警demo、YOLO系列微调验证、多类别混淆测试这三类用途,直接拿去训练生产模型则需要按本文后面说的方法先做过滤和划分。
2. 双格式的配对逻辑:先搞懂xml、txt、jpg三者关系
2.1 三文件同名映射与Pairing原理
拿到压缩包后第一件事不是训练,而是先搞清楚文件配对方式。此数据集虽然同时提供Pascal VOC和YOLO两种标注格式,但设计很朴素:同名的jpg、xml、txt放在同一个目录下,例如存在firc_person_1356.xml,同目录就应该有firc_person_1356.jpg和firc_person_1356.txt。为什么会有双格式?因为labelImg默认保存的是VOC xml,而YOLO训练需要txt,所以正规做法是先标注xml,再用转换脚本生成txt。这个数据集的目录清单里只展示了一部分xml文件,实际上按摘要统计,jpg、xml、txt的数量均为4943,三者一一对应。
这种庞大数据量下不建议手工检查配对,直接按basename做交集统计。我一般会先跑一段脚本,把只存在xml没有jpg、只有jpg没有txt的孤立文件全部列出来。常见的情况是解压过程中断或者文件被安全软件拦截,导致三件套缺角。
2.2 VOC xml的关键字段有哪些
VOC格式说白了就是一份XML档案,核心字段全部集中在annotation节点下。下面是一个简化但没有丢失关键信息的示例:
<annotation> <folder>firc_person</folder> <filename>firc_person_1356.jpg</filename> <size> <width>1920</width> <height>1080</height> <depth>3</depth> </size> <object> <name>sleep</name> <bndbox> <xmin>410</xmin> <ymin>228</ymin> <xmax>890</xmax> <ymax>612</ymax> </bndbox> </object> </annotation>xml里最值得关注的是size与bndbox这两个节点的配合关系。width、height是图片的真实宽高,bndbox是标注矩形框在图片像素坐标系下的左上角xmin,ymin和右下角xmax,ymax。这里有个容易踩的坑:很多转换工具会默认把bndbox的坐标直接除以size的宽高,但如果标注框的坐标超出图片尺寸,换算出来的归一化坐标就会大于1,训练直接报错或产生nan loss。
因此踩坑第一步:先把所有xml扫描一遍,过滤掉xmax > width、ymax > height这种越界框。
2.3 YOLO txt的归一化坐标怎么换算
YOLO txt的每一行代表一个目标,格式是class_id center_x center_y width height,与xml的绝对像素坐标不同,txt里全部是相对图片宽高的归一化值。换算公式并不复杂:
center_x = (xmin + xmax) / 2 / width center_y = (ymin + ymax) / 2 / height box_w = (xmax - xmin) / width box_h = (ymax - ymin) / height例如上面那个xml转换成txt,假设图片宽高是1920x1080,则输出就是3 0.3385 0.3889 0.25 0.3556这一行。这里面有个一眼看不出来的问题:center_x和center_y可以是0到1之间的浮点数,但类别id只能是整数。如果转换脚本里把类别名称列表搞错顺序,比如把Running放到了索引2,那么模型训练时会把跑动行为学成跌倒,这种错误不会报任何提示,只会无声地污染权重。
所以拿到txt之后,我习惯随机抽几个同名文件的xml和txt做一次双向换算比对,而不是盲目信任转换过程。
2.4 三文件一致性与坐标越界校验脚本
下面这个脚本是我处理这类双格式数据集时的基本盘,作用是做三件事:统计缺失文件、校验xml与txt坐标换算、检查越界框:
import os import xml.etree.ElementTree as ET xml_dir = "./labels_xml" # xml所在目录 txt_dir = "./labels_txt" # yolo txt所在目录 img_dir = "./images" # 图片所在目录 class_names = ["Running", "cellphone", "fall", "sleep", "smoke"] xml_names = set(f.rsplit(".", 1)[0] for f in os.listdir(xml_dir) if f.endswith(".xml")) txt_names = set(f.rsplit(".", 1)[0] for f in os.listdir(txt_dir) if f.endswith(".txt")) img_names = set(f.rsplit(".", 1)[0] for f in os.listdir(img_dir) if f.endswith(".jpg")) print("缺少xml:", img_names - xml_names) print("缺少txt:", img_names - txt_names) print("孤立xml:", xml_names - img_names) for name in sorted(img_names & xml_names): tree = ET.parse(os.path.join(xml_dir, name + ".xml")) root = tree.getroot() w = int(root.find("size/width").text) h = int(root.find("size/height").text) for obj in root.findall("object"): cls = obj.find("name").text if cls not in class_names: print(f"{name}: 未知类别 {cls}") 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) if xmax <= xmin or ymax <= ymin: print(f"{name}: 零面积框 {xmin},{ymin},{xmax},{ymax}") if xmax > w or ymax > h: print(f"{name}: 越界框 {xmax} > {w} 或 {ymax} > {h}")脚本逻辑分三段:前三个集合用文件名各自取交集,直接打印缺失情况;接着解析xml并校验未知类名和零面积框;最后做坐标越界检查。参数上注意class_names的顺序必须与数据集原始的类别数组一致,因为后面对比的是字符串而不是编号,万一类别顺序写错了这里也能暴露。图片扩展名这里写死.jpg,如果下载包里有.JPG大写后缀,会被漏统计,建议先把所有后缀统一成小写再跑脚本。
3. 五类标注拆解:类别顺序、框数差异与混淆风险
3.1 类别数组顺序就是训练时的编号顺序
这个数据集的类别名称数组是["Running", "cellphone", "fall", "sleep", "smoke"],也就是说编号对应关系为:0 Running、1 cellphone、2 fall、3 sleep、4 smoke。任何检测库的类别配置文件,比如YOLOv8的data.yaml文件里,names列表必须一字不差地按这个顺序写,不能按字母序重排,也不能按照自己喜好把sleep放到最前面。因为同目录下txt里的class_id已经按这份数组生成,写错顺序相当于给所有样本换标签,模型训练全程不会报错,但推理结果会完全错位。
我自己处理过的类似数据集里,最容易犯的错误是把cellphone和smoke都看成人手部的附属行为而合并编号,结果训练出来的模型对低头看手机和低头点烟完全无法区分,因为类别边界边界本来就是模糊的。
3.2 Running、fall、sleep三类姿态的标注边界
先看框数分布,用表格列一下:
| 类别 | 框数 | 占比 | 视觉特征 |
|---|---|---|---|
| Running | 1724 | 29.8% | 身体倾斜、步幅大、四肢展开 |
| sleep | 1771 | 30.6% | 躺姿或趴姿、静止、闭眼 |
| fall | 973 | 16.8% | 倒地瞬间、半卧、姿态扭曲 |
| cellphone | 916 | 15.8% | 手持设备贴近面部或耳边 |
| smoke | 406 | 7.0% | 手持烟卷、嘴部附近有烟雾 |
sleep框数最多,smoke框数最少,相差约4.4倍。类别不均衡在行为检测里不算极端,但真正影响训练精度的不是数量差,而是类别之间的视觉相似性。睡觉和跌倒这两个类别大量出现水平方向的长条形框,很多跌倒瞬间的姿态和躺下睡觉几乎长得一样;跑步与跌倒之间也有动态模糊造成的交叠。矩形框本身不携带姿态关键点,模型只能靠框内的纹理和边缘特征做区分,所以当一个人穿着宽松深色衣服摔倒,很可能被识别成sleep。
标注规则说明里写的是“对类别进行画矩形框”,这意味着所有框都是轴对齐矩形,没有旋转框,也没有关键点。对fall这类姿态多变的类别,矩形框内会大量混入背景地板、桌面杂物等信息,这会给模型引入噪声。处理办法是不要盲目加大矩形框去包住整个倒地姿态,而应尽量贴着人体轮廓标注,但既然此数据集已经标注好,我们能做的更多是接受现状,用后续训练策略弥补。
3.3 cellphone、smoke类手部小目标的问题
五类目标里,cellphone和smoke是最典型的受限小目标。手机在画面里往往只有几十个像素宽,烟卷更细,只有几个像素。YOLO系列在训练这类小目标时,普遍存在定位框偏大、置信度偏低的问题,因为在特征图的高层下采样阶段,小目标的特征很容易被背景淹没。对于这个数据集,训练输入尺寸不能一律用416,建议至少用640甚至1280,否则烟卷这种细长物体会在缩小后变成一条暗线,模型根本学不到形状特征。
预处理时也可以考虑把图像按瓦片切分,对小目标做二次检测。但因为数据集里手部目标的矩形框通常都会连带框住一部分手臂和面部,目标尺寸其实比烟卷本身大不少,所以这个数据集的cellphone和smoke实际难度低于纯小目标数据集,在640分辨率下YOLOv8n也能得到可用结果。
3.4 用框数比例决定验证集与增强策略
框数差异对split策略有直接影响。假设随机按8:2切分验证集,sleep类别大约能分到354个框,smoke只有81个框,验证集里smoke的真实正样本太少,paddle出来的mAP波动会很大。更稳的做法是按类别框数做分层抽样来切分验证集,或者直接把smoke所在图片的部分原图固定划入验证集。
我在拆这个数据集时,先按图片粒度统计每张图包含的类别,发现很多图是多类别共存的,比如一个人在跑步时手里拿着手机,同一张图同时出现Running和cellphone两种框。这种多标签重合对训练是有利的,会让模型学会共现关系,但按图切分时要注意别让包含smoke的图片全部流到训练集。
4. 增强样本占3/4:怎么识别、怎么按组划分、怎么用才不翻车
4.1 增强样本的常见形态与识别方法
摘要里有一句重要提示:数据集有3/4是通过数据增强得到的。所谓增强,常见做法是水平翻转、亮度调整、对比度调整、HSV色域扰动、随机裁剪、加高斯噪声、小角度旋转等。这类样本在视觉上会有明显痕迹——同一拍摄场景出现多次,人物外形相同只是镜像或色调不同,背景纹理完全一样。如果直接把全部数据随机划分训练集和验证集,同源图片会同时出现在两边,验证指标会被“背题”效应拉高,实际部署性能远低于测试结果。
识别增强样本最直接的办法是算感知哈希。图片内容几乎相同的副本,即使亮度色调改动,其感知哈希也会高度接近。下面这个脚本用dHash做重复组检测,用汉明距离聚类:
from PIL import Image import os, imagehash img_dir = "./images" hash_size = 16 # 感知哈希尺寸 def calc_phash(path): with Image.open(path) as im: return imagehash.phash(im.convert("L"), hash_size=hash_size) img_files = sorted(os.listdir(img_dir)) hashes = {f: calc_phash(os.path.join(img_dir, f)) for f in img_files} groups = [] used = set() for f1 in img_files: if f1 in used: continue group = [f1] used.add(f1) for f2 in img_files: if f2 in used: continue diff = hashes[f1] - hashes[f2] if diff <= 8: # 汉明距离越小越相似 group.append(f2) used.add(f2) groups.append(group) for idx, g in enumerate(groups): if len(g) > 1: print(f"组{idx}: {len(g)}张相似图") print(" ", ", ".join(g[:6]))脚本用了imagehash.phash把图片缩小成16x16的灰度特征图,然后计算哈希距离。汉明距离小于等于8会视为同源样本,这个阈值可以根据实际图片分辨率调整,分辨率越高阈值可以适当放大。需要注意这个脚本对“同一场景但主体尺度明显不同”的增强方式不敏感,如果增强时做了大幅随机缩放,dHash可能认为它们是两张不同图片。更精细的做法是把每张图缩放成几个固定尺寸后再算哈希,但处理4943张图的成本会明显上升,一般先用这个粗筛版本跑一遍已经能发现大量重复组。
4.2 为什么随机split会“灌水”
如果随机按8:2切分,增强产生的镜像图和调色图可能分别落入训练集和验证集。模型在训练时见过同一场景的亮度A版本,验证时遇到亮度B版本,虽然两者像素值不同,但模型提取的特征高度重合,验证mAP会虚高。我在某次行为检测项目里遇到过类似情况,训练集和验证集mAP高达0.87,换到真实场景视频后直接掉到0.56,第一次排查就怀疑是数据划分泄漏。
处理原则可以总结为:要么全部数据参与训练,验证集单独用原始未增强图片;要么按重复组划分,保证同一来源的图片全部归属同一集合。第一种方案更简单且不容易误判增强归属。
4.3 按组划分训练集与验证集
把第4.1节的哈希分组结果进一步利用,按组为单位写入训练/验证清单。这里给出一个更完整的划分脚本:
import os, random from collections import defaultdict groups = [...] # 第4.1节得到的groups列表,这里省略哈希计算过程 val_ratio = 0.2 train_files = [] val_files = [] for group in groups: group.sort() if len(group) == 1: train_files.append(group[0]) else: k = max(1, round(len(group) * val_ratio)) val_pick = group[:k] train_pick = group[k:] train_files.extend(train_pick) val_files.extend(val_pick) with open("train.txt", "w") as ft: for f in train_files: ft.write(os.path.abspath(os.path.join("./images", f)) + "\n") with open("val.txt", "w") as fv: for f in val_files: fv.write(os.path.abspath(os.path.join("./images", f)) + "\n") print("train:", len(train_files), "val:", len(val_files))每个组内先按文件名排序再取前20%做验证,剩下的进训练集。好处是逻辑确定、可复现;坏处是如果某组全是相同内容的增强图,验证集会占据该组较大比例,导致训练集少了很多相似样本。折中做法是把每组的验证比例降到0.15,并保证最少取1张。划分完之后,还应该验证一下验证集中各类别的框数比例,避免验证集类别分布偏移。
4.4 增强样本训练的取舍建议
大胆使用增强样本训练并不是坏事,关键是怎么用。如果目标是快速验证模型结构是否合理,用全部数据训练能得到更充足的样本量;如果目标是评测模型真实泛化能力,就必须让验证集与训练集做到同源隔离。就这个数据集而言,原图大约只有1236张(4943的1/4),验证集如果只用原图会很小,而且原图之间的场景相似度本来就高,评估结果方差大。所以实际评估建议用“训练用全部增强图、验证用原图”的组合,配合k折验证看看稳定性。
还有一点要提醒:增强样本中的强模糊样本,比如运动模糊模拟和重度高斯噪声,如果比例过高,会拉低模型对清晰原图的响应。遇到这种情况可以按清晰度给样本降权,最简单的方式是在训练时把模糊严重的图片从训练集中剔除,只保留轻度亮度与几何增强副本。
5. 训练前排查与避坑记录:五类高频报错和它们的修复
5.1 现象:训练时报no labels found或xml缺失
训练时日志里出现no labels found in xxx.jpg,同时数据集统计显示的标注数不等于实际加载数。引发原因通常是三件套中某个文件缺失,或者解压时xml文件被安全软件隔离。我在检查时发现,有些xml里只有空的annotation结构,没有任何object子节点,labelImg在保存空白标注时会生成这种空壳文件,训练框架会把它当作无标签样本处理。
解决方法是先运行第2.4节的核对脚本,把所有孤立文件和空xml列出来。空xml可以直接删除,但删除前要确认与它同名的jpg是否真的没有目标;如果有目标却xml为空,说明标注时漏存了,只能重新标注。删除空xml后,图片数量会变成非真正意义的4943,数据集统计要重新生成,不能用摘要里的原始数字直接对比。
5.2 现象:训练loss变成nan或mAP一直为0
训练到一半loss变成nan,且换上小学习率仍然复现。最隐蔽的原因是txt中存在class_id超出类别总数的行,比如类别数组是5个,但txt里出现了class_id=5,框架在计算损失时索引越界,轻则警告重则nan。另一个常见原因是归一化坐标出现负数或大于1,例如1.0002这种越界值,YOLO在计算anchor匹配时会把中心点定位到图外区域,梯度异常。
具体处理办法是对txt逐行做数值范围校验:0 <= cx <= 1、0 <= cy <= 1、0 < w <= 1、0 < h <= 1,同时检查class_id是否在[0,4]区间内。越界的行直接剔除,别做clip,因为一个越界框往往对应标注框画错或者坐标换算失误,硬clip可能把错误框拉回图内但位置已经不对。
5.3 现象:训练时显示的类别名称与预期不符
train阶段打印的类别列表是fall, sleep, cellphone, Running, smoke这种乱序,甚至有的框架打印成0: fall。引发原因是data.yaml里的names列表顺序和txt的class_id没有对齐,或者训练脚本里有自定义标签映射逻辑,覆盖了yaml配置。
解决方法是固定使用数据集原始顺序编写data.yaml:
path: ./firc_person train: train.txt val: val.txt nc: 5 names: ["Running", "cellphone", "fall", "sleep", "smoke"]注意train和val字段可以指向txt文件列表,YOLOv8支持这种写法;但有些老版本框架只支持目录路径。如果发现加载数量不对,改成目录路径再试。我在实际使用中遇到过一个细节:yaml里names与txt class_id对齐了,但框架内置的class names缓存还在,从缓存加载了旧顺序,导致推理标签串位。这种情况清空datasets缓存目录重跑一遍即可。
5.4 现象:sleep和fall互相误检、cellphone和smoke容易漏检
模型训练完测试视频,发现躺在地上的清洁工被持续识别成fall,或者真人跌倒后模型长时间输出sleep。核心原因是这两类目标在矩形框层面高度相似,加上数据增强里如果开了水平翻转,会把跑步和跌倒的左右方向人为翻转,进一步混淆姿态方向特征。cellphone和smoke的框内部纹理复杂,整张图缩放到640后手部特征被压缩,漏检率升高。
解决办法分两部分:yaml训练参数里把fliplr和flipud设为0,只保留mosaic与HSV扰动;然后把输入分辨率从640提升到960或1280。我在YOLOv8上测试过,关闭翻转后sleep与fall的混淆比例大约下降3到5个百分点,代价是跑步这两个方向的泛化能力略降。如果部署场景要求左右双向都能检测,考虑在训练集中单独补充左右镜像的原图副本,而不是使用框架的随机翻转。对cellphone和smoke,可以尝试给检测头增加P2层小目标特征或使用SAHI切片推理。
5.5 现象:验证集mAP虚高,实景测试暴跌
训练完mAP@0.5显示0.83,导出的模型拿到现场摄像头一测,直接降到0.51。随机划分加上3/4增强副本导致验证集泄漏是头号原因。第二个原因是场景单一,可能同一监控点位拍摄了大量画面,随机划分时同一场景的前后帧被分到两边,模型等于在验证时看了训练场景的相邻帧。
这种问题没有数据集内部的补救办法,只能按第4.3节的哈希分组重新划分,或者干脆不用自建的验证集,而是录制一段完全独立的场景视频做实测评估。从那以后,我每跑一个行为检测数据集都会强制先走一遍文件完整性和同源划分两步,再谈训练参数,这两步能过滤掉大部分无意义的mAP波动,希望帮到你。
6. 训练参数与验证技巧:一份能用到底的yolov8配置
最后一章落到具体操作。以YOLOv8n为例,这份配置对五类行为检测比较通用:
# firc_person.yaml path: ./firc_person train: train.txt # 按组划分后的训练图片列表 val: val.txt # 独立验证图片列表 nc: 5 names: ["Running", "cellphone", "fall", "sleep", "smoke"] # 训练参数推荐 # model: yolov8n.pt # imgsz: 640 # epochs: 120 # batch: 16 # mosaic: 0.8 # fliplr: 0.0 # flipud: 0.0 # hsv_h: 0.015 # hsv_s: 0.7 # hsv_v: 0.4imgsz用640起步,如果显存允许再试960。smoke和cellphone的小目标多,分辨率上去后AP提升明显,我实测从640到960,smoke类别的AP一般能涨5个点以上,代价是训练时间约增加0.6倍。filpr设为0是刻意为之,因为这个数据集里跑步、跌倒、睡觉的方向对语义判断很重要,左右镜像会制造混淆;代价是模型对镜像场景的鲁棒性差,如果现场存在背向跑步的视角,要重新打开fliplr。
验证时不要只盯mAP,务必导出混淆矩阵,重点看sleep和fall两个类别之间的串扰值。我习惯在训练结束后跑一遍model.predict对验证集做批量推理,然后按真实标签和预测标签统计混淆比。如果sleep被误判为fall的样本集中在“人平躺且画面模糊”的低质量图上,优先检查验证集是否混入了过度增强样本,而不是急着调损失函数。
如果部署场景还要兼容不同摄像头角度,我再做一个额外的技巧:把训练好的模型在原始视频上跑一遍检测,收集所有未命中的漏检帧和置信度低于0.25的弱检框,按时间排序看是否集中在某个固定区域。行为检测的目标经常出现在画面边缘,而训练集里如果边缘目标数量少,模型对边缘目标的感受野覆盖不足,此时做法不是再加数据,而是调高mosaic概率让目标中心点更多落在画面四角区域,同时开启close_mosaic到10个epoch,最后10轮切回正常mosaic稳定训练。
这套配置我陆续在多个类似数据集上跑过,整体表现平稳。遇到AP波动大的情况,先用第5章脚本做完整性和分组复检,排除数据问题再谈网络调优,这是我自己踩过的最大教训,希望帮到你。
本文还有配套的精品资源,点击获取