做人体跌倒识别,我现在的第一反应不是一上来就堆时序网络,而是老老实实让 YOLOv8 把画面里的每个“人”先抓出来,再用一套轻量级状态机去完成跌倒判定。标题里带“目标检测完整源码”,说明它本质是一个偏向工程落地的项目,不是 train 一个模型顺手画几根曲线就结束。这套方案解决的核心问题是:在单目摄像头下,怎么用尽量低的成本、尽量稳的结果,把“有人跌倒了”这件事检测出来并报警。适合正在学目标检测、或者想把 YOLO 真正用到一个实际场景里的开发者参考。
整个系统拆开来看其实就三块:YOLOv8 负责感知,几何特征负责“看懂姿态”,状态机负责“判断事件”。很多人容易卡在第二块和第三块,以为检测出人就算完事,结果一测真实场景全是误报。这篇文章我就把这三块的完整链路、关键代码和踩坑记录都写清楚。
1. 项目整体思路:为什么用目标检测做跌倒识别
1.1 两条路线的取舍
人体跌倒识别在技术选型上大致分两条路线。一条是视频理解路线,把连续十几帧画面直接送入类似于 C3D、SlowFast 或者关键点序列 LSTM 这类模型,让网络自己学习“跌倒”这个动作的时空特征。另一条就是标题里这种路线:先用目标检测器拿到每一帧的人体边界框,然后再依靠检测框的几何变化规律去判定事件。
视频理解路线听起来很“智能”,但落地时有一道绕不过去的坎——跌倒样本又少又贵。跌倒动作本身是突发性的,真实场景里不可能大量采集,公开数据集的规模也不大,而这类时序模型恰恰又极其依赖大规模样本。我见过不止一个项目停在数据标注这一步,跌倒是负样本、正常人活动是正样本,但跌倒的多样性远比想象中复杂,前倾跌倒、侧身跌倒、从板凳上滑落、带着物品跌倒,每多一种情况,模型的数据需求就往上涨一截。
目标检测加状态机的路线就务实得多。我不需要让模型理解“跌倒”这个抽象动作,只需要它稳定输出每一个人的检测框。一个成年人正常站立时,检测框的比例大概是高大于宽;一旦躺倒或者在低矮位置,检测框会从“高瘦”变成“矮胖”。这个变化在像素坐标里是一个再清晰不过的几何事实,用规则去描述它,天然就有很强的可解释性。
1.2 单帧检测框为什么够用
有朋友问过我:只靠检测框的宽高比,能区分“躺下休息”和“真的跌倒”吗?确实不能,所以我在系统里做了一个关键升级——不只看单帧,而是看连续帧的联合变化。
这里要拆成两个维度来看。第一个维度是姿态维度,人体边界框的宽高比可以反映人体主轴方向;第二个维度是运动维度,跌倒发生时人体在垂直方向上的移动速度会突然变大,而倒地之后一段时间内几乎不再有大位移。这两个特征都藏在检测框的位置和尺寸变化里,不需要额外的模型,只需要一个跨帧追踪机制把这些框对应到同一个人的连续轨迹上。
所以最终系统的工作流程大概是:YOLOv8 检测到人 → 计算每个人的边框宽高比和中心点位置 → 跨帧分配 ID → 用滑动窗口观察连续若干帧的变化模式 → 触发报警状态。这个方案的另一个优势是实时性极好。视频理解模型通常要处理一个时间窗口内的多帧特征,而检测加状态机的基本单位就是一帧,配合轻量级权重,在普通 CPU 或者边缘盒子上也能跑到实时。
从部署角度看,这套逻辑也非常友好。所有中间结果都是可解释的,任何一个误报都能反查出来是“宽高比阈值过高”还是“中心点速度突变判据出错”,这在做工程项目调试时太重要了。
2. 数据与标注:把“样本问题”搞定
2.1 数据来源与自采方案
如果你只是想复现一套 demo 级系统,数据可以分两条路走。一条是直接用公开的跌倒数据集,里面有室内摄像头视角下模拟跌倒的短视频。这类数据集的优点是画面经过整理,直接用很方便;缺点是样本量普遍不大,视角场景有限,拿来验证逻辑没问题,真到现场部署时大概率还要补数据。
另一条就是自己动手采集。我接手某个项目时就是让几位同事模拟跌倒动作,在客厅、走廊、卧室三个场景分别录了一段时间的视频。听起来有点好笑,但实际效果比想象中好,因为自采数据的场景、光照、摄像头角度和你的部署现场完全一致,模型和规则都会更贴合真实环境。
自采数据有个关键点容易被忽略——多样性。很多人录的时候习惯让测试者从画面正中间垂直倒下,录出来全是一个角度。真正布置摄像头时,视角通常偏高,人可能从画面左侧摔倒、右侧摔倒、背对镜头摔倒、被物体遮挡一半后摔倒。这些情况都需要在采集阶段反复覆盖,不要怕麻烦。
2.2 标注与格式转换
数据采集完成后需要标注。本项目既然核心是找到画面里的“人”,标注类别可以非常克制。我自己在做系统时反复比较过两种标注策略,这里展开说:
策略一是只标注一个 person 类别,直接复用已经训练好的 YOLO 预训练模型。COCO 数据集训练出来的模型本来就认识 person,不需要再训练检测器,你只需要把后面的状态机逻辑写好。策略二是单独标注一个 fall 类别,让 YOLO 自己去学习“跌倒的人”长什么样。第二种策略听起来更智能,但实际效果往往不如预期——跌倒的人的形态差异太大,蜷缩、侧躺、趴着都算跌倒,模型很容易把其中某个姿态学得过拟合,换个角度就不认识了。
我的建议是:把“找人”的工作交给预训练模型,把“判断跌倒”的工作交给规则。如果你就是想自己训练一版,那至少要准备几千张经过整理的样本。标注工具可以用 X-AnyLabeling 或者 LabelImg,直接导出 YOLO 格式。重点提醒一点:标注时不要只标跌倒瞬间,正常站立和走动的负样本一定要有足够数量。系统误报大多不是模型找错了人,而是规则把“坐下” “蹲下”也当成了跌倒,负样本的存在能在训练阶段就帮模型区分出卧姿与普通低姿态的边界。
2.3 数据增强与样本平衡
训练 YOLOv8 时数据增强的默认策略已经很强,Mosaic、随机透视、色彩抖动都是默认开启的。但在跌倒检测这个具体场景里,有两个增强选项需要手动留意。
一是旋转增强要克制。YOLO 默认的旋转增强角度范围比较大,如果旋转过大,本来站着的样本会被拧成躺着的样子,反而干扰模型对真实卧姿的学习。我自己是把旋转角度限制在正负 10 度以内。
二是左右翻转可以直接开。摄像头摆放位置不同,人从左侧倒还是从右侧倒在语义上是等价的,左右翻转不但不会破坏跌倒语义,还能直接让训练样本翻倍。
类别平衡方面,如果你最终决定训练 fall 类别,一定要控制正负样本比在合理范围,大概正样本:负样本在 1:3 到 1:5 之间比较合适。负样本过多模型会倾向于把所有“人”推断成正常姿态,导致跌倒完全漏报。
3. YOLOv8 训练配置与调参心得
3.1 环境与模型选型
先解决环境问题。YOLOv8 目前通过 Ultralytics 框架使用,安装非常简单,一个 pip 命令就能搞定。基础依赖是 PyTorch,版本建议 1.8 以上,Python 3.8 以上都不挑。
模型选型方面,YOLOv8 有 n、s、m、l、x 几个梯度。这个项目里,我的经验是用 nano 或者 small 就足够。为什么?因为跌倒检测的核心判定并不依赖极高精度的分割或细小特征,只需要边界框足够稳定。模型越大,边界框越准,但推理速度直线下降,对边缘部署非常不友好。实测下来,在同等条件下 nano 版本的大小模型差别对最终跌倒判定的准确率影响并不大,反而推理延迟上升会让跨帧追踪变得吃力。
| 模型版本 | 推理速度参考 | 精度参考 | 适用场景 |
|---|---|---|---|
| yolov8n | 快 | 较低 | 边缘盒子、CPU 实时 |
| yolov8s | 中 | 中 | 普通 GPU、实时监控 |
| yolov8m | 较慢 | 较高 | 离线分析、高精度需求 |
| yolov8l/x | 慢 | 最高 | 服务器端离线批量 |
如果你完全采用“只检测人”的策略,连训练都可以省掉,直接用官方预训练权重就可以,因为 COCO 预训练模型已经能稳定识别 person 类。我实际做项目时大部分时间花在状态机调参上,模型训练反而很快,这算是这套路线的隐藏福利。
3.2 训练命令与参数含义
如果你确实打算自己训练一个 person 检测器,命令并不复杂。准备一个 YAML 格式的数据集配置文件,包含训练集、验证集路径和类别名称,然后执行:
yolo detect train data=fall.yaml model=yolov8n.pt epochs=150 imgsz=640 batch=16几个核心参数值得展开说一下。epochs 我建议先设 150,配合早停机制让训练在验证集指标不再提升时自动停下来,防止过拟合。imgsz 训练分辨率直接决定边框回归精度,640 是一个精度和速度都很稳的默认值;如果你的摄像头画面中人体区域很小,可以尝试 960,代价是训练时间变长。batch 大小取决于显存,显存不够就往下调,同时学习率会自动适应。
训练过程中的关键指标是 mAP50 和 mAP50-95。对于单类 person 检测,mAP50 通常要跑到 0.85 以上才算健康。如果始终上不去,优先检查标注框有没有错位,其次检查数据增强是不是过于激进。
3.3 训练踩过的坑
训练阶段最典型的坑是远处小目标漏检。摄像头视角一宽,远处的人可能只有几十个像素高,模型在大分辨率训练时对小目标不敏感。我当时的解决方案是,把训练分辨率提高到 640 以上,并且在数据集里刻意保留了大量“远处的人”的样本,而不是只截取近景。
另外一个坑是负样本不足导致误检。监控画面里的背景往往包含大量人形物体,比如衣柜里的衣服、柱子旁边的人形阴影,这些都会骗过检测器。如果发现检测模块在背景区域频繁框出虚假的人,请把负样本数据增强再加强一些,或者直接采集现场的“没有人”的空背景帧混入训练集。
4. 跌倒判定的核心状态机设计
4.1 单帧几何特征计算
一旦检测模块稳定输出边界框,接下来就是整个项目的灵魂部分——如何从边界框序列中判断跌倒。先看单帧特征。
对于每一个人体边界框,我计算两个最基础的特征。第一个是宽高比,定义为边界框高度除以宽度。想象一下正常站立的人,检测框是竖长的,宽高比通常在 2 到 4 之间;人一旦躺倒,检测框变成横向的,宽高比直接掉到 1 以下。第二个是中心点坐标,尤其是中心点的垂直位置,用来感知躯干在画面中的高度变化。
只拿这两个特征做判断会出现一个明显问题:人蹲下来系鞋带时,宽高比也会变成 1 左右,中心点高度只降了一半,这看起来和跌倒的前半段很像。所以单帧特征只能用来标记“疑似姿态”,绝对不能作为报警依据。
4.2 时序过滤与目标追踪
解决“疑似”到“确认”之间的沟壑,靠的是时序信息。一个人在跌倒时,宽高比从大于 1 骤降到小于 1 的同时,中心点在垂直方向会产生一个明显快速下移。这个“快速下移”就是区分跌倒和主动蹲下的关键:主动蹲下时中心点移动是平滑的、可控的,而跌倒时的下移是突然的、不可控的。
要让这些特征落脚到“同一个人”身上,跨帧追踪必不可少。我用的不是复杂的深度学习追踪器,而是一个基于检测框在相邻帧之间重叠程度的简易追踪器。每一帧检测出来的框,与上一帧已有的跟踪轨迹计算交并比,如果交并比大于某个阈值,就把当前框分配给那条轨迹。没有匹配上的检测框则新建轨迹。这样做简单轻量,一个人跌倒后身体变形也不会导致轨迹立刻断裂。
时序过滤还有一个额外技巧——容错机制。短自测的时候我试过,人跌倒后身体会蠕动一下,此时某个瞬间的宽高比可能会短暂变大,如果不允许这样的“回弹”,系统会误认为人又站起来了。我在设计时给了状态一个容错计数,宽高比异常时计数增加,正常时计数缓慢减少,只有连续多个异常帧才能触发报警。折下来,容错值设为总判定帧数的四分之一非常合适。
4.3 报警状态机的完整流程
我把整个判定流程设计成一个简单但关键的状态机。每个目标 ID 都有独立的状态,互不干扰。默认状态是 normal,系统监测宽高比和运动速度。当宽高比低于阈值且中心点下降速度超过阈值时,状态进入 pre_fall。pre_fall 状态维持时,如果连续 M 帧仍然满足疑似条件,状态进入 alarm,触发报警。在 pre_fall 期间如果宽高比恢复正常,状态立即退回 normal。
alarm 状态不是永久的。系统会持续监测该目标,一旦宽高比恢复到接近正常水平并保持一段时间,自动复位到 normal,表示人已经起身;对于严重场景还可以人工确认后复位,防止误报持续刷屏。
这套状态机的设计逻辑就是三个字:先怀疑、再确认、再恢复。它把检测结果从“这一帧像不像跌倒”提升到了“这个人近期是否发生了跌倒事件”,误报率极大降低。
5. 完整代码实现与关键逻辑解析
5.1 检测与特征提取代码
这里直接给出一版可用性很高的核心代码骨架,把检测和特征提取串起来。代码基于 Ultralytics 框架,第一个文件是检测模块。
import cv2 from ultralytics import YOLO model = YOLO("yolov8n.pt") def get_person_boxes(frame): results = model(frame, verbose=False) boxes = [] for r in results: for box in r.boxes: cls = int(box.cls[0].item()) conf = float(box.conf[0].item()) if cls == 0 and conf > 0.5: x1, y1, x2, y2 = [float(v) for v in box.xyxy[0]] w = x2 - x1 h = y2 - y1 center_y = (y1 + y2) / 2.0 boxes.append({ "x1": x1, "y1": y1, "x2": x2, "y2": y2, "w": w, "h": h, "ratio": h / max(w, 1e-6), "center_y": center_y, "conf": conf }) return boxes代码里我加了三个关键点。类别 0 是 COCO 中 person 类别的 ID,分类置信度阈值设在 0.5,太低会引入大量背景误检,太高则容易漏掉被遮挡的人。宽高比计算时加了一个极小值保护,防止边界框宽度为 0 时出现除零。
如果你决定训练 fall 单一类别,只需要把类别 ID 判断改成你自己的编号,后续状态机逻辑完全不变。
5.2 状态机与报警逻辑代码
接下来是状态机模块。这个类负责追踪单个目标的连续状态。
class FallState: def __init__(self, track_id, ratio_th=0.9, speed_th=5, need_frames=10, max_hysteresis=3): self.track_id = track_id self.ratio_th = ratio_th self.speed_th = speed_th self.need_frames = need_frames self.max_hysteresis = max_hysteresis self.suspicious_count = 0 self.state = "normal" self.last_center_y = None def update(self, center_y, ratio): abnormal = False if self.last_center_y is not None: dy = abs(center_y - self.last_center_y) if ratio < self.ratio_th and dy > self.speed_th: self.suspicious_count += 1 abnormal = True else: self.suspicious_count = max(0, self.suspicious_count - 1) self.last_center_y = center_y if self.state == "normal" and self.suspicious_count >= self.need_frames: self.state = "fall_alarm" return "trigger_alarm" elif self.state == "fall_alarm" and not abnormal: self.state = "normal" self.suspicious_count = 0 return "reset" return "running"这里有个细节值得说明,我特意没有使用复位时清空 suspicious_count 马上触发再次报警的错误做法。报警触发后需要持续观察,宽高比恢复到正常阈值以上且没有新的冲击运动,才认为人已经起身。
主循环里做的事就是检测、关联轨迹、状态机更新。追踪器的具体实现可以用判断 IoU 加上最近邻匹配,篇幅关系就不完整展开。
6. 实测效果与优化方向
6.1 测试场景设计
项目做完后,我在实际场景里做了一批测试。测试场景一共设计了五种:正常走动、坐下休息、蹲下捡东西、模拟站立跌倒、模拟床上滑落。每个场景连续跑半小时以上的视频流,记录误报率和漏报率。
结果很有参考价值。正常走动和坐下场景基本零误报;蹲下捡东西偶尔会出现一次短时误报,但因为在 pre_fall 状态下没有达到连续确认帧数,没有触发报警。模拟跌倒场景的检出率很高,但侧身跌倒时因为检测框宽度和高度变化不如正躺明显,报警响应时间会比正面跌倒慢大概一秒钟。
6.2 误报与漏报的调优方向
针对测试中的问题,我做了两个方向的调优。
一是姿态阈值动态化。侧身跌倒是检测框宽高比变化不那么极端的情况,单纯用固定阈值会漏掉。我给系统加了“动态基线”机制,持续追踪每个目标在过去 5 秒内的宽高比均值,当前宽高比如果低于基线的一定比例就视为异常。这个改动同时解决了另一个问题:不同摄像头安装高度下的宽高比基线不同,固定阈值很难一个参数走天下。
二是引入静止检测。跌倒后的人在一段时间内不会明显移动,我把“目标中心点在报警前一段时间内速度是否为 0”作为报警确认的必要条件之一。这个逻辑可以过滤掉快速下蹲又马上站起的情况,还能防止把从楼梯上急速下楼误判为跌倒。
6.3 轻量化与延迟优化
延迟是跌倒识别系统最容易忽视的指标。跌倒检测晚一秒钟,后果可能是完全不同的。我在实际部署时做了三个层面的优化。
画面输入分辨率裁到 640 并保持纵横比,过高的输入分辨率会成倍增加推理时间,而检测效果几乎没有提升。推理线程和视频采集线程分离,采集线程不等待推理结果,降低了积累延迟。对于 CPU 部署场景,考虑将推理线程绑定到指定核心,并把摄像头帧率限制在 15 到 20 帧,没必要追求 60 帧的高帧率,状态机逻辑并不依赖高帧率采样。
7. 常见问题与排查速查
最后整理一张我做这个项目时踩过的典型问题对照表,按优先级排序,新同学可以直接对着排查。
| 现象 | 可能原因 | 解决办法 |
|---|---|---|
| 站着被误报跌倒 | 宽高比阈值太高或目标被遮挡一半 | 降低阈值到 0.8,同时检查检测模块是否有遮挡漏检 |
| 真的跌倒没报警 | 侧躺宽高比变化不明显 | 启用动态基线和中心点下降速度双重判据 |
| 坐下后长时间报跌倒 | 缺少静止检测,坐下后状态没有复位条件 | 增加复位条件:宽高比恢复正常且稳定保持 N 帧 |
| 多人场景目标 ID 串换 | 简易追踪器在人群交叉时失败 | 换成更强的匹配策略,增加外观特征匹配 |
| CPU 上跑不到实时 | 模型过大或输入分辨率过高 | 换 yolov8n,分辨率降到 512 或 640 |
| 夜晚画面误检漏检严重 | 光线不足导致检测框抖动 | 使用红外补光摄像头,提高检测置信度阈值 |
这个排查表是我自己反复试出来的结果。特别想强调一点,遇到误报先不要盲目调参数,先找到是检测模块还是状态机模块的问题。有个快捷方法:把检测框画在画面上连续播放,肉眼观察边界框是否稳定跟着人走。如果检测框都在跳,就别浪费时间调状态机了。
做一个这样的跌倒识别系统,技术难度其实没有想象中高,真正花时间的是状态机设计的严谨性。从决定使用 YOLOv8 开始,我就在想一个问题:系统最终是给人用的,它不能只在理想测试集上表现良好,必须在真实杂乱场景下也能稳定工作。如果你打算做一个类似的实时人体行为分析项目,我的建议是不要一开始就追求大模型和高精度指标,先跑通检测加状态机的完整链路,再把精力投入到真实场景的数据采集和参数调优上。这套方案的可扩展性也远比想象中好,后续可以加入关键点检测来判断更细的骨骼姿态,或者接入多个摄像头的目标重识别,都能在现有框架上平滑升级。