做人体跌倒识别我现在的第一反应不是一上来就堆时序网络而是老老实实让 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 类别一定要控制正负样本比在合理范围大概正样本负样本在 13 到 15 之间比较合适。负样本过多模型会倾向于把所有“人”推断成正常姿态导致跌倒完全漏报。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 datafall.yaml modelyolov8n.pt epochs150 imgsz640 batch16几个核心参数值得展开说一下。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, verboseFalse) 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_th0.9, speed_th5, need_frames10, max_hysteresis3): 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 开始我就在想一个问题系统最终是给人用的它不能只在理想测试集上表现良好必须在真实杂乱场景下也能稳定工作。如果你打算做一个类似的实时人体行为分析项目我的建议是不要一开始就追求大模型和高精度指标先跑通检测加状态机的完整链路再把精力投入到真实场景的数据采集和参数调优上。这套方案的可扩展性也远比想象中好后续可以加入关键点检测来判断更细的骨骼姿态或者接入多个摄像头的目标重识别都能在现有框架上平滑升级。