简介这是一份面向目标检测学习与开发者的行人跌倒检测数据集采用YOLOv5标准目录格式包含person、fall等5个类别图像统一为800×800的RGB图片可直接用于模型训练和验证无需额外转换或格式调整。压缩包内共2000个文件主要由1999个txt标注/类别说明文件和1个Python可视化脚本组成脚本无需修改即可运行能随机读取一张图片并绘制边界框将结果保存在当前目录非常适合快速检查标注质量资源整体大小约438.48MB。数据已明确划分为训练集和验证集训练集含7609张图片及对应标签验证集含1902张图片及对应标签目录结构清晰。无论是进行跌倒检测算法验证、模型调优还是用于毕业设计或实际工程项目都可以直接在此基础上开展。目前已有404人学习浏览标注规范、即下即用是这套数据的主要特点。1. 行人跌倒检测数据集在YOLOV5目录格式下到底能做什么做监控场景的行人跌倒检测最磨人的往往不是模型结构而是数据集本身。直接按“跌倒/正常”二分类去标注模型学不到姿态变化的过程落地时误报能把人逼疯。标题里这个目标检测数据集走的是更稳的路子5个类别全按行人姿态划分目录、标签、划分方式全部对齐YOLOV5目录格式拿来就能跑yolov5训练自己的数据集。这篇笔记把目录结构怎么搭、5个类别怎么定、标注和训练前需要校验的细节拆开讲适合正在做跌倒检测的工程师也适合第一次用yolov5组织数据集的初学者。下面按目录搭建、类别定义、训练前校验、踩坑记录和效果验证的顺序过一遍。2. 搭建YOLOV5目录格式目录结构、划分脚本与标签文件检查YOLOV5默认的数据集加载逻辑很固定train.py 通过 data.yaml 里的路径去找 images 和 labels两者必须同级train/val 目录名要对应。很多新手把标签文件直接塞到一个文件夹里训练时报 “No labels found”就是因为目录结构不符合约定。这一章先把目录格式立起来再给一个可以直接用的划分脚本。2.1 目录结构与命名规范为什么 images 和 labels 必须同级标准的目标检测数据集目录长这样images 和 labels 是两个平级根目录各自下面再按 train/val/test 分子目录。图片和标签文件必须同名只是后缀不同图片是 .jpg标签是 .txt。YOLOV5 找标签的逻辑就是拿图片文件名去 labels 目录里找同名 txt找不到就跳过这张图。fall_detect_dataset/ ├── images/ │ ├── train/ │ │ ├── cam01_0001.jpg │ │ └── cam01_0002.jpg │ ├── val/ │ │ └── cam02_0100.jpg │ └── test/ │ └── cam03_0200.jpg └── labels/ ├── train/ │ ├── cam01_0001.txt │ └── cam01_0002.txt ├── val/ │ └── cam02_0100.txt └── test/ └── cam03_0200.txttrain/val/test 的比例我一般按 8:1:1 切val 不能太小否则最后 best.pt 选得不稳val 损失曲线波动大。test 集不是必须的如果视频素材紧张可以把 test 砍掉只留 train 和 val。有一点要注意不要在一个目录里同时放不同来源的图片跌倒检测场景大多是监控视频抽帧如果多路摄像头帧号都是 0001 开始文件名直接冲突后拷贝的会把先拷贝的覆盖掉。命名时带上摄像头编号或视频片段编号能省掉后面很多检查时间。2.2 划分脚本把图片和标签按比例自动切到 train 与 val下面是按 8:1:1 划分的脚本基于“图片和标签同名配对”的组织方式。脚本逻辑是先扫描原始目录里所有图片检查同名 txt 是否存在只保留配对成功的样本打乱后按比例切片最后把文件复制到目标目录。复制而不是移动万一划分有问题原始数据还在这是后悔药。import os import random import shutil src_img_dir ./raw_images # 未整理的图片目录 src_lab_dir ./raw_labels # 未整理的标签目录 dst_dir ./fall_detect_dataset # 目标数据集根目录 ratio_train, ratio_val 0.8, 0.1 # 剩余 0.1 自动归到 test # 只保留图片与标签都存在的样本防止空标签混入 samples [] for fname in os.listdir(src_img_dir): stem os.path.splitext(fname)[0] txt stem .txt if os.path.isfile(os.path.join(src_lab_dir, txt)): samples.append(stem) samples.sort() random.seed(42) # 固定随机种子保证多次运行划分结果一致 random.shuffle(samples) n len(samples) pos_train int(n * ratio_train) pos_val int(n * (ratio_train ratio_val)) sets { train: samples[:pos_train], val: samples[pos_train:pos_val], test: samples[pos_val:], } for split in sets: img_out os.path.join(dst_dir, images, split) lab_out os.path.join(dst_dir, labels, split) os.makedirs(img_out, exist_okTrue) os.makedirs(lab_out, exist_okTrue) for stem in sets[split]: shutil.copy2(os.path.join(src_img_dir, stem .jpg), img_out) shutil.copy2(os.path.join(src_lab_dir, stem .txt), lab_out) print(train/val/test 样本数:, *[len(v) for v in sets.values()])关键参数有两处。random.seed(42) 保证重新跑脚本时划分结果完全一致方便复现实验ratio_train 和 ratio_val 是划分比例如果后续发现 val 里某个类别样本太少可以调成 0.9/0.05 重新切。脚本里扩展名写死为 .jpg如果你的原始素材是 .png 或 .bmp把 stem .jpg 改成实际扩展名。另外这个脚本按单个样本随机划分适合没有时间连续性的图片集如果素材是同一段视频的连续帧见第 5 章的避坑记录需要按视频片段划分否则验证指标会虚高。2.3 标签文件格式与类别统计先确认每行到底写了什么YOLO 标签 txt 每行是一个目标格式是类别ID 归一化中心x 归一化中心y 归一化宽度 归一化高度。所有坐标都是相对于图片宽高的比值范围在 0 到 1 之间。例如一张 1920x1080 的图里有一个行人框左上角在 (960, 540)宽 200高 600对应 txt 里大概是4 0.5333 0.7000 0.1042 0.5556。类别ID 从 0 开始对应 data.yaml 里 names 的顺序。# 统计 train 标签里每个类别各有多少框 for c in 0 1 2 3 4; do echo class $c: cat labels/train/*.txt | awk -v c$c $1c {n} END {print n} done这条命令会遍历 labels/train 下的所有 txt用 awk 按每行的第一个字段类别ID计数。如果某个类别的统计数明显偏少比如 lying 只有十几框后面第 5 章会讲到怎么处理这种类别不平衡。统计这一步建议在划分完目录后立刻做一次确认标签没有因为脚本或手动拷贝而丢失。2.4 data.yaml 配置YOLOV5 与 YOLOV8 都认这套结构有了目录结构和标签文件接下来要写 data.yaml 告诉训练脚本去哪读数据、分几类。YOLOV5 和 YOLOV8 的 data.yaml 字段基本一致path 指向数据集根目录train/val 写相对路径。注意 YOLOV5 早期版本里 train/val 写的是相对 images 目录的路径现在统一写法是相对 path 的路径按下面的写就行。这个数据集目录格式做好后即使以后切到 yolov8 训练自己的数据集也只是换训练脚本数据侧不用重做。path: /data/fall_detect_dataset # 数据集根目录的绝对路径 train: images/train val: images/val nc: 5 names: 0: standing 1: walking 2: sitting 3: bending 4: lyingnc 必须和 names 列表长度一致names 的顺序就是标签文件里类别ID 的含义。这块最容易在后续改类别时出错如果你决定加一个类别比如把 bending 拆成 bending 和 squatting不仅 nc 要改成 6所有标签文件里的 ID 也要重新映射否则模型训练时读到的类别含义和标注时完全错位。改类别映射的脚本在第 3 章会给出。3. 5个类别的定义逻辑与标注工具落地从 class_id 到框选规则标题明确写了 5 个类别这是这个数据集区别于普通二分类跌倒数据集的根本。为什么不是直接标“跌倒”和“正常”因为“跌倒”是一个瞬间动作单帧静态图上很难一致地标注“正在跌倒”不同标注员会给出完全不同的框。5 个行姿态类别把问题转成更容易对齐的静态分类模型学到的是姿态本身跌倒事件靠后续帧序列逻辑判断。3.1 为什么是5个类别而不是“跌倒/正常”二分类监控场景里真正能用的是“事后判断”一个人从站立或行走状态快速变成躺倒这才是跌倒事件。如果数据集只标“跌倒/正常”模型会无所适从因为跌倒瞬间的中间帧看起来既像弯腰又像半蹲标注员自己都吵起来。5 个类别的价值在于把连续的姿态变化离散化成稳定可标注的状态模型只需要学会区分“站、走、坐、弯腰、躺”跌倒事件交给后端逻辑判断状态从 1 秒内的 walking/standing 跳变到 lying就触发报警。这个设计也直接影响标注效率。二分类的跌倒框在跌倒瞬间的边界极难画而 5 分类里 lying 的框非常直观就是贴着躺倒的人体画矩形。bending 类则吸收了“正在下蹲”“弯腰捡东西”“从椅子上站起一半”这些模糊状态让模型不必做精确的动作边界判断。3.2 5个类别的定义与 class_id 映射表类别定义要写成标注员能看懂、不会产生歧义的口径。下面是我在跌倒检测项目里实际用的映射表直接写进标注工具的 classes.txt顺序不能乱。class_id类别名姿态定义检测到后的处置建议0standing直立或近似直立重心在双脚不触发报警1walking双腿交替移动有位移趋势不触发报警2sitting臀部接触椅面/地面躯干基本竖直低优先级3bending躯干前倾超过45度或膝盖明显弯曲下蹲中优先级进入观察4lying躯干水平或接近水平倒地/躺卧高优先级可触发报警bending 和 lying 是跌倒报警的关键分界。标注时侧躺、半卧、靠在墙角滑坐到底都算 lying只要躯干接近水平就算弯腰捡东西、系鞋带、蹲着整理货架都算 bending。这个度要靠标注规范固定下来我处理时是把“膝关节角度明显弯曲”作为 bending 的硬性标准把“躯干与地面夹角小于30度”作为 lying 的硬性标准避免主观判断。标注规范最好写成一条条可检查的规则随数据集一起发给协作标注的人。3.3 标注工具选型classes.txt 顺序决定标签ID不能乱目标检测常用标注工具里labelImg 是最轻量的选择输出 YOLO 格式方便适合个人项目Label Studio 适合多人协作能在线审标X-AnyLabeling 带辅助检测模型可以先用预训练模型预标注再人工修正。不管用哪个第一步都是先配置 classes.txt把第 3.2 节的 5 个类别名按顺序写进去。标注工具生成的 class_id 就是按这个顺序来的如果 classes.txt 里第 4 个是 bending而 data.yaml 里 names 第 4 个是 lying训练出来的模型预测结果会整体错位这种翻车最容易发生在中途调整类别顺序之后。我一般会在标注完一版后跑一个映射脚本把类名转成目标顺序。常见做法是读入原始标注文件用类名字典重新生成 txt多类别项目几乎是必踩的流程。import os # 原标注顺序 - 目标数据集顺序 old_to_new { stand: 0, stand_up: 0, walk: 1, sit: 2, bend: 3, squat: 3, fall: 4, lie: 4, down: 4, } label_dir ./raw_labels for fname in os.listdir(label_dir): if not fname.endswith(.txt): continue path os.path.join(label_dir, fname) out_lines [] with open(path) as f: for line in f: parts line.split() if len(parts) 5: continue cls_name parts[0] new_id old_to_new.get(cls_name) if new_id is None: print(f未映射的类别: {fname} {cls_name}) continue out_lines.append(f{new_id} { .join(parts[1:5])}) with open(path, w) as f: f.write(\n.join(out_lines))这段脚本解决两类问题。一类是不同来源的标注类别名不统一比如有人标 stand有人标 stand_up通过字典映射归一另一类是标注到一半决定把“fall”并入“lying”用脚本批量改 ID 而不是手动重画。注意脚本里 new_id 写的是字典里的数字如果后续再调整类别顺序只改字典值就行。跑完这个脚本必须回头重跑第 2.3 节的类别统计命令确认每个类别的框数量合理。3.4 框选规则遮挡、小目标与躺姿目标的标注口径标注规则不写清楚多人协作时数据质量一定崩。几条硬规则目标被遮挡超过一半的框不标目标高度小于图片高度 5% 的不标这类小目标对模型是噪声而不是有效样本lying 姿态的框要紧贴人体最小外接矩形不要把地面反光或影子扩进去同一行人在相邻帧里的类别和框不能忽大忽小建议从视频抽帧后按帧顺序连续标注保持一致性。bending 和 lying 的边界最容易标歪。一个人靠着墙慢慢滑坐到地上前半段躯干还是竖直的算 sitting后半段躯干完全卸力、几乎水平才算 lying。如果一个人蹲在地上低头看东西躯干前倾但臀部没有着地这是 bending。标注时不要试图标“跌倒的过程帧”只标姿态稳定下来的状态这样模型学到的类别特征才干净。4. 训练前的数据校验与增强调优避开 yolov5 默认参数的两个坑目录搭好、标签也画完了很多人直接开训结果 loss 掉不下去或者 mAP 极低。训练前必须做数据校验和增强策略选择。这一章给可跑的校验脚本再讲 yolov5 超参数里两类常见的坑。4.1 数据校验脚本缺标签、空标签、越界坐标一次查完最影响训练的不是标注偏差而是标签文件本身写坏了。比如一张 1920x1080 的图标注工具导出的坐标是绝对像素没转成归一化坐标训练时边界框全跑到图外或者某张图只有一个空 txttrain.py 不会报错但该样本等于被丢弃。下面这个脚本按图片目录扫描检查每个同名标签文件的格式。import os def check_labels(img_dir, label_dir): errors [] total_boxes 0 for fname in os.listdir(img_dir): stem os.path.splitext(fname)[0] label_path os.path.join(label_dir, stem .txt) if not os.path.isfile(label_path): errors.append(f缺标签: {fname}) continue with open(label_path) as f: lines [l.strip() for l in f.read().splitlines() if l.strip()] if len(lines) 0: errors.append(f空标签: {fname}) continue for line in lines: parts line.split() if len(parts) ! 5: errors.append(f格式错误(应为5列): {label_path} - {line}) continue try: cid int(parts[0]) x, y, w, h map(float, parts[1:]) except ValueError: errors.append(f数值错误: {label_path} - {line}) continue if cid 0 or cid 4: errors.append(f类别ID越界: {label_path} - {line}) if w 0 or h 0 or not (0 x 1 and 0 y 1): errors.append(f坐标越界或非归一化: {label_path} - {line}) total_boxes 1 return errors, total_boxes img_dir fall_detect_dataset/images/train label_dir fall_detect_dataset/labels/train errs, boxes check_labels(img_dir, label_dir) print(总框数:, boxes) if errs: print(发现, len(errs), 个问题:) for e in errs[:50]: print(e) else: print(校验通过)脚本的重点是检查三类典型问题缺标签、空标签、格式错误。坐标越界判断直接看 x、y、w、h 是否都在 0 到 1 之间非归一化坐标几乎必然越界。类别ID 越界检查写死成 0 到 4如果你改了类别数量记得同步改这个范围。跑完后如果错误数很多不要只盯着打印的前 50 条先回看是哪个环节造成的是划分脚本没配对成功还是标注工具导出时配置错了。这个脚本我每换一版数据都会跑一次是训练前最便宜的一次体检。4.2 增强策略mosaic 未必适合跌倒检测翻转要谨慎yolov5 默认超参数里 mosaic 是 1.0意味着每张训练图有 80% 概率由 4 张图拼接而成。mosaic 对小目标检测帮助很大但对跌倒检测数据集可能适得其反拼接图里的行人姿态被截断standing 和 walking 基本靠腿判断被拼接线切掉下半身后类别特征直接丢失。我见过一版数据训出来 walking 的 AP 特别低排查半天发现是 mosaic 把人物腰部以下全切掉了。另一种做法是训练前中期关掉 mosaic最后 30 个 epoch 再打开做微调。yolov5 的超参数文件是 data/hyps/hyp.scratch-low.yaml改这三个关键项mosaic: 0.0 # 数据量小或姿态依赖肢体细节时先关掉 mixup: 0.0 # 混合增强会让不同姿态的人体重叠类别边界更糊 fliplr: 0.5 # 左右翻转对姿态检测安全但如果你区分“向左侧倒”和“向右侧倒”必须关掉fliplr 默认 0.5 是随机左右翻转。大多数跌倒检测场景不关心倒向开着没问题但如果你的业务要统计倒向比如判断从床上跌落还是从站立位摔倒翻转会把方向信息洗掉必须设成 0。degrees 旋转增强我一般不超过 10监控画面本身是固定的过大的旋转会引入不存在的拍摄角度反而让模型在真实监控画面上飘。4.3 超参数里的两个默认坑输入尺寸与锚框重算第一个坑是图省事用 --img 320 训练。跌倒场景里 lying 的行人框往往又扁又长缩到 320 后宽度方向的像素点极少模型几乎学不到“躺着的人”和“地上的一滩影子”的区别。训练和验证都用 640 起步小目标占比高就上 1280代价是显存占用和训练时间翻倍。显存不够时优先减 batch不要减 img。第二个坑是锚框。yolov5 每次训练都会打印 Analyzing anchors 的日志自动用 k-means 重算锚框。多数情况下你不用干预但如果发现日志里显示 anchor 的平均召回率低且 lying 类检不准先用下面命令单独计算一次自定义锚框python train.py --data data.yaml --weights yolov5s.pt --epochs 100 --img 640 --batch 16训练时看终端里这段日志Analyzing anchors... 0.98, 1.00, 1.02 ...锚框平均召回率在 0.9 以上就不用管。低于 0.85 时可以把数据集中所有框的长宽比打印出来如果绝大多数框都是高大于宽的竖长条说明默认锚框初始值偏向 COCO 的通用分布和跌倒场景的竖长人体不匹配。这种情况不要手动改锚框让 autoanchor 多跑几轮自己收敛你只需要保证训练脚本里没有加 --noautoanchor其余交给 k-means。5. 跌倒检测数据集在 yolov5 训练中的避坑记录四种常见翻车点数据集的坑比模型代码的坑难查十倍因为报错很温柔训练能跑loss 正常下降验证集上也看得过去但一上线就废。下面几条是从标注、划分到训练过程里最常出现的问题按“现象、原因、解决”写的踩坑记录。5.1 类别ID 错位所有预测结果都“对不上人”现象训练完成后推理模型把站着的人识别成 lying把躺下的人识别成 sitting而且切换类别有某种固定规律。loss 曲线完全正常mAP 也不算低就是类别张冠李戴。原因标注工具里的 classes.txt 顺序和训练用的 data.yaml 的 names 顺序不一致。比如标注工具里第 4 个类是 fall标注时写进 txt 的类别ID 是 3但 data.yaml 里第 4 个ID3是 bending于是所有 fall 样本都被模型当成 bending 学习。解决这是纯数据问题不用重训也能查出来。随机抽几张图打开对应的 txt对照映射表看每行 ID 是否和图上框对应。确认错位后用第 3.3 节的映射脚本把 txt 里的ID 重排重新训练。以后每次改类别顺序第一时间同步改标注工具 classes.txt 和 data.yaml names。5.2 缺标签文件训练集看起来很大实际有效样本少一半现象训练日志里每个 epoch 的 val 指标都很差甚至 mAP 为 0。检查 4.1 节的脚本发现大量图片在 labels 目录里没有同名 txt。原因划分脚本只检查了图片目录没有校验标签是否配对成功。比如原始标签目录里文件名多了个空格、或者后缀是 .txt 而脚本找的是文件名加 .txt都会导致配对失败。train.py 对缺失标签的处理是直接跳过图片从而训练样本数远小于预期。解决没有捷径重新跑配对逻辑。检查文件名是否包含不可见字符建议在划分脚本里加入“图片存在但标签缺失”的统计并把这类样本打印出来而不是静默跳过。数据量大的时候写一个反向检查统计 labels 目录里所有 txt 有哪些在 images 里找不到对应 jpg这类孤儿文件同样会污染测试结果。5.3 跌倒样本太少lying 类别只有几十个框现象训练结果里 lying 类的 recall 特别低而 standing、walking 的 AP 正常。看验证集躺倒的行人十有八九被漏检。原因视频里跌倒行为本来就是小概率事件抽帧后 lying 样本天然少于其他姿态。类别不平衡让模型把精力放在大类别上对稀有小类别的学习不充分这在目标检测里比分类更隐蔽因为 loss 是按所有框累计的。解决不要指望单纯加训练轮数。对 lying 和 bending 这两个关键类别做复制粘贴增强把人物框裁剪出来粘贴到不同背景上注意粘贴时不要遮挡原图的其他人。左右翻转和轻微旋转同样只对小样本类别做轮次控制在总轮次的 1/5 以内粘贴过多会让模型学到“躺倒的人一定有复制痕迹”。更简单的做法是从原始视频里把跌倒片段以不同帧率重新抽帧比如正常 5 帧抽 1 帧跌倒片段 2 帧抽 1 帧相当于定向增加跌倒样本。5.4 标签框过小远距离躺倒的行人完全漏检现象近景跌倒检测正常走廊远处或画面角落的 lying 目标始终检不出来。测试时把置信度阈值降到 0.1能出框但置信度极低。原因输入缩放到 640 后小目标框的宽高可能只有几个像素yolov5 特征图的最深层感受野是 32 倍下采样一个 10x10 像素的框在最大的特征图上只有 1 到 2 个像素响应loss 对小框的梯度贡献也小。解决这属于数据与模型容量不匹配。优先把训练和验证的 --img 提到 1280小框像素翻四倍代价是训练时间更长其次是做切片把原始图片按 1280 窗口切成多块训练但推理时也要切成同样大小再拼结果检测逻辑会复杂一些。如果硬件只允许 640那就要在标注时放弃特别小的目标高度小于图片高度 3% 的框留着只会拉低整体指标。5.5 数据泄露mAP 高得离谱却上线就崩现象验证集 mAP 0.95 以上precision 也很高但把视频灌进去实时检测连续几秒出现误报或漏报跟验证结果完全不匹配。原因同一个视频片段抽出的连续帧被随机划分到了 train 和 val。模型在训练时已经见过几乎相同的背景和人物姿态val 的 AP 自然虚高。跌倒检测的视频场景高度重复同一段摔倒过程的前后帧高度相似随机划分等于开卷考试。解决划分时必须以视频片段为最小单位。比如文件名是 cam01_0001_0001.jpg把视频片段编号作为分组 key同一片段的所有帧只能出现在一个集合里。下面的代码按片段前缀分组再划分而不是逐张划分import os import random samples_by_video {} for fname in os.listdir(./raw_images): stem os.path.splitext(fname)[0] video_key stem.rsplit(_, 1)[0] # 去掉帧号保留视频片段ID samples_by_video.setdefault(video_key, []).append(stem) video_keys sorted(samples_by_video.keys()) random.seed(7) random.shuffle(video_keys) n_videos len(video_keys) train_keys video_keys[:int(n_videos * 0.8)] val_keys video_keys[int(n_videos * 0.8):int(n_videos * 0.9)] train_samples [s for k in train_keys for s in samples_by_video[k]] val_samples [s for k in val_keys for s in samples_by_video[k]]这段脚本把同一个视频片段当作最小划分单位保证 val 里出现的画面内容训练时完全没见过。数据泄露在跌倒检测里属于最隐蔽的玄学问题mAP 虚高时先怀疑划分方式再怀疑标注错误。6. 验证跌倒检测效果的正确姿势混淆矩阵、误报率与连续帧判定数据集做完、模型也训出来了最后一步是验证。不要只看 mAP 一个数字跌倒检测的落地指标需要拆开看。验证命令用 yolov5 自带的 val.py它会输出 P、R、mAP0.5 和混淆矩阵图这些文件都落在 runs/val 目录下。python val.py --data data.yaml --weights runs/train/exp/weights/best.pt --img 640 --conf 0.256.1 混淆矩阵才是主角看哪两类在互相打架val.py 生成的 confusion_matrix.png 比 mAP 诚实得多。对跌倒检测数据集重点看 lying 行和 bending 行的数值如果 lying 被大量预测成 bending说明标注时两者的边界没有划清如果 standing 和 walking 互相混说明腿部特征被增强策略破坏了。mAP 高只能说明整体平均不差混淆矩阵能告诉你产品落地时误报来自哪一对类别。指标计算方式跌倒检测里的参考范围mAP0.5所有类别在IoU0.5时的平均AP0.75以上lying recalllying类TP / (TPFN)0.90以上误报率被报警的非跌倒帧数 / 非跌倒总帧数5%以下单独的 lying recall 优先于整体 mAP。如果 standing 的 AP 掉到 0.7但 lying recall 有 0.95这个模型是可用的反过来整体 mAP 0.9 但 lying recall 只有 0.6上线后跌倒漏报会非常致命。评估时用混淆矩阵筛类别用 lying recall 决定是否发布模型。6.2 连续帧过滤单帧阈值调低了误报多调高了漏报多监控场景里跌倒是一个持续状态不依赖单帧判定。常见做法是滑动窗口投票连续 5 帧里至少有 3 帧检测到 lying 且置信度超过阈值才触发报警。这能把弯腰捡东西瞬间误识别成 lying 的单帧跳变滤掉也能容忍模型偶尔丢一帧。from collections import deque def sliding_alert(frame_preds, target_cls4, conf_thr0.5, win_size5, min_votes3): frame_preds: 每帧的检测结果列表 target_cls: 目标类别IDlying4 q deque(maxlenwin_size) for preds in frame_preds: hit 0 for det in preds: cls_id int(det[5]) conf float(det[4]) if cls_id target_cls and conf conf_thr: hit 1 break q.append(hit) votes sum(q) if len(q) win_size and votes min_votes: yield True else: yield Falsewin_size 和 min_votes 是核心参数。win_size 越大报警越稳定但响应越慢病房跌倒场景里 5 帧窗口约等于 0.2 秒延迟可以接受min_votes 越高越不容易误报但连续快速跌倒且被遮挡时会漏报。我一般从 win_size5、min_votes3 起步用验证集视频调整到误报率和漏报率的平衡点。这套流程走完你会发现真正决定项目成败的是最朴素的数据集组织类别定义是否有歧义、目录结构是否对齐格式、划分是否避开了数据泄露。我自己在数据集上栽过不止一次跟头特别是把 lying 和 bending 混标导致误报爆炸后来把所有标注规范写成硬性规则模型返工率才降下来。按这个思路把数据集做扎实跌到检测项目已经成功了大半希望帮到你。本文还有配套的精品资源点击获取