简介:面向计算机及相关专业学生,这是一份可支撑毕业设计、课程设计、期末大作业或项目实战的深度学习完整项目,基于YOLOv5人体检测与OpenPose姿态检测实现摔倒识别,适合希望获得可运行项目范式、快速产出演示系统的学习者参考。资源提供完整源码与训练好的模型,覆盖行人定位、多人姿态估计、摔倒行为判定等核心环节,能够帮助读者在真实场景中理解目标检测与姿态估计模型的串联过程,并快速搭建用于医院、养老院、家庭看护等场景的跌倒预警原型。zip压缩包约40.25MB,当前已有166人学习/下载,适合具备一定Python基础、正在学习计算机视觉或深度学习的开发者使用。通过该项目还可掌握模型调用、参数调整、推理结果可视化以及围绕骨骼关键点特征进行异常行为判断的思路,并可在现有基础上进一步扩展行为识别、跌倒报警等落地功能。
1. 摔倒检测为什么非得是 yolov5+openpose:单目摄像头下的工程真相
独居老人摔倒后无人发现、工地高处坠落无人预警、患者在医院走廊倒地——这类场景的共同点是:只有一路普通监控摄像头,没有穿戴设备,不能指望人一直盯着屏幕。深度学习领域里针对这件事的主流做法,就是把人体检测和目标姿态估计串成一条流水线:先用训练好的模型确定画面里哪里有人,再用姿态估计模型读取骨架信息,最后用几何逻辑判断这个人是不是摔了。yolov5 负责“人在哪”,openpose 负责“人摆成什么样”,两者结合之后,摔倒检测才从“看着像”变成“算得出来”。
这个项目是很多深度学习初学者的第一个综合性实战:它不靠单一模型硬解,而是把目标检测、关键点回归、时序判断三个环节串在一个推理链路里。你还会接触到模型转换、阈值调参、误报排查这些真实部署才有的问题。下面按我自己的落地顺序,把环境和代码拆开讲清楚,新手跟着能跑通,老手可以跳过铺垫直接看避坑清单。
2. 方案选型和判断逻辑:yolov5 与 openpose 在摔倒检测里的分工
2.1 为什么不能用单一模型硬刚:检测和姿态估计的边界
先回答一个常见疑问:直接用 openpose 检测人,再拿关键点判断摔倒,行不行?理论上行,工程上难受。openpose 这类姿态估计模型本身就包含人的检测,但它在画面里人较少且姿态复杂时表现不稳定,尤其是身体互相遮挡的场景。摔倒恰恰常发生在人群遮挡中——比如病房里有人站在倒地者旁边,工地里几个人走在一起。yolov5 先做目标级过滤,只保留置信度足够的“人”,再把人框裁剪出来单独送入姿态模型,姿态模型的输入干扰就少了一大半。
另一个现实理由是算力。yolov5s 在 640x640 输入下,CPU 上大约每帧 80~150ms,GPU 上轻松到 5ms 以内。而 openpose 的原版 Caffe 实现即便在 GPU 上也要 100ms 以上,CPU 上按秒算。如果在全图上跑姿态估计,视频流根本追不上实时。先用目标检测把人框剪出来,让 openpose 只处理框内区域,推理时间能缩短到原来的三分之一甚至更少。这就是为什么这个项目的标准架构不是“二选一”,而是“串联”。
2.2 摔倒的几何特征:把“摔倒了”翻译成模型能算的数字
摔倒判定不是模型直接输出“摔倒/未摔倒”,而是基于姿态关键点做规则判断。openpose 输出每个人的 18 个(COCO 格式)或 25 个(BODY_25 格式)关键点坐标。摔倒检测常用以下四类特征组合打分:
| 特征 | 计算方式 | 物理意义 |
|---|---|---|
| 人体框宽高比 | 检测框宽度 / 高度 | 站立时远小于 1,倒地时接近或大于 1 |
| 躯干与地面夹角 | 脖子到髋部连线与水平线的夹角 | 站立接近 90°,倒地接近 0° |
| 头部中心高度变化 | 头部关键点 y 坐标的连续差 | 摔倒瞬间头部 y 值快速下降 |
| 各部位垂直分布 | 肩部、髋部、脚踝关键点的 y 值离散程度 | 倒地后跨关键点高度差变小 |
单用任何一个特征都会翻车,比如下蹲会导致框宽高比变大,搬东西弯腰会导致躯干夹角变小,但这些行为的人还在继续活动。工程做法是给每个特征分配权重,算出“摔倒分数”,再用时序逻辑确认——下一小节展开。
2.3 推理链路编排:先检测、再裁剪、后判断
在常见实现里,每一帧的处理顺序是这样的:
- yolov5 跑一次前向,得到所有人体的边界框
- 对每个边界框,按坐标在原图上裁剪出 ROI
- 将 ROI 缩放后送入 openpose 模型,得到关键点坐标
- 根据关键点计算摔倒特征值,结合上一帧状态,确定是否触发警报
为什么要裁剪而不是直接把整帧关键点拿出来用?一是因为 openpose 的输入尺寸通常被限制在 368x368 或 656x368,直接缩全帧会丢失小目标的关键点细节;二是因为裁剪后姿态模型对单人的专注度更高,关键点抖动更小。这个链路在很多真实项目里也被改为并行处理:用低帧率的姿态模型配合高帧率的目标检测,减少计算压力。
# 伪代码:摔倒检测主循环的常见结构 while cap.isOpened(): ret, frame = cap.read() frame = cv2.resize(frame, (1280, 720)) # 1. yolov5 检测人体, 只保留 label='person' results = yolo_model(frame) boxes = results.xyxy[0].cpu().numpy() person_boxes = [box for box in boxes if results.names[int(box[5])] == 'person'] # 2. 遍历每个 person 框, 裁出 ROI 后送 openpose for box in person_boxes: x1, y1, x2, y2 = int(box[0]), int(box[1]), int(box[2]), int(box[3]) roi = frame[y1:y2, x1:x2] # 3. openpose 姿态推断, 得到关键点数组 keypoints = pose_model(roi) # shape: [num_person, 18, 3], 最后一维是 x, y, conf # 4. 摔倒判断 score = fall_score(keypoints[0]) # 返回 0~1 的摔倒概率值 if score > 0.6: trigger_alert(frame, box)这个流程的关键在于第 4 步的fall_score()函数,它才是整套系统的“决策大脑”。下面给出一种我在项目里验证过的打分实现,它综合了框宽高比、躯干倾角和头部速度三项,各自归一化后加权求和。
3. 跑通摔倒检测最小系统:环境搭建与核心代码
3.1 环境准备:conda、yolov5 依赖与 openpose 的模型权重
你拿到的项目包通常已经带了预训练权重,但环境还是得自己配。我建议用 conda 独立环境隔离,避免把系统 Python 搞乱。
# 创建 python 3.8 环境(yolov5 官方支持 3.8~3.10) conda create -n fall-detection python=3.8 conda activate fall-detection # 安装 pytorch,CUDA 版本根据显卡驱动选,不确定就先装 CPU 版跑通流程 conda install pytorch=1.12.1 torchvision=0.13.1 torchaudio=0.12.1 cpuonly -c pytorch # yolov5 依赖 pip install -r yolov5/requirements.txt # 视觉与数据处理 pip install opencv-python numpy pandas参数说明:如果你有 NVIDIA 显卡,cpuonly改成cudatoolkit=11.3对应的安装命令;没有显卡就先 CPU 跑,yolov5s 检测单人的速度还能接受,openpose 会慢一些,先验证流程没问题再换 GPU。Python 版本不要用 3.11 以上,部分旧版 openpose 实现依赖的numpy接口不兼容。
3.2 yolov5 人体检测:最小可用脚本
yolov5 官方提供了 torch.hub 方式加载模型,不强制从源码 clone,但项目里通常会包含yolov5/目录,因为训练脚本和超参数文件都在里面。
# detect_person.py import torch import cv2 def load_yolo(weights_path="yolov5s.pt", device="cpu"): """ 加载 yolov5 模型 weights_path: 权重文件路径, 可以是官方预训练或自己训练的 device: cpu 或 cuda:0 """ model = torch.hub.load("./yolov5", "custom", path=weights_path, source="local", force_reload=False) model.conf = 0.35 # 置信度阈值: 低于此值的目标被丢弃 model.iou = 0.5 # NMS IoU 阈值: 重叠大于此值只保留置信度高的 model.classes = [0] # COCO 中类别 0 是 person, 只保留人体 model.to(device) return model def detect_person(model, img_bgr): """ 输入 BGR 图像, 返回 person 边界框列表 每个框为 [x1, y1, x2, y2, confidence] """ # yolov5 接收 RGB 图像, 这里做通道转换 img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB) results = model(img_rgb, size=640) # size=640 是输入分辨率, 越大越准但越慢 df = results.pandas().xyxy[0] boxes = [] for _, row in df.iterrows(): if row["class"] == 0: # person boxes.append([int(row["xmin"]), int(row["ymin"]), int(row["xmax"]), int(row["ymax"]), row["confidence"]]) return boxes逻辑说明:model.classes = [0]是一种省算力的技巧,模型只输出 person 类别,不必全类别输出后再过滤。model.conf = 0.35表示检测框置信度低于 0.35 就会丢掉,背景复杂或图中有密集小人时可以调到 0.25,但误检率会上升。size=640是向模型输入的边长,如果你的摄像头画面里人很小,调到 960 能改善小目标召回,代价是推理时间增加约一倍。
3.3 openpose 姿态推断:从人体框到关键点
这里以项目中常见的轻量级 pytorch openpose 实现为例,重点是把输出转换为标准坐标系,之后再接摔倒逻辑。
# predict_pose.py import cv2 import numpy as np import torch from models.with_mobilenet import PoseEstimationWithMobileNet from modules.keypoints import extract_keypoints from modules.pose import Pose, propagate_ids def load_pose_model(weights_path="checkpoint_iter_370000.pth"): """ 加载轻量级 openpose 模型 该模型基于 mobilenet, 速度和精度平衡适合本地部署 """ model = PoseEstimationWithMobileNet() checkpoint = torch.load(weights_path, map_location="cpu") model.load_state_dict(checkpoint["state_dict"]) model.eval() return model def infer_pose(model, roi_bgr): """ 输入人体 ROI (BGR), 输出 18 个关键点坐标 返回 shape: [18, 3], 每行为 [x, y, confidence] """ # 统一输入尺寸 roi = cv2.resize(roi_bgr, (368, 368)) roi = roi.astype(np.float32) / 255.0 roi = cv2.cvtColor(roi, cv2.COLOR_BGR2RGB) roi = torch.from_numpy(roi.transpose((2, 0, 1))).unsqueeze(0) with torch.no_grad(): stages_output = model(roi) # 网络输出 heatmap 和 PAF (部件亲和场) heatmaps = stages_output[-2].cpu().numpy()[0] pafs = stages_output[-1].cpu().numpy()[0] # 峰值提取与关键点配对 keypoints = extract_keypoints(heatmaps, pafs) return keypoints参数说明:checkpoint_iter_370000.pth是轻量 openpose 常用的预训练权重名,不同版本迭代次数不一样,你拿到的项目里通常会把这个文件放在weights/目录下。model(roi)输出的是 two-stage 结构:最后一层是 PAF 特征图,倒数第二层是关键点热力图。extract_keypoints是开源实现里的工具函数,它会把热力图上的峰值点做非极大值抑制,并通过 PAF 的向量场把属于同一个人的关键点连接起来。如果程序报这个函数不存在,说明你用的不是同一个版本,直接调用项目包里的modules.keypoints即可。最关键的一点是:openpose 输出的关键点坐标是相对于输入 ROI 的,使用时必须加上 ROI 在原图中的偏移量,否则后面计算高危区域的坐标会错位。
3.4 摔倒判断逻辑:把几何特征变成可调的分数
摔倒判断是项目的核心得分点。下面是综合宽高比、躯干倾角、头部下降速度三种信号的打分函数。
# fall_logic.py import numpy as np import math # COCO 关键点索引 NECK = 1 # 颈部 HIP = 8 # 髋部中心(原版 COCO 没有髋尾, 这里用左髋右髋中点代替) LEFT_HIP = 9 RIGHT_HIP = 10 NOSE = 0 # 鼻子, 代表头部 def calc_torso_angle(kpts): """ 躯干倾角: 颈部到髋部连线的水平夹角(度) 站立时接近 70~90 度, 倒地时接近 0~30 度 """ if kpts[NECK][2] < 0.2 or kpts[LEFT_HIP][2] < 0.2: return 90.0 # 关键点缺失时按站立处理, 保守不误报 neck_x, neck_y = kpts[NECK][:2] hip_x = (kpts[LEFT_HIP][0] + kpts[RIGHT_HIP][0]) / 2 hip_y = (kpts[LEFT_HIP][1] + kpts[RIGHT_HIP][1]) / 2 angle = math.degrees(math.atan2(hip_y - neck_y, hip_x - neck_x)) return abs(angle) def fall_score(box, kpts, prev_head_y, head_speed_threshold=50): """ 摔倒综合打分, 返回 0~1 box: 当前帧人体框 [x1, y1, x2, y2, conf] kpts: 关键点数组 [18, 3] prev_head_y: 上一帧头部 y 坐标 """ # 特征 1: 宽高比 w = box[2] - box[0] h = box[3] - box[1] ratio = w / (h + 1e-5) # 站立约 0.3~0.5, 横躺可能大于 1.0 ratio_score = min(ratio / 1.2, 1.0) # 大于 1.2 视为满分特征 # 特征 2: 躯干倾角 angle = calc_torso_angle(kpts) angle_score = 1.0 - min(angle / 45.0, 1.0) # 夹角越小, 分数越高 # 特征 3: 头部下降速度 head_y = kpts[NOSE][1] head_speed = 0 if prev_head_y is not None: head_speed = head_y - prev_head_y # 正数表示头部在下降 speed_score = min(max(head_speed / head_speed_threshold, 0), 1.0) # 加权融合: 角度权重最大, 避免单靠躺倒触发 total = 0.5 * angle_score + 0.3 * ratio_score + 0.2 * speed_score return total逻辑说明:三个特征里,躯干倾角权重最高,宽高比次之,速度信号只作为辅助。原因在于速度特征对噪声敏感——弯腰捡东西时头部也会快速下降,但躯干倾角变化不明显,加权处理后分数不会越界。head_speed_threshold=50是按 720p 视频、30fps 估算的像素位移阈值;如果摄像头是俯视角度,这个阈值要减小到 20 左右。关键点置信度低于 0.2 时,该点不做几何计算,避免噪声估出的坐标污染角度值。
主循环里,加入一个“连续确认”机制:当摔倒分数连续 3 帧超过 0.6 才触发告警(因为真实摔倒后的躯干倾角变化会维持数十帧)。
4. 让模型适配你的摄像头:数据准备与关键参数调优
4.1 数据集策略:摔倒不是一张图片,而是状态序列
很多初学者把摔倒检测当成图像分类任务来训练,给它喂“站立的图”和“躺倒的图”,结果模型一到真实视频就失效。原因是摔倒是一个过程,跌倒瞬间的人体形态和躺倒后的静止形态差异很大。正确的数据集构造方式是以“状态序列”为单位:每个样本包含 5~15 帧连续动作,标注分为“站立”“蹲下/弯腰”“正在摔倒”“倒地”四类。yolov5 只负责检测人,所以标注重点在人体框上——但如果你微调模型,就得确保训练集中包含了“正在摔倒”这种介于站立和躺倒之间的半程形态。
公开数据集方面,常见的选择是 Le2i Fall Detection Dataset 和 UR Fall Detection Dataset。前者拍摄于室内房间,视角多变;后者包含深度图序列,适合做多模态扩展。这两类数据集的图像质量普遍偏低,建议用你自己的摄像头重新采集一部分画面做迁移微调,否则场景光照差异会让模型在白天窗边、夜间灯下表现飘忽。
4.2 yolov5 训练参数:照着这份设置跑第一轮
如果你打算用自己的数据微调 yolov5 的 person 类别,建议从预训练权重yolov5s.pt开始,不要从零训练。yolov5 的训练脚本在项目目录的train.py里,常用参数如下:
python train.py \ --weights yolov5s.pt \ --data fall.yaml \ --epochs 100 \ --batch-size 16 \ --img 640 \ --device 0 \ --workers 4 \ --cache ram参数说明:--data fall.yaml需要三个字段,train和val指向图像目录,nc填类别数(默认为 1,person 单类),names填['person']。--epochs 100对单类检测足够了,跑 300 轮反而会增加过拟合。--cache ram在显存足够时把图像缓存到内存中,显著缩短训练时间。还有一个容易被忽略的--multi-scale参数,如果摄像头安装了广角镜头,开启这个参数可以让模型适应不同尺度的人体。训练完成后,用export.py导出模型为 TorchScript 或 ONNX 格式,部署时推理速度更快。
4.3 推理侧的三个高频调整点:置信度、NMS 与姿态关键点阈值
模型训练完只是第一步,推理侧的参数才是你真正频繁碰的东西。下表对照了默认值和摔倒场景的推荐值:
| 参数 | yolov5 默认值 | 摔倒检测推荐值 | 原因 |
|---|---|---|---|
| 置信度阈值 model.conf | 0.25 | 0.35~0.45 | 摔倒场景误报警代价高,宁可漏检也不要错检 |
| NMS IoU 阈值 model.iou | 0.45 | 0.5~0.6 | 多人近距离时,宽松 IoU 让重叠人体框保留更多 |
| 输入尺寸 size | 640 | 640 或 960 | 960 改善小目标(远处的人)检出,但帧率下降 |
| openpose 关键点置信度过滤 | 无 | 0.2~0.3 | 低于此值的关键点不参与运算,去除抖动 |
置信度阈值是整套系统最敏感的设置。在养老院室内场景,0.5 的光照和监控视角下,0.35 与 0.45 的差异每天可能带来 2~3 次误报。我的做法是:先把阈值调到 0.25 运行一周记录误报帧,再逐步提高直到误报消失,取中间值。
yolov5 的 NMS 参数同样重要。默认的 0.45 在多人重叠场景里有问题:人 A 的框与人 B 的框高度重叠,NMS 会删掉其中一个,摔倒者的框可能被相邻者吞掉。把model.iou调到 0.6,能让重叠的人体框共存,代价是同一人身上可能出现两个嵌套框,需要按置信度做二次筛选。
5. 摔倒检测的四个高频坑与排查清单
5.1 现象:openpose 在 CPU 上慢得像幻灯片,每帧要 3~5 秒
原因:轻量 openpose 虽然比原版快,但 CPU 推理仍需要数百毫秒到数秒。如果你没有裁剪 ROI 而直接在全图上推理,算力消耗成倍增加。还有个容易被忽略的因素是模型内部执行了多次图像缩放,输入尺寸越大耗时越长。
解决:首先确认检测链路是否做了 ROI 裁剪——先用 yolov5 把人体框裁减到cv2.resize的 ROI 上,再送入 openpose。其次把 openpose 输入从 656x368 降到 320x240,精度下降但速度能提升 3 倍。最后可以考虑每 2 帧跑一次姿态估计,目标检测保持每帧执行。真实摔倒过程至少持续 600ms,30fps 下隔帧采样完全够用。
5.2 现象:人蹲下整理鞋带、弯腰捡东西,被误判为摔倒
原因:宽高比和躯干角度两个特征在下蹲时都会朝“摔倒”方向偏移。下蹲时躯干角通常在 30 度左右,宽高比接近 0.8,两个特征叠加后总分可能达到 0.6 的触发线。真实摔倒与下蹲的差异在于时间尺度和速度:摔倒时头部 y 坐标变化速度极快,且帧间方向一致。
解决:给速度特征增加一个“突变持续时间”条件——要求连续 5 帧头部速度都超过阈值时才计入分数,单纯一帧的快速头部移动不参与加权。同时把触发分数提高到 0.7,并加入髋部中心高度变化辅助判定(髋部在摔倒时快速降低,下蹲时相对缓慢)。实测这一改动能把误报率降低一半以上。
5.3 现象:关键点坐标严重抖动,角度和宽高比计算时好时坏
原因:openpose 输出的关键点坐标是浮点数,不同帧间存在 ±5 像素的噪声。摔倒判定对躯干角度极其敏感,5 像素的抖动在远距离可造成 10 度以上的角度偏差。
解决:对关键点坐标做一级低通滤波,最简单的是指数移动平均(EMA)。
# keypoint_smoothing.py import numpy as np smooth_kpts = None alpha = 0.4 # 新帧占比, 越小越平滑但延迟越大 def smooth(raw_kpts): global smooth_kpts if smooth_kpts is None: smooth_kpts = raw_kpts.copy() else: # 置信度低于 0.2 的关键点不做平滑, 直接用原始值 mask = raw_kpts[:, 2] < 0.2 smooth_kpts[mask] = raw_kpts[mask] smooth_kpts[~mask] = alpha * raw_kpts[~mask] + (1 - alpha) * smooth_kpts[~mask] return smooth_kpts参数说明:alpha=0.4兼顾平滑度和时效性,调小到 0.2 画面会更稳定但摔倒的快速姿态变化会被滞后。出生时摔倒检测目标是抓“突变”,所以 alpha 不宜小于 0.3。
5.4 现象:摄像头从平视改成俯视安装后,所有判断全部失效
原因:平视场景下,人摔倒后宽高比会明确大于 1;但俯视 60 度安装时,站立的人本身宽高比就可能接近 0.8~1.2,摔倒前后宽高比变化不明显,躯干角度的几何定义也变化了。如果你使用别人预训练好的模型和原项目阈值,几乎必定翻车。
解决:俯视相机场景下,重心速度与地面距离成为更可靠的特征。头部到髋部中点的投影距离突然缩短、髋部 y 值快速增大,这两个信号的鲁棒性优于角度。改判据时建议重写fall_score()函数,不要微调原阈值。另外,模型标注需要加入“侧卧”“俯卧”“仰卧”三种姿势的遮挡情况,俯视下关键点大量遮挡,openpose 会丢点,这时候宁可漏报也不要依据残缺关键点强行判断。
5.5 现象:训练时进程崩溃,报错与 numpy、json 相关
原因:yolov5 对numpy版本有要求,新版 numpy 移除了某些接口导致旧代码报错。同时,pycocotools在 Windows 上不安会导致训练无法启动。
解决:固定numpy==1.22.4,确保 pycocotools 安装成功(Windows 下用pip install pycocotools-windows)。opencv 注意不要用 4.8 以上版本,部分推理代码对cv2.CascadeClassifier的兼容性不理想。
6. 把误报压下去:时序确认与二次验证的进阶技巧
运行稳定之后,你会发现自己最大的敌人不是模型精度,而是边缘计算设备上的帧率波动。以树莓派或边缘盒子为例,yolov5s 在 CPU 上约 8fps,openpose 约 2fps,两者串行时实际帧率可能掉到 1.5fps。这时摔倒过程仅能捕获 2~3 帧,时序确认的窗口就要缩短——连续 2 帧确认比连续 3 帧更适合低帧率设备。
我踩过的一个坑是:把摔倒检测结果直接接入微信/短信告警,结果模型把“拖地时拖把的影子”当成另一人摔倒,半夜电话响不停。后来加了二次验证:触发告警后,把摔倒前后各 15 帧的关键点坐标、速度峰值、角度变化曲线存为 JSON 日志;同时在画面中摔倒位置截取一张带关键点绘制的图片,人工核查时只看图片就能判断误报原因。这组日志不仅帮我调好了阈值,还成了论文里最有说服力的实验截图。
另一个值得投入的方向是摔倒后的静止检测——真摔之后,人往往无法立刻站起来。在摔倒判定触发后,继续跟踪检测框内人体关键点的相对位移,若在 10 秒内检测到髋部高度抬升超过初始值的 50%,则取消告警。这个机制能有效过滤“跌倒后立刻爬起”的误报,也让系统更贴近真实监护需求。我自己做这个项目时,把误报率从每天 7 次压到了每天不到 1 次,靠的就是这个静止确认逻辑,外加把置信度阈值从 0.4 微调到 0.45。取阈值这事确实有几分玄学,但按“先跑一周统计分布,再做微调”的路子走,基本不会走偏。希望帮到你。
本文还有配套的精品资源,点击获取