资讯动态

跌倒检测实战:数据集处理、YOLO训练与状态机过滤

发布时间:2026/10/14 14:51:04 来源:尧图企业网站定制
简介面向人员状态检测任务的数据集覆盖跑、睡、打电话、跌倒、抽烟五类常见行为提供Pascal VOC与YOLO两种格式的矩形框标注可直接用于目标检测模型训练与算法验证适合算法工程师、科研人员及竞赛学习者使用。压缩包共两千个文件其中一千九百九十九个为XML标注文件另含一个使用说明TXT整体大小约二百三十二点八MB结构简单便于快速解压与配置。全部标注由labelImg完成累计框数五千七百九十个各类别分布明确XML文件记录矩形框坐标与类别TXT文件提供归一化格式便于按需转换同时约四分之三标注来自数据增强且不对训练结果精度作保证使用时需注意数据划分与模型评估的合理性可作为行为识别方向的实验数据基础。截至目前已有四百九十三人学习下载资源组织规范格式统一便于直接查阅与二次开发。1. 4943张覆盖5类人员状态的检测数据集跌倒这类难样本为什么值得自己训拿到这个压缩包先别急着解压就跑训练。4943张图片、5个类别听起来比那些几万张的公开数据集小一圈但它覆盖的是安防和视频监控里最头疼的一类任务人员状态检测跑、睡、抽烟、打电话、跌倒。这里面真正值钱的不是前面四类而是最后的“跌倒”——动作持续时间短、姿态变化大、经常被遮挡单帧检测模型在这类样本上容易翻车。这份数据集的体量适合用来做第一版模型验证也适合补进现有项目里做迁移学习。如果你是做工地安全巡检、养老看护、园区监控预警的开发者手里的通用检测模型又总在跌倒场景上漏检这份数据能帮你把问题从“模型不行”定位到“数据不够”还是“训练配置不对”。我下面按自己拿到这种数据集后的完整流程来讲先摸清包内结构再转格式训出模型最后把坑一个个填掉。2. 先摸清压缩包里有什么目录结构、标注格式与5类场景的识别特征2.1 解压后的第一件事确认标注格式别被文件名骗了.7z压缩包解压后的内部结构不同来源差别很大。我见过有的包把图片和标注平铺在根目录有的按images/和annotations/分好也有的干脆全塞进一个文件夹靠扩展名区分。所以第一步不是数图片而是看目录和标注。7z x 人员状态跑睡抽烟打电话跌倒检测数据集4943张5类别.7z tree -L 2 -dtree -L 2 -d只显示两层目录结构能快速看清有没有train/val划分。有些 Linux 发行版没装tree用find . -maxdepth 2 -type d替代也行。解压后我一般会再随机抽查三到五个标注文件确认到底是哪种格式纯文本.txt每行五个数字类别 x_center y_center width height这是 YOLO 系格式。XML 文件包含objectname和bndbox节点这是 VOC 格式。JSON 文件带images、annotations两个数组这是 COCO 格式。这个判断决定了后面转换脚本怎么写。别拿到.txt就以为是 YOLO 格式有的包把 VOC 的标注内容也存成.txt只是结构不同直接喂给训练脚本会报 shape 错误。2.2 五类标签的边界与视觉特征这份数据集的五个类别里我建议你先把“跑”和“跌倒”这两个类别的边界搞清楚。两者在单帧画面上高度相似身体前倾、四肢展开、运动模糊。落到实际标注里区别往往只有连续帧上下文才能判断。下表是我在训练前会自己整理一遍的类间混淆矩阵类别关键视觉特征最容易混淆的对象run身体前倾、步幅大、有跨步姿态、可能带运动模糊跌倒瞬间、走路快走sleep平躺或靠坐、闭眼、身体放松、长时间静止跌倒后静止、躺卧休息smoke手部持物靠近口部、烟体可见、烟雾缭绕打电话、手指遮挡call手机贴耳或举在脸侧、手部姿势固定抽烟、拿饮料fall倒地姿态、头肩位置低于正常站立线、躯干与地面接触跑动瞬间、躺卧睡觉这张表不是用来背的是用来检查样本质量的。如果某个类别的图片里混入了大量边界样例比如把弯腰捡东西标成跌倒把躺着玩手机标成睡觉训练时模型会学到错误的判别依据推理时自然会在这些场景上反复横跳。2.3 先做一次标注健康度检查在动手转格式之前先写个几行脚本统计标签分布和框的合法性。这一步能省下后面排错的大量时间。from collections import Counter import os label_dir labels counter Counter() bad_files [] for name in os.listdir(label_dir): if not name.endswith(.txt): continue path os.path.join(label_dir, name) with open(path, r, encodingutf-8) as f: for line in f: parts line.strip().split() if len(parts) ! 5: bad_files.append((name, field_count)) continue cls_id int(parts[0]) xc, yc, w, h map(float, parts[1:]) counter[cls_id] 1 if w 0 or h 0 or w 1 or h 1: bad_files.append((name, fbox_range:{w:.3f},{h:.3f})) print(类别分布:, sorted(counter.items())) print(异常文件示例:, bad_files[:10])这段脚本做两件事统计每个类别标签总数检查标注框坐标是否越界。类别分布能直接告诉你训练时要不要做重采样比如跌倒类只有 200 个框而其他类每类近千个训练时cls损失就会被大类别带走框的宽高比如果是极端值比如宽度为 0 或大于 1说明原始标注就有问题这类样本会在训练早期拉高损失甚至让模型不收敛。3. 把标注转成 YOLO 训练格式转换脚本与四个容易踩的边界坑3.1 先确认来源格式再决定批量转换脚本标题没有说明标注格式实际从网上下到这类压缩包最常见的是 VOC XML 或 COCO JSON。两种格式我都转过这里用 VOC 转 YOLO 做范例因为它的 XML 结构直白适合讲清楚坐标转换的每个细节。判断格式时不要只看扩展名。我习惯先打开一个文件看头部XML 文件一定以annotation开头JSON 文件则以{开头。确定是 VOC 后还要确认size节点里有没有图片宽高。有的包在导出时把 width 和 height 写成了 0这种样本转出来全是除零错误后面训练会莫名少图。3.2 把 VOC XML 转成 YOLO txt脚本与参数说明import glob import os import xml.etree.ElementTree as ET classes [run, sleep, smoke, call, fall] def convert_voc_to_yolo(xml_path, out_dir): tree ET.parse(xml_path) root tree.getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) if img_w 0 or img_h 0: print(f跳过 {xml_path}: 图片尺寸非法 {img_w}x{img_h}) return lines [] for obj in root.findall(object): name obj.find(name).text if name not in classes: print(f跳过未知类别 {name} 在 {xml_path}) continue cls_id classes.index(name) 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) x_center (xmin xmax) / 2 / img_w y_center (ymin ymax) / 2 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h if w 0 or h 0: print(f跳过空框 {xml_path}: {name}) continue lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) if not lines: return txt_name os.path.basename(xml_path).replace(.xml, .txt) with open(os.path.join(out_dir, txt_name), w) as f: f.write(\n.join(lines)) os.makedirs(yolo_labels, exist_okTrue) for xml_file in glob.glob(annotations/*.xml): convert_voc_to_yolo(xml_file, yolo_labels)这段脚本把 XML 里的xmin ymin xmax ymax转换成 YOLO 要求的中心点坐标和归一化宽高。核心逻辑是除以图片宽高把像素坐标压到 0~1 区间。坐标保留 6 位小数太低会在训练时把多个框的 anchor 匹配搞混。脚本里加了两个自我保护分支遇到图片尺寸为 0 就跳过遇到宽高为负或零的空框也跳过。这两个分支不是废话。我解压过的数据集里至少有两次碰到某些 XML 的xmax比xmin还小多半是标注工具导出时索引错位不拦截的话转换后会出现负数坐标。3.3 数据划分与类别映射别让 filename 和 label 打架转换完成后的下一步是划分训练集、验证集和测试集。这个步骤的常见错误是图片在images/标注在yolo_labels/文件名前缀对不上导致训练时一半图片没有对应标签模型只能学背景。import os import random import shutil img_dir images label_dir yolo_labels images [f for f in os.listdir(img_dir) if f.lower().endswith((.jpg, .jpeg, .png))] random.seed(42) random.shuffle(images) total len(images) train_imgs images[: int(total * 0.8)] val_imgs images[int(total * 0.8): int(total * 0.9)] test_imgs images[int(total * 0.9):] for split_name, split_imgs in [(train, train_imgs), (val, val_imgs), (test, test_imgs)]: os.makedirs(fdataset/images/{split_name}, exist_okTrue) os.makedirs(fdataset/labels/{split_name}, exist_okTrue) for img in split_imgs: base os.path.splitext(img)[0] src_img os.path.join(img_dir, img) src_label os.path.join(label_dir, base .txt) if not os.path.exists(src_label): print(f警告: 缺少标注 {src_label}) continue shutil.copy(src_img, fdataset/images/{split_name}/{img}) shutil.copy(src_label, fdataset/labels/{split_name}/{base}.txt)这里用random.seed(42)固定随机种子保证每次运行划分结果一致。测试如果某张图片没有对应.txt脚本会打印警告并跳过防止半张图半张标注的情况进入训练。另外一个容易忽略的点是类别映射顺序。classes列表一旦定下来转换和训练必须用同一份。比如fall排在第 4 位那标签文件里类别编号就是 4训练配置里的names也必须把fall放在下标 4 的位置。这个顺序错位是最隐蔽的模型能收敛mAP 也不低但推理结果里“跌倒”永远显示成“跑”。3.4 转换后的四类常见异常与排查顺序转换和划分过程中我遇到最多的四类异常按出现频率排序如下第一文件名前缀不一致。image_0001.jpg对应的标注可能是annot_0001.txt这类问题在划分脚本里加了存在性检查后会直接暴露遇到就统一改名。第二XML 里存在没有bndbox的对象。有的数据集把难例标为“ignore”类没有坐标框。转换脚本里对应的obj.find(bndbox)会返回None如果直接.find(xmin)会崩。稳妥做法是如果box is None就跳过该对象。第三同一张图有多个类别但个别框的类别名与 classes 列表不一致比如出现standing、unknown这类噪音标签。我的处理是打印出来人工看如果数量很少直接跳过如果占比超过 5%说明原始标注质量有问题需要退回。第四标注文件的编码问题。Windows 下导出的 XML 可能是 GBK 编码ET.parse会报 UnicodeDecodeError。解法是先用二进制方式读取并尝试 UTF-8 解码失败则用gbk解码后重新写一遍再解析。4. 训练可用的检测模型训练命令、超参设置与指标核对4.1 把数据集组织成训练目录结构训练框架对数据集目录的约定比较严格推荐组织成下面的结构这也是现在主流检测框架的通用约定dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ └── labels/ ├── train/ ├── val/ └── test/对应的数据配置文件写成 YAML。注意path用绝对路径相对路径在不同机器上执行时容易飘。path: /data/fall_dataset train: images/train val: images/val names: 0: run 1: sleep 2: smoke 3: call 4: fallnames列表里编号必须和标签文件里的第一个数字一一对应。这个配置文件的重点不在于字段多少而在于path指向的目录下必须同时存在images和labels两个平级目录且两个目录里的子目录名完全一致。有人把图片放在dataset/images/train、标签放在dataset/labels/train却把path配成dataset/images/训练直接报“Label not found”。4.2 训练命令与参数说明数据量只有 4943 张属于中小规模数据集不建议第一轮就上大模型。先用轻量模型把流程跑通确认 loss 能正常下降再换更大模型看收益。yolo detect train \ modelyolov8n.pt \ datafall_dataset.yaml \ epochs100 \ imgsz640 \ batch16 \ patience15 \ device0 \ seed42第一轮训练用yolov8n这个轻量级权重它的参数量小训练速度快适合用来验证数据链路是否正常。epochs100是起点如果训练到 80 轮验证集指标还在上涨就再加到 150如果 30 轮就过拟合说明数据量撑不住这个模型容量。batch16在 8G 显存的卡上比较稳显存足够的可以开到 32训练速度会快一些但小 batch 在类别不均衡时反而更能反映真实梯度。patience15表示连续 15 轮验证集指标没有提升就提前终止省时间。seed42固定随机种子方便复现和对比实验。4.3 五类样本不均衡时的调参顺序这个数据集的五个类别数量通常不会均匀分布。跑、睡这类静态场景容易大量采集跌倒这类瞬时动作往往数量偏少。如果统计后类别差异超过 3 倍我的调整顺序是先改数据增强再加 loss 权重最后才考虑复制样本。第一优先是打开训练框架内置的马赛克增强它会把四张图拼在一起训练相当于变相增加了跌倒样本的上下文多样性。其次可以调close_mosaic10意思是最后 10 轮关闭马赛克增强让模型回到真实自然图像上做微调。先不做类别复制因为复制样本会带来过拟合风险尤其跌倒这种姿态变化极大的类别同一个跌倒样本复制十遍模型记住的是同一套像素。如果增强已经调满跌倒类的验证集 mAP 还是明显低于其他类再考虑给分类损失加权重。常见做法是把cls损失系数从默认的 0.5 调到 0.7让模型对错误分类更敏感。但我不建议一上来就调这个先确认是深度特征学不好而不是数据本身不够。4.4 第一次训练完必看的几个指标训练结束后第一眼看验证集上每个类别的mAP50而不是只看总的mAP50。原因很直接跑、睡、抽烟、打电话数量多这四类指标会把总的 mAP 拉到很高跌倒类即便只有 0.3 的 mAP50总指标看起来还是不错。我的习惯是拉出混淆矩阵逐类看。重点看两处一是跌倒是否被误判成跑二是抽烟和打电话是否互相污染。这两组混淆在跌倒检测场景里最常出现。如果混淆矩阵显示跌倒和跑互串先不要调模型回去看第 2 章里讲过的标签边界把跌倒样本的框规范重新对齐比换模型管用。如果说“mAP50 高但实际视频里漏检”大概率是跌倒样本的框太小模型对目标的定位精度不够下一步就该检查第 5 章里的小目标问题。5. 避坑记录小目标跌倒、抽烟打电话混淆与漏检的排查5.1 现象跌倒检测框只框了半个人验证集里框不在目标中心训练结束后可视化验证集预测框发现跌倒的框经常只框住一条腿或者半个躯干框的置信度虽然不低但位置明显偏离。原因在于标注规范不统一。此数据集部分跌倒样本只框了上半身另一些框了全身导致模型学到的“跌倒”不是一个完整目标而是一堆不同尺度的局部特征。小目标本身像素少模型更容易被局部纹理带偏。解决方法是先统一标注框的规范再重新训练。我采用的规则是跌倒框必须包含头部和躯干紧贴外部矩形四肢可以切掉但头部不能出框。这样调整后跌倒框的宽高比会从极端值 1:4 收敛到 1:2 左右模型学到的定位目标更一致。5.2 现象抽烟和打电话的置信度一直在 0.4 上下晃动换上验证集会交叉误报抽烟和打电话在单张画面上最关键的区别其实是手持物的位置和形状。烟通常更靠嘴部中央手机贴在耳侧但遮挡和角度一变化单靠一帧根本分不清。这种混淆不能靠加大训练轮次解决。我用了两个手段组合第一从原始视频里多抽侧脸和背身样本补进这两个类别让模型见到更多“看不到手持物”的帧第二在推理阶段把置信度阈值从 0.25 提到 0.45并加上连续帧判定要求连续若干帧都是同一类别才真正输出结果。这个组合能把绝大多数单帧抖动滤掉但代价是响应速度会慢几百毫秒。5.3 现象连续视频流里跌倒和跑步的结果来回跳直觉上“跌倒”和“跑”姿态差异很大但在视频流里人从跑到摔倒的过程中中间帧的姿态介于两者之间。单帧检测器没有时序概念遇到中间帧时就会输出跳变。这个问题的根源在数据标注如果数据集里把倒地瞬间的帧全标成了跌倒把跑动起跳的帧标成了跑模型在边界帧上天然会撕裂。我的解决思路不是改数据而是改判定逻辑。在推理端维护一个长度为 15 帧的滑动窗口只有窗口内超过 8 帧输出“跌倒”才触发报警并且设置复位条件为连续低置信度帧超过 3 帧才回到正常状态。这个状态机的思路放在第 6 章详细展开这里先记住单帧结果只该用于展示不该用于告警。5.4 现象白天场景检测效果正常夜间或逆光场景一塌糊涂我遇到过训练时验证集 mAP 有 0.8部署到现场晚上基本全漏的情况。回查训练集发现夜间样本占比不到 5%模型几乎没有见过低照度下的纹理。解决分两步。第一步用图像增强做兜底在训练配置里增大亮度扰动和加高斯噪声让模型对光照变化更鲁棒。第二步才是根本解法尽量补充夜间抽帧数据用已有模型做预标注人工修正后并回数据集中重新训练。数据增强只能缩小差距补数据才能拉平。5.5 现象训练中 loss 在初期正常下降到第 20 轮以后突然出现 NaN或者停在某个值不动出现这种情况先别调学习率回去查标签文件。最容易藏雷的是某些标注文件里混入了负数坐标或者宽高为 0 的空框。这类文件不会导致训练直接中断但会让损失计算出一个巨大值反馈到骨干网络后权重更新异常。import os bad_list [] for root, _, files in os.walk(dataset/labels): for f in files: if not f.endswith(.txt): continue path os.path.join(root, f) with open(path, r) as fp: for line in fp: parts line.strip().split() if len(parts) ! 5: bad_list.append((f, fields)) break w, h float(parts[3]), float(parts[4]) if w 0 or h 0 or w 1 or h 1: bad_list.append((f, range)) break print(f发现问题文件数: {len(bad_list)}) print(bad_list[:20])这段脚本是最后的保险在每次开始训练前跑一遍。范围检查包含宽高在 0~1 之间这个条件同时排除了未归一化的标注和越界的畸形框。实际踩坑中有次数据处理脚本把宽高写反了所有类别训练到第 30 轮后 mAP 都不超过 0.2查了两天才定位到是标注的w和h互换了。所以如果你也遇到“loss 一直在降精度上不去”优先怀疑标注格式而不是模型结构。6. 从“检出”到“判定”用连续帧过滤把跌倒报警做得更稳最后一章不聊训练聊一个让这个数据集真正可用的小技巧。当你把模型训练到验证集 mAP 不错之后直接拿单帧结果去触发业务报警会发现误报率让人崩溃。原因前面说过跌倒和跑的边界帧、抽烟和打电话的混淆帧单帧置信度都会抖动。我在实际项目里不会直接消费模型的单帧输出而是用一个状态机做时间维度的过滤。class FallStateMachine: def __init__(self, conf_thr0.45, hit_need8, clear_after3, window15): self.conf_thr conf_thr self.hit_need hit_need self.clear_after clear_after self.window window self.scores [] self.alerted False def update(self, detections): fall_conf max( [d.conf for d in detections if d.cls 4], default0.0 ) self.scores.append(fall_conf) if len(self.scores) self.window: self.scores.pop(0) hits sum(1 for s in self.scores if s self.conf_thr) if hits self.hit_need and not self.alerted: self.alerted True return alert low_count sum(1 for s in self.scores if s self.conf_thr) if low_count self.clear_after and self.alerted: self.alerted False return normal这个状态机的核心是滞后比较触发条件要求窗口内超过一半帧数是低置信度报警后才复位。它比单纯的“连续 N 帧超过阈值”多了一个好处——跌倒持续时间一般超过半秒若以 15 帧窗口、8 帧命中作为触发条件既不会因为单帧抖动乱报也不会因为跌倒过程中某几帧模型失手而漏报。hit_need8和clear_after3是两个要一起调的核心参数触发帧数设太短则误报高设太长则真正跌倒后报警延迟明显。验证这套逻辑时我习惯把测试视频逐帧喂进模型再跑状态机统计“触发时间点”与人工标注的倒地时间点的差距。这是我保留的习惯每训完一版模型逻辑上总觉得指标不错但一定先拿一段真实视频流回放而不是只盯着验证集里的 mAP 数字做决策。这份数据集加上这个判定逻辑已经能支撑起一个可演示、可评估的跌倒检测原型。希望帮到你。本文还有配套的精品资源点击获取

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

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

免费获取报价 →
↑