OpenPose+OpenCV人体形态识别:跌倒检测与动作计数实战
简介面向计算机视觉开发者的完整人体姿态识别项目基于 Python、OpenCV 与 OpenPose 实现从摄像头或视频流中实时检测人体关键点。资源包为 7z 格式共 40 个文件大小约 8.12MB其中包含 19 个 Python 脚本、12 个 pyc 编译文件、2 个 Markdown 文档、2 个示例图片以及 1 个演示视频。Python 脚本覆盖数据预处理、模型训练、推理检测、关键点提取与可视化全流程包括train.py、demo.py、val.py等可直接运行的入口以及models、modules、scripts等子模块Markdown 文档提供环境配置与在自定义数据集上训练 OpenPose 模型的详细指南示例图片与演示视频便于直观查看效果。项目还附带 COCO 数据准备脚本、关键点滤波、损失函数、模型定义等模块可帮助深入理解网络训练与推理机制。已有 7028 人浏览学习适合运动分析、健身指导、虚拟现实、人机交互和医疗康复等场景例如教练可通过姿态关键点判断运动员动作是否标准或用于智能健身设备实现实时动作反馈。1. 视频和摄像头里识别人体形态OpenPose 不是终点形态要靠自己算养老院走廊尽头有个摄像头凌晨三点多老人从床边站起来时身影歪斜了一下。你要在连续几帧里判断出他是不是要跌倒这就是“视频摄像头”场景下用 Python、OpenCV、OpenPose 做人体形态算法识别最典型的需求。这套方案的核心不是让 OpenPose 直接输出“跌倒”两个字而是从视频帧里提取 18 个骨骼关键点再按几何关系算出躯干倾角、包围盒宽高比、重心移动速度这些形态指标。适合做行为分析、动作计数、安防巡检的小团队快速落地一个低成本原型。2. 人体形态识别的底层逻辑从 OpenPose 关键点到形态指标2.1 OpenPose 输出了什么关键点顺序与两个模型怎么选OpenPose 是一个人体姿态估计模型输出不是“这个人站得好不好”这种结论而是一组关键点热图。网络有两条分支一支预测每个关节的置信度热图另一支预测 PAFPart Affinity Fields部位亲和域用于判断两个关键点之间是不是同一条肢体、属于同一个人。在大多数实际项目里我们消费的是热图分支PAF 主要帮官方后处理做多人骨架拼装。用 OpenCV DNN 加载模型时PAF 分支同样会在 forward 里输出但分组效果不如官方 C 后处理完整后面避坑章节会专门讲。最常用的模型是 COCO 18 点关键点顺序是固定的索引顺序直接影响后续所有几何计算贴在这里方便对照查表索引部位索引部位0鼻子9右膝1脖子10右踝2右肩11左髋3右肘12左膝4右手腕13左踝5左肩14右眼6左肘15左眼7左手腕16右耳8右髋17左耳OpenPose 还提供 BODY_25 模型在 COCO 基础上多了中髋点索引 8和脚部关键点比如大脚趾、小脚趾、脚跟。对步态分析和站立重心判断更有用但推理量更大CPU 上会更吃力模型文件也更大。如果是做跌倒检测、蹲起计数这类以躯干姿态为核心的需求COCO 18 点已经够用而且它在 OpenCV DNN 里加载更快、参数更少。2.2 形态指标不是模型直接给的角度、倾角、包围盒比例怎么定义把关键点变成“形态”这一步才是人体形态算法识别真正的工程量。常用的形态指标有三个全部用几何公式从关键点坐标推算。第一个是躯干倾角。取左右肩的平均点作为肩部中点左右髋的平均点作为髋部中点连线这个向量和图像竖直方向的夹角就是躯干倾角。站直的时候接近 0 度人慢慢倒向地面时角度增大躺平时肩和髋处于同一水平线夹角接近 90 度。跌倒检测最核心的指标就是它。第二个是髋膝角。同一侧的髋、膝、踝三个点在膝关节处形成的夹角正常站立接近 180 度坐下或者屈膝时小于 150 度这个指标用来区分站立、坐下和倒地。第三个是包围盒宽高比。把所有可见关键点的最小外接矩形算出来宽度除以高度。站姿宽高比通常小于 0.5躺姿一般大于 1。实际写代码时夹角用向量点积和 atan2 计算图像坐标系 y 轴向下计算出的正负号还能表达向左倒还是向右倒。这些几何计算本质上跟 OpenCV 里的直线拟合、角度测量是一套东西很多做过 opencv 检测直线、骨架提取的同学应该不陌生。关键认知是模型的输出只是坐标点形态语义完全由这些后处理公式定义阈值工程决定最终效果。2.3 为什么用 OpenCV DNN 加载模型而不是编译官方 OpenPose网上很多教程会让直接装官方 OpenPose但那个编译链在 Windows 上非常容易翻车。我最早照着官方文档去编译卡在 CMake 和 Caffe 依赖链上耗了一整天最后发现 OpenCV DNN 模块可以直接加载同一个 Caffe 模型文件代码量反而更少。两套路径对比如下方式依赖调试成本CPU 实时性部署体积官方 OpenPose APICaffe、CMake、cuDNN推荐 GPU高编译链长差代码库庞大OpenCV DNN caffemodelopencv-python 即可低改参数方便可接受降分辨率后能跑两个模型文件加脚本对环境的要求是 opencv4.2这不是玄学而是 4.2 之后 DNN 模块对 Caffe 模型的解析更完整旧版本遇到 prototxt 里的 Reshape 层可能直接崩。OpenCV DNN 的代价是多人场景的关键点分组不如官方后处理精细所以方案适用边界要提前想清楚室内单人或者稀疏人群场景这套组合性价比很高密集人群且要求精确的多人骨架归属还是得回到官方推理链路。3. 环境与模型准备用 OpenCV DNN 把 OpenPose 模型跑起来3.1 Python 与 OpenCV 安装别急着装最新版很多 Python 入门教程里只会说“pip install opencv-python”但直接装最新版经常踩坑新版本 DNN 输出布局可能有调整opencv-python 和 opencv-contrib-python 混装还会导致 cv2 包冲突。我一般在项目里固定一个已经验证过的组合而不是追新版本。# Python 3.9 或 3.10 下跑过的组合 pip install opencv-python4.8.0.74 pip install opencv-contrib-python4.8.0.74 pip install numpycontrib 和主包版本必须保持一致不然以后想用 cv2.Tracker 之类的扩展模块会出现导入异常。其实只做 DNN 推理不装 contrib 也能跑但后续目标跟踪、图像处理项目里要加功能时又得补干脆一次装齐。装完先在命令行里验证一下import cv2 print(cv2.__version__)在 VS Code 里跑的时候还要注意解释器路径Python 环境配置最常见的问题就是终端里 pip install 装好了F5 运行时 import cv2 仍然报 no module named cv2原因是 VS Code 选中的解释器和终端里的 Python 不是同一个。检查方法很简单在 VS Code 右下角看解释器路径确保和 pip 安装时用的是同一个环境。Windows 上同时装多个 Python 版本时尤其容易出这种问题报错信息看起来像包没装实际是解释器选错了。3.2 模型文件放哪里pose.prototxt 和 pose.caffemodel 的准备OpenCV DNN 加载 OpenPose 只需要两个文件一个描述网络结构的 prototxt一个保存训练权重的 caffemodel。prototxt 在 OpenPose 开源仓库的 models/pose/coco 目录下可以找到caffemodel 来自 CMU 官方的 pose_iter_440000 模型发布页。下载后的目录结构建议这样组织openpose_cpu/ ├── models/ │ ├── pose.prototxt │ └── pose_iter_440000.caffemodel └── run.py下载完先看文件大小是否正常caffemodel 是几百 MB 的文件如果只有几十 KB 那就是下载被中断或者拿到了网页文件。prototxt 要用文本编辑器打开确认里面有 input_dim 字段并且网络层名称和 caffemodel 对应。有一个很容易踩的坑是 prototxt 和 caffemodel 不配套网上能找到各种修改版结构混用会导致 readNetFromCaffe 不报错但 forward 输出形状异常。验证方式就是直接读一次net cv2.dnn.readNetFromCaffe(models/pose.prototxt, models/pose_iter_440000.caffemodel) print(net)这一步不报错才继续往下走。另外路径里尽量不要有中文和空格readNetFromCaffe 在部分系统上对中文路径支持不好报错时很难想到是这个原因。3.3 先跑通单张图片最小关键点检测脚本单张图片跑通是整条链路的里程碑后面所有摄像头逻辑都复用这一段。下面是完整的最小脚本import cv2 import numpy as np # 加载模型 proto models/pose.prototxt weights models/pose_iter_440000.caffemodel net cv2.dnn.readNetFromCaffe(proto, weights) net.setPreferableBackend(cv2.dnn.DNN_BACKEND_OPENCV) net.setPreferableTarget(cv2.dnn.DNN_TARGET_CPU) # 读图并构建 blob frame cv2.imread(person.jpg) h, w frame.shape[:2] blob cv2.dnn.blobFromImage(frame, 1.0, (368, 368), (127.5, 127.5, 127.5), swapRBFalse, cropFalse) net.setInput(blob) outs net.forward() # outs 形状(1, 18, 46, 46)46 是 368 的 1/8 num_points outs.shape[1] points [] for i in range(num_points): heatmap outs[0, i] _, conf, _, pt cv2.minMaxLoc(heatmap) if conf 0.1: x int(pt[0] * w / heatmap.shape[1]) y int(pt[1] * h / heatmap.shape[0]) points.append((x, y, conf)) cv2.circle(frame, (x, y), 4, (0, 255, 0), -1) cv2.imwrite(out.jpg, frame)逻辑说明blobFromImage 的第二个参数 1.0 表示不对像素做缩放第三个参数 (368, 368) 是网络输入尺寸mean 传 (127.5, 127.5, 127.5) 是 OpenPose 训练时用的标准化方式千万不要用 ImageNet 那套均值否则热图响应值会整体偏低。swapRBFalse 是因为 Caffe 模型按 BGR 顺序训练cropFalse 表示图片直接拉伸到 368而不是中心裁剪。forward 之后 outs 的形状是 (1, 18, 46, 46)46 是输入尺寸 368 经过网络步长 8 下采样得到的。每个通道是一张关键点热图cv2.minMaxLoc 找出热图里响应最强的位置和置信度 conf。坐标缩放要用原图尺寸除以热图尺寸即 w/46 和 h/46不能直接乘固定倍数因为原图到 368 是等比拉伸但不同帧的宽高比不同固定倍数会偏。阈值 0.1 是经验值。近距离单人场景建议提到 0.3 减少乱跳远景小人保持 0.1 仍然可能漏检。如果所有关键点都检不出来先检查 mean 和 swapRB 设置再看输入图片是不是被过度压缩。3.4 把摄像头和视频文件统一成一个数据源视频文件和摄像头在 OpenCV 里是同一个接口只需改 VideoCapture 的入参。这块代码可以直接套用source 0 # 0 是默认摄像头改成 test.mp4 就是视频文件 cap cv2.VideoCapture(source, cv2.CAP_DSHOW) cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720) cap.set(cv2.CAP_PROP_FPS, 30) while True: ok, frame cap.read() if not ok: break # 复用 3.3 的模型 forward 逻辑 points detect_pose_points(net, frame) # 后续接形态指标计算 cv2.imshow(pose, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release()代码说明Windows 下第二个参数传 cv2.CAP_DSHOW 可以绕过系统默认的 MSMF 后端解决一部分摄像头打不开或延迟高的问题。Linux 下不用传这个参数V4L2 是默认后端。source 传 0 是默认摄像头插了多个 USB 摄像头时可以试 1、2、3。设置分辨率时要注意很多 UVC 摄像头只认固定档位设置 1280x720 后最好读回来确认print(cap.get(cv2.CAP_PROP_FRAME_WIDTH))如果设置不生效画面会以默认档位输出。还有局域网 RTSP 拉流中断的问题摄像头地址突然断流时 cap.read() 会持续返回 False常见做法是轮询 cap.isOpened()检测到断开就重新创建 VideoCapture同时设置 CAP_PROP_OPEN_TIMEOUT_MSEC 和 CAP_PROP_READ_TIMEOUT_MSEC 两个超时参数避免拉流卡死。这个坑在接网络摄像头时几乎一定会遇到提前写成重连逻辑能省很多事。4. 人体形态算法识别实战跌倒判定和动作计数怎么做4.1 关键点的几何计算函数夹角与重心拿到关键点之后首要任务是封装几何工具函数。下面是两个最常用的计算函数import math import numpy as np def calc_angle(a, b, c): 关键点 a-b-c返回 b 点处的张角度 v1 (a[0] - b[0], a[1] - b[1]) v2 (c[0] - b[0], c[1] - b[1]) dot v1[0] * v2[0] v1[1] * v2[1] cross abs(v1[0] * v2[1] - v1[1] * v2[0]) return math.degrees(math.atan2(cross, dot)) def center_of(points, indexes): 多个可见关键点取平均中心返回 (x, y) 或 None pts [points[i] for i in indexes if i len(points) and points[i][2] 0.1] if not pts: return None return (int(np.mean([p[0] for p in pts])), int(np.mean([p[1] for p in pts])))calc_angle 用 atan2(cross, dot) 而不是直接用 acos(dot / norm)原因是 dot 在浮点计算下可能超出 [-1, 1] 范围导致 acos 报 nan。atan2 对接近 0 度和 180 度都更安全。center_of 里的置信度过滤 0.1 跟前面热图阈值保持一致如果某些关键点置信度低直接丢弃而不是参与平均否则会把中心点拉偏。这两个函数是后面所有形态指标的地基。只要 points 数组结构统一为 (x, y, conf)无论换 BODY_25 还是换其他姿态模型上层计算逻辑都可以复用。我现在做姿态相关项目第一步永远是先把这两个函数放进一个 geom_utils.py后面所有业务逻辑都从这里面调。4.2 跌倒检测判定阈值组合与生效条件跌倒判定不能靠单帧必须用时间窗口。单帧躯干倾角大可能是弯腰捡东西也可能是伸懒腰。所以需要组合指标加上持续帧数确认。下面是一段可运行的判定核心from collections import deque def trunk_angle(points): 躯干与竖直方向的夹角 mid_shoulder center_of(points, [2, 5]) # 左右肩 mid_hip center_of(points, [8, 11]) # 左右髋 if mid_shoulder is None or mid_hip is None: return None dx mid_shoulder[0] - mid_hip[0] dy mid_shoulder[1] - mid_hip[1] norm math.hypot(dx, dy) 1e-6 # 与竖直方向 (0, 1) 的夹角 return math.degrees(math.acos(max(-1.0, min(1.0, dy / norm)))) def bbox_ratio(points): 关键点包围盒宽高比 xs [p[0] for p in points if p[2] 0.1] ys [p[1] for p in points if p[2] 0.1] if len(xs) 3: return None return (max(xs) - min(xs)) / (max(ys) - min(ys) 1e-6)判定逻辑建议这样组织维护一个最近 5 帧的 deque存储每一帧的 trunk_angle、bbox_ratio 和身体中心点。身体中心点可以用肩髋四个点的均值。当连续 3 帧中至少 2 帧满足下面三个条件时进入“可疑跌倒”状态再持续 1 秒确认报警参数建议值说明躯干倾角大于 45 度躯干接近水平包围盒宽高比大于 1.0身体宽度超过高度中心点下移速度大于 20 像素/帧720p 下的快速倒地速度三个条件里倾角负责描述“身体歪了”宽高比负责描述“已经接近水平”速度负责描述“快速发生”。单独用任何一个都会产生大量误报。比如弯腰捡东西时倾角可能大于 45 度但宽高比不会反转人躺在沙发上睡觉时宽高比大于 1但速度接近 0。组合判断加时间确认误报率能压到可接受范围。阈值不是固定的要根据摄像头安装高度和视角调整。俯视摄像头和水平视角的摄像头同一个动作的躯干倾角差异很大。我一般先把阈值调宽松录一段包含正常活动的视频离线跑统计误报后再逐步收紧。4.3 动作计数和形态识别的通用套路状态机跌倒本质上是一个状态变化过程站立、正在倾倒、倒地。动作计数比如深蹲也是一样的状态机套路用一维指标的时间曲线切分状态。state STAND count 0 stand_y None for points in frame_stream: hip center_of(points, [8, 11]) if hip is None: continue hip_y hip[1] if stand_y is None: stand_y hip_y if state STAND and hip_y stand_y 60: state DOWN elif state DOWN and hip_y stand_y 10: state UP count 1 stand_y hip_y这里的逻辑是用髋部高度做相位。深蹲时髋部下降超过 60 像素进入 DOWN 状态回到接近站立基准时认为完成一次并计数。stand_y 会动态更新适应人在画面里缓慢移动造成的高度漂移。相对距离而不是绝对像素能容忍人离摄像头远近不同。这个套路可以平移到很多动作计数需求上比如引体向上用下巴高度做相位走路步数用髋部垂直振荡做相位。形态识别的现场需求大部分用状态机就能解决不需要一上来就上 LSTM。时间序列模型在数据量不够时反而容易过拟合状态机至少你能解释每一帧的判定依据。5. 摄像头视频识别实操避坑从打不开摄像头到关键点乱飘5.1 摄像头打不开或画面花屏现象cv2.VideoCapture(0) 返回 False或者画面出现撕裂、偏色、只有一半能显示。原因设备索引不对是最大的可能。笔记本自带摄像头、外接 USB 摄像头、采集卡各自占用一个索引你以为的 0 不一定是摄像头。另一个是 Windows 默认的 MSMF 后端和部分 UVC 设备的兼容性差表现为能打开但帧率为 0。此外多个 USB 摄像头同时插在同一个 USB 2.0 口上带宽会被瓜分出现画面撕裂。解决先写一个枚举脚本循环打开 0 到 9 的索引打印每个索引的 isOpened 结果和分辨率。Windows 下创建 VideoCapture 时显式传 cv2.CAP_DSHOW。多摄像头需求优先用 USB 3.0 接口或者走采集卡并且在代码里把分辨率统一降到 720p带宽压力会明显减小。提示摄像头的“花屏”问题先换一根短一点的 USB 线试试线材质量差会导致数据错误这类问题经常和代码无关。5.2 关键点乱飘热图阈值和分辨率的关系现象人站着不动手腕和脚踝关键点却在身体附近抖动置信度数字忽高忽低画出来的骨架像得了帕金森。原因一是输入分辨率不够。网络输入是 368远景小人在缩放后可能只占十几个像素关键点热图根本分不清关节还是背景。二是热图阈值设得太低背景噪声被当成关键点。三是运动模糊摄像头帧率低时快速挥手边缘糊成一片热图峰值位置不稳定。解决优先提高输入分辨率从 368 提到 432关键点稳定性有明显改善代价是推理时间增加。其次把阈值从 0.1 提到 0.3宁可漏检也不乱检漏检可以通过时间平滑补回来。如果画面里人比较小先做人形检测框把 ROI 裁剪出来放大再送 OpenPose比直接全图检测效果好得多。这是我在 opencv 图像处理项目里最常用的优化手段。5.3 CPU 掉帧严重先降分辨率再降什么现象720p 摄像头接入后处理帧率不到 5 帧画面看起来像 PPT关键点动画一顿一顿。原因OpenPose 的前向推理在 CPU 上是主要开销。输入图 720p 要缩放成 368网络本身有 18 层 VGG 风格的卷积每帧都要算 18 张热图单帧推理在普通桌面 CPU 上要几百毫秒。解决第一个动作是降输入尺寸368 降到 256帧率能提升接近一倍形态判定用的倾角、宽高比对分辨率不敏感损失不大。第二个动作是跳帧每处理一帧就跳 2 到 3 帧配合上一章的时间窗口判定效果比强行追求每帧都算更平滑。第三个动作是换带 CUDA 的 OpenCV 构建打开 DNN_BACKEND_CUDA但这需要显卡和配套的预编译包。不要急着上多线程OpenCV DNN 的 forward 在多线程上如果没有 OpenMP 支持收益很有限还容易把帧率搞得更乱。5.4 多人目标张冠李戴关键点归属错乱现象两个人交叉走过骨架突然左右互换A 的左手接到了 B 的肩膀上形态指标瞬间跳变。原因OpenCV DNN 加载 OpenPose 的 caffemodel 时拿到的是热图和 PAF 的原始输出多人骨架的分组匹配没有官方 C 后处理那么完整。当两个人距离近、肢体交错时关键点归属关系很容易错。解决给每个人做中心关联。用上一帧的颈部和髋部中心预测当前帧位置然后和当前帧检测到的人做最近邻匹配贪心匹配就能在大多数场景下维持 ID 稳定。更稳一点是改用 BODY_25 模型利用中髋点作为人体根节点归属关系比 COCO 模型更可靠。如果业务场景允许最省事的办法是限制单人场景用一个人体检测框把最大的人框出来其他人直接忽略。很多室内监控场景本来就是单人活动没必要为不存在的多人需求增加复杂度。6. 效果验证与进阶时间平滑、CUDA 加速和回归测试6.1 用同一段录制视频做回归测试形态识别的效果不能靠盯着画面看感觉要靠漏报率和误报率说话。我现在每个项目都会保留一段 regress.mp4里面固定包含站立、走路、弯腰捡东西、坐下、倒地这几个动作每次改完阈值就重跑同一段视频把结果逐帧写入 JSON{ frame_id: 102, trunk_angle: 48.3, bbox_ratio: 1.1, falling: true }跑完用播放器逐帧对照统计该报警没报的有几帧不该报警报了的又有几帧。改阈值后重新跑一遍数值变差了就说明改法有问题。没有这个回归流程调参基本靠玄学今天改好了明天又坏。6.2 关键点时间平滑与 CUDA 加速关键点乱飘的最后一层防线是时间平滑。最简单的是 EMA 指数平滑新值 alpha * 观测值 (1 - alpha) * 旧值。alpha 取 0.3 到 0.5 之间太小跟随慢太大会让抖动直接透传。对每个关键点的 x 和 y 分别平滑骨架动画立刻稳下来。更正式的做法是用 OpenCV 自带的 cv2.KalmanFilter状态设为四维 (x, y, dx, dy)测量噪声和过程噪声需要现场调。多数场景 EMA 够用卡尔曼的优势是能预测下一帧位置对多人做目标关联时有额外价值。加速方面先确认当前 OpenCV 是否带 CUDA 支持print(cv2.buildInformation())如果编译选项里没有 CUDA可以找带 CUDA 的预编译 wheel 或自己用 CMake 编译然后再设置net.setPreferableBackend(cv2.dnn.DNN_BACKEND_CUDA) net.setPreferableTarget(cv2.dnn.DNN_TARGET_CUDA)显存占用方面4GB 的显卡跑 368 输入没有问题再往上调输入尺寸就会吃紧。没有显卡也不要强求CPU 方案把分辨率降到 256 加跳帧在单人或稀疏人群场景下也能支撑形态识别这类慢业务。这套方案我前后用在两个项目上一个是老人活动状态监测一个是康复训练动作计数。最大的体会是 OpenPose 决定的是天花板你能从它那里拿到关键点坐标而形态识别的下限全部在指标定义、阈值组合、时间平滑这三件事上。先跑通单张图再做时间域处理最后才接摄像头实时流。不要一上来就对着摄像头调参数那样分不清是成像问题还是算法问题。希望帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联
返回资讯列表 →