
简介一份面向老年人行为监护的深度学习姿态检测项目基于OpenPose实现站、坐、躺及摔倒识别适合需要落地跌倒预警或行为分析的开发者和研究人员。包内包含完整Python源码、操作说明、requirements环境依赖清单并提供数据集与预训练模型。项目内置两个可运行入口demo_my.py采用线性算法流程webcam_demo_my.py为每个算法维护独立队列二者均可直接运行输出含人体框、关键点和状态识别的JSON文件便于后续接入告警或统计模块。资源共580个文件以Python脚本、JPEG图片、Markdown文档为主还包含C源码、YOLO相关cfg配置与权重、模型检查点等压缩包整体约75.5MB目录结构清晰适合快速定位源码、样例与文档。目前已有1316人学习适合具备一定深度学习基础、希望基于OpenPose快速构建姿态识别与摔倒检测原型的读者。1. 拿 openpose 做老人跌倒检测不是单纯标个框就完事如果你只训练一个 YOLO 检测器去框出画面里的老人遇到摔倒这种瞬时动作十有八九会漏检——人在跌落的几百毫秒里目标框的宽高比变化并不稳定单靠“框”很难区分弯腰系鞋带和真的摔倒。这个项目把 openpose 人体姿态检测和 YOLO 目标检测做了串联先用 YOLO 出人体框再用 openpose 提取骨骼关键点最后根据关键点之间的角度和位置关系判断当前状态是站、坐、躺还是摔倒。源码里同时包含数据集和训练好的模型预训练权重和推理脚本齐全对做养老院监护、居家看护这类场景的开发者来说是一个可以直接拿来改的起点。适合熟悉 Python、想用深度学习做行为识别的从业者搭完环境跑通两个 demo 后就能在摄像头前看到实时 JSON 输出。2. openpose 姿态估计的核心原理与关键点定义2.1 两分支网络关键点置信图与 PAF 亲和场openpose 最关键的创新在于它不是像 AlphaPose 那样先检测人体框再单独回归关键点而是把关键点检测和关键点之间的连接关系放到了同一个网络里。输入一张图网络输出两个分支一个分支生成每个人体关键点的置信图Confidence Map另一个分支生成部件亲和场Part Affinity FieldsPAF——PAF 是一组向量场用来编码骨骼两端关键点之间的“连接方向”和“连接强度”。为什么要 PAF因为多个人同时出现在画面里时甲的手肘和乙的肩膀可能挨得很近光靠关键点位置无法判断哪两个关键点应该连成一条骨骼。PAF 通过对向量场的积分来度量候选连接的可信度从而把同一个人身上的关键点正确配成对。在你的监控画面里哪怕两个老人距离很近PAF 也能在多人模式下切分清骨骼归属。在工程端openpose 提供了基于 TensorFlow 或 PyTorch 的复现版本。多数项目用的是 COCO 标注格式输出 18 个关键点或者 25 个关键点。本项目源码里包含42_model.ckpt这类中间权重说明作者把训练好的模型参数一并打包了推理时不需要自己重新训练。2.2 关键点序号与物理含义以 COCO 的 18 关键点为例这个项目后续判断“站、坐、躺、摔倒”都要依赖这些序号。下面这张表是你在读代码时需要对照的第一张表关键点序号名称含义0nose鼻子1neck颈部2right_shoulder右肩3right_elbow右肘4right_wrist右腕5left_shoulder左肩6left_elbow左肘7left_wrist左腕8right_hip右髋9right_knee右膝10right_ankle右踝11left_hip左髋12left_knee左膝13left_ankle左踝14right_eye右眼15left_eye左眼16right_ear右耳17left_ear左耳要注意openpose 的颈部neck通常是根据两个肩膀的中点近似出来的不是单独检测的关键点。在代码里读取pose[1]拿到的就是颈部中心用它和髋部中心连线可以估算躯干倾角这对判断“站着弯腰倒向地面”非常有用。2.3 为什么还要串联 YOLO 而不是只用 openposeopenpose 本身也输出人体框但这个框是后处理阶段从关键点最小外接矩形生成的速度慢而且对于完全躺平的人关键点可能在图像边缘被截断框的稳定性不如 YOLO。项目源码里出现了yolov3-spp.cfg、yolo-voc.cfg、yolov3.cfg等配置文件说明作者用 YOLO 前置提取人体检测框将框区域裁剪后送入 openpose 网络。import cv2 import numpy as np # 假设 yolo_net 已加载 yolov3.cfg 和对应权重 # pose_net 是 openpose 推理网络 def detect_frame(frame, yolo_net, pose_net): boxes yolo_net.detect(frame, conf_threshold0.5) results [] for bx, by, bw, bh in boxes: person_crop frame[by:bybh, bx:bxbw] # 在裁剪区域内推理关键点 keypoints pose_net.infer(person_crop) results.append({ box: [bx, by, bw, bh], keypoints: keypoints.tolist() }) return results这段代码的逻辑是YOLO 负责定位人openpose 负责在每一个人的局部区域里找关键点。这样做的好处是避免了 openpose 在整张大图上计算 PAF 时被远处的物体干扰代价是单人框的裁剪误差会直接影响关键点精度。所以源码里对 YOLO 的 confidence 阈值设得比较保守0.5 左右宁可多框一个误检也不允许漏掉正在跌落的老人。3. 环境搭建与项目文件结构3.1 Anaconda PyCharm 下的 Python 环境配置项目说明里特意强调“建议先安装 Anaconda 和 PyCharm”说明这个源码不是一个纯现成 exe 包需要自己还原依赖。我建议你在 Anaconda 里创建一个独立环境避免把本机的 Python 环境改乱。conda create -n pose_monitor python3.7 conda activate pose_monitor pip install -r requirements.txt选择 Python 3.7 是考虑到 TensorFlow 1.x 或其他老版本框架的兼容性。如果requirements.txt里有tensorflow-gpu1.15这类锁定版本在 Python 3.8 以上会直接安装失败。3.2 requirements.txt 依赖清单的拆解项目里的requirements.txt是核心依赖文件包括但不限于 opencv-python、numpy、tensorflow 或 pytorch、yacs、scipy 等。实际运行前建议先逐行确认每个库是否和你本地的 CUDA 版本匹配。依赖库用途安装时的常见坑opencv-python图像读取、绘制版本过高对cv2.dnn有行为差异tensorflow / pytorchopenpose 网络推理TF 1.x 和 2.x API 差异大numpy数组运算、关键点坐标处理与 tensorflow 版本需配套scipy信号平滑、插值在 Webcam 实时场景中可选json输出状态结果标准库无需安装安装完后跑一个快速自检脚本确认 openpose 模型能正常加载而不是等运行 demo 后才发现模型路径配错。import tensorflow as tf import cv2 print(TF version:, tf.__version__) print(OpenCV version:, cv2.__version__) # 加载 42_model.ckpt 对应权重确认模型图结构匹配 saver tf.train.import_meta_graph(model/42_model.ckpt.meta) with tf.Session() as sess: saver.restore(sess, model/42_model.ckpt) print(Model restored successfully)这里import_meta_graph是 TensorFlow 1.x 的常见做法。如果报错找不到 meta 文件说明项目里保存的是 checkpoint 而不是冻结的 pb 文件需要用源码提供的加载逻辑读取。3.3 项目文件结构源码包里除了两个 demo 脚本还有一堆.cfg和.cpp文件。deep_matching.cpp、conv.cpp这类文件是 openpose 官方分支里做 dense optical flow 或者自定义算子用的普通用户不需要编译它们只把它们看作参考实现即可。一个常见的目录组织方式是这样的. ├── demo_my.py ├── webcam_demo_my.py ├── requirements.txt ├── models/ │ └── 42_model.ckpt ├── configs/ │ ├── yolov3.cfg │ └── yolov3-spp.cfg └── datasets/ └── pose_samples/如果你发现权重的文件名不是42_model.ckpt而是graph_opt.pb也没关系只需要在加载代码里对应调整。项目源码的价值在于 demo 脚本已经把关键点检查和状态输出串联好了你不用自己写 NMS 和 PAF 解析。4. 两个 demo 的工程实现与运行逻辑4.1 demo_my.py线性流水线式推理demo_my.py是作者写的第一个版本逻辑是“所有算法线性预算计算完第一个模块计算第二个模块”。直观理解就是读一帧图像先跑 YOLO 检测得到人体框然后把每个人体框分别送入 openpose 提取关键点最后用关键点到状态判定函数里判断当前姿态。整个过程是同步串行的。# demo_my.py 核心流程简化 import json import cv2 def run_pipeline(cap): while True: ret, frame cap.read() if not ret: break # 阶段1: YOLO 检测人体框 persons yolo_detect(frame) for idx, person in enumerate(persons): # 阶段2: openpose 关键点检测 keypoints pose_infer(person.crop) # 阶段3: 状态判定 state judge_posture(keypoints) # 阶段4: 输出 JSON out { person_id: idx, box: person.box, keypoints: keypoints, state: state } with open(result.json, a) as f: f.write(json.dumps(out) \n) cv2.imshow(pose monitor, frame) if cv2.waitKey(1) 0xFF 27: break这段代码最值得注意的地方是persons的循环。如果画面里同时出现三个老人就会依次在同一个线程里完成三个人的姿态推理总时延是三个人的推理时延相加。在离线视频分析场景下问题不大但在实时摄像头监控里帧率会掉到个位数。JSON 输出采用追加写入的方式方便后续分析跌倒事件的时间线。4.2 webcam_demo_my.py每个算法开一个队列第二个版本webcam_demo_my.py引入了多队列模式。作者说的“每个算法开一个队列”本质上是生产者消费者模型采集线程把原始帧放入frame_queueYOLO 检测线程从frame_queue取帧把检测结果放入person_queueopenpose 推理线程再从person_queue取带框图像计算关键点后把结果放入state_queue。每个队列可以设置不同的大小起到缓冲作用。import queue import threading frame_queue queue.Queue(maxsize8) person_queue queue.Queue(maxsize16) def yolo_worker(): while True: frame frame_queue.get() persons yolo_detect(frame) # 耗时操作 person_queue.put((frame, persons)) def pose_worker(): while True: frame, persons person_queue.get() for p in persons: kp pose_infer(p.crop) p.keypoints kp # 供主线程绘制或输出 state_queue.put((frame, persons))线程数量一旦超过两个就要注意 GIL 对 CPU 推理的影响。如果你的 openpose 用的是 TensorFlow 后端session.run会释放 GIL所以多线程能真正提速。如果是纯 PyTorch CPU 推理GIL 会锁得比较死这时候更适合用多进程而不是多线程。队列的长度设置是个平衡frame_queue太长会导致缓存帧过多延迟增大太短会阻塞采集线程丢失实时性。建议把长度设为 4~16并且可以用queue.get(timeout1)防止线程死等。4.3 关键点 JSON 输出结构与后续应用两个 demo 最终都会保存 JSON 文件。根据资源说明JSON 里包括人体的框、关键点和当前状态。一个合理的 JSON 结构如下{ frame_id: 120, timestamp: 1629000000.123, persons: [ { id: 0, box: [320, 88, 156, 360], keypoints: [ {id: 0, x: 332, y: 90, score: 0.98}, {id: 1, x: 350, y: 180, score: 0.97} ], state: standing } ] }这里面的score是每个关键点的置信度在 Keras 实现里一般由最后一个卷积层的 softmax 输出得到。做跌倒分析时不要直接信任低置信度的关键点建议过滤掉score 0.5的关键点避免状态判定被噪声带偏。5. 站、坐、躺与摔倒的判定规则和调优5.1 基于关键点角度和比例的状态判定拿到关键点后怎么判断站坐躺常见做法是计算三个指标躯干倾角、髋膝角度、关键点包围盒的高宽比。import math def angle_between(p1, p2, p3): # 计算向量 p1-p2 和 p3-p2 的夹角 v1 (p1[0] - p2[0], p1[1] - p2[1]) v2 (p3[0] - p2[0], p3[1] - p2[1]) dot v1[0] * v2[0] v1[1] * v2[1] norm1 math.hypot(v1[0], v1[1]) norm2 math.hypot(v2[0], v2[1]) cos_val max(-1.0, min(1.0, dot / (norm1 * norm2))) return math.degrees(math.acos(cos_val)) def judge_posture(kps): neck kps[1] hip_mid ((kps[8][0] kps[11][0]) / 2, (kps[8][1] kps[11][1]) / 2) ankle_mid ((kps[10][0] kps[13][0]) / 2, (kps[10][1] kps[13][1]) / 2) torso_angle angle_between(ankle_mid, hip_mid, neck) bbox_h max(kps[:, 1]) - min(kps[:, 1]) bbox_w max(kps[:, 0]) - min(kps[:, 0]) ratio bbox_h / max(bbox_w, 1e-5) if torso_angle 30 and ratio 1.2: return standing elif torso_angle 60 and ratio 0.8: return lying elif torso_angle 45 and ratio 0.9: return sitting else: return transition这里torso_angle是脚踝中心到髋部的连线方向再和髋部到颈部连线的夹角。人站立时这个角度接近 180 度但我们计算的是两向量的夹角所以代码里小于 30 度视为直立。躺着时躯干接近水平角度会超过 60 度。阈值需要根据摄像头安装位置微调斜朝下的摄像头看到的角度会比水平视角更小。5.2 摔倒检测的关键特征中心点垂直速度跌倒与“躺下”不同跌倒是在短暂时间内姿态从站立快速变成躺倒伴随颈部和髋部中心点的垂直速度骤增。只靠单帧是无法可靠判断摔倒的项目源码的 demo 里如果只输出当前状态那多半是利用了连续多帧的 state 变化。prev_center_y None fall_frames 0 def check_fall(kps, frame_time): global prev_center_y, fall_frames neck_y kps[1][1] if prev_center_y is not None: vel (neck_y - prev_center_y) / max(frame_time, 0.01) if vel 150: # 像素/秒根据实际分辨率调整 fall_frames 1 else: fall_frames max(0, fall_frames - 1) prev_center_y neck_y return fall_frames 5这个逻辑里设置了连续 5 帧满足条件才报警能有效避免弯腰捡东西造成的瞬时误检。但在实际部署中150这个速度阈值需要根据摄像头画面中人的像素尺寸换算。离摄像头越远人体在画面中越小跌倒时颈部位移的像素就越少阈值就要调低。更稳妥的方式是先估算人体框高度用“颈部位移 / 人体框高度”作为归一化速度。5.3 误报和漏报的调优方向我在实际跑这类项目时最头疼的不是模型精度而是状态判定规则在不同房间、不同机位下表现差异极大。你可以按下面这个顺序排查确认 openpose 的后处理参数upsample_ratio默认是 0.5如果检测到关键点特别稀疏可以调到 1.0代价是推理时间增加约 30%。调整keypoint_threshold过滤低置信度关键点。如果一个人躺在暗处关键点置信度普遍低于 0.3这时候用阈值 0.5 会直接把整个人丢掉。对 JSON 输出做平滑。跌倒检测最忌一帧一个状态建议用滑动窗口对状态序列做投票取窗口内占比最高的状态作为最终输出。注意 YOLO 框的抖动。当人快速倒地时YOLO 框尺寸剧烈变化裁剪区域可能只包含一部分身体导致 openpose 拿到不完整信息。可以在 YOLO 检测后把框向外扩展 10%~20% 再裁剪保证四肢完整。# 一个实用技巧用 ffmpeg 从视频流抽帧测试避免摄像头环境干扰 ffmpeg -i input.mp4 -vf fps10 -q:v 2 frames/%06d.jpg把抽出的帧按序列传入 demo_my.py输出 JSON 后逐帧对比状态画出状态变化曲线。通常会发现“站→坐”和“站→蹲”的过渡状态被误标成“躺”这时候只需要把torso_angle的判定阈值提高 5 度或者引入膝盖与髋部的高度差来区分坐与蹲。最终调优不是让模型更聪明而是让规则更贴合你的摄像头视角和老人身高分布。本文还有配套的精品资源点击获取