尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

基于OpenPose的人体姿态识别预警系统开发实战

基于OpenPose的人体姿态识别预警系统开发实战 简介基于OpenPose卷积神经网络的人体姿态识别及预警系统是一套完整的毕业设计项目面向计算机、人工智能、通信工程等专业的在校学生、教师及企业开发者适用于毕业设计、课程设计、项目立项演示等场景。这套项目重点对OpenPose检测模块进行轻量化改进通过优化卷积结构在保持识别精度的同时提升运算速度实现人体关键点实时检测与姿态预警是理解姿态识别算法并落地工程实践的良好实例。压缩包共147个文件既有62个Python源码文件、30张png/jpg效果截图和6个mp4演示视频也包含UI界面、XML配置、CSV/DB数据库、模型权重pth/pt以及Dockerfile部署配置等总大小约232.08MB目录结构清晰并附有Markdown说明文档。代码已经测试运行成功答辩评审平均分达到96分搭配文档、截图和视频可帮助快速完成环境搭建与复现也可以基于源码进一步做二次开发如扩展其他姿态预警逻辑。目前已有275人学习下载尤其适合需要完整掌握轻量化OpenPose系统设计思路、并据此完成毕业设计或课程设计的读者。1. 为什么毕业设计选 OpenPose 人体姿态识别预警系统人体姿态识别是计算机视觉里“看懂人”的起点。很多毕业设计会把任务简化成“动作分类”但 OpenPose 代表的是另一套思路先用卷积神经网络回归出人体关键点坐标再基于关键点序列判断跌倒、久坐等异常。这套系统的难点不在于关键点检测本身因为预训练模型已经做得足够好难点在于把检测结果变成稳定、可解释、能写进数据库的报警事件。比如跌倒动作在侧视角下躯干倾斜角可能只有 50 度在正视角下却接近 80 度直接设定固定阈值必然产生大量误报。下面这套方案按一个可复现的毕业设计结构展开先从卷积神经网络原理讲清 OpenPose 为什么能输出 18 个关键点再到如何把关键点帧写入 MySQL 数据库最后给出 Python 实现的报警规则引擎和阈值调优技巧。读者不管是想复现源码还是接手这类老项目都能找到可以直接抄的参数和代码。2. OpenPose 的卷积神经网络原理与姿态识别选型2.1 关键点热图与部件亲和场为什么单靠 CNN 不够OpenPose 的骨干网络通常采用 VGG-19 前 10 层做特征提取后续不是简单的全连接分类而是分成两个并行分支一个分支预测关键点位置的热图Heatmap另一个分支预测部件亲和场PAF即向量场用于表示关键点之间“谁和谁相连、方向如何”。热图只能告诉我们“左肩在哪里”无法区分这个左肩属于 A 还是 BPAF 通过编码肢体向量让关键点匹配变成二分图最大权匹配问题从而解决多人纠缠。从工程视角看PAF 的存在让 OpenPose 在多人场景下的召回率明显优于当时的单阶段方法。毕业设计如果只处理单人镜头可以手动关闭多人大匹配逻辑推理速度能快 30% 左右。但理解 PAF 仍然重要因为后面预警规则的很多特征比如躯干角度本质上是关键点之间的相对向量而不是原始坐标。2.2 网络输出张量与关键点坐标的对应关系OpenPose 的典型输出以 COCO 模型为例是一个形状为(batch, 57, h, w)的卷积特征图其中前 18 层是 18 个关键点的热图接下来 19 层是 PAF 的 x 分量再接下来 19 层是 y 分量。很多同学直接拿特征图做 argmax忽略了下采样导致坐标偏移最终画出的骨架会悬空。常见做法是在 argmax 之后乘以下采样倍率再放大回原图尺寸。下面是观察输出张量的最小代码import numpy as np def parse_heatmap(output, body_scale8.0, threshold0.1): # output: 模型输出shape 为 (57, H, W)这里以单人推理为例 heatmaps output[:18] # 前 18 通道是关键点热图 keypoints [] for idx, hm in enumerate(heatmaps): h, w np.unravel_index(np.argmax(hm), hm.shape) score hm[h, w] if score threshold: keypoints.append((0, 0, 0.0)) else: keypoints.append((int(w * body_scale), int(h * body_scale), float(score))) return keypoints这段代码先切出 18 通道热图对每个通道找最大值位置body_scale是网络相对于原图的缩放系数OpenPose 官方推理脚本里通常取 8threshold用于过滤置信度过低的关键点默认 0.1 在侧身场景下容易丢点建议下调到 0.05但要接受一点噪声。2.3 OpenPose 与轻量级方案的选择对比指标OpenPoseMediaPipe PoseMoveNet关键点数量18/253317多人支持是单人为主单人/多人依赖模型大小200MB 级别5MB 级别10MB 级别CPU 实时性差需 GPU较好较好毕业设计适配度高论文有理论深度高复现快中如果导师只看演示效果MediaPipe 更省事但题目明确写了 OpenPose 和卷积神经网络说明答辩重点在网络结构和预警机制上。那就不要换成轻量模型否则题目和实现对不上。我的建议是保留 OpenPose只在预处理阶段用 OpenCV 的resize控制输入尺度比如单人场景用368x368多人场景用656x368这也是官方模型常见的输入尺寸。3. 人体姿态识别预警系统的数据库设计与关键点存储3.1 预警系统需要保存哪些数据姿态识别产生的是高频序列数据每秒约 1030 帧。预警逻辑只关心“异常发生前几秒到确认报警”这一段。所以数据库设计不能把所有关键点帧都长期保存否则一张 1 小时的视频就能产生十几万行记录。常见做法是用户表存身份信息姿态帧表存关键点序列快照报警表存触发事件和当时的上下文。帧表的设计重点是减少字段数。不要为每个关键点建一列那样 18 个关键点就要 54 列还不算 PAF而是用 JSON 文本存储关键点坐标或者用BLOB存二进制数组。MySQL 的JSON类型在高版本里支持良好可以配合虚拟列做简单过滤。报警表单独记录时间、类型、置信度和帧ID便于答辩时按时间轴回溯。3.2 MySQL 建表语句与字段说明下面是一组可用的建表语句字符集设为utf8mb4避免中文备注乱码CREATE DATABASE IF NOT EXISTS pose_warning DEFAULT CHARSET utf8mb4; USE pose_warning; CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(32) NOT NULL, age TINYINT, role TINYINT COMMENT 0:普通用户, 1:老人, 2:儿童 ) ENGINEInnoDB; CREATE TABLE pose_frame ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, capture_time DATETIME(3) NOT NULL, keypoints_json JSON NOT NULL, keypoints_version VARCHAR(8) DEFAULT coco18, frame_image_url VARCHAR(128) COMMENT 关键帧截图路径, INDEX idx_user_time (user_id, capture_time) ) ENGINEInnoDB; CREATE TABLE warning_record ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, warning_type TINYINT COMMENT 1:跌倒, 2:久坐, 3:姿态异常, current_score FLOAT, trigger_frame_id BIGINT, warning_time DATETIME(3) NOT NULL, is_handled TINYINT DEFAULT 0, INDEX idx_user_time (user_id, warning_time) ) ENGINEInnoDB;pose_frame表通过keypoints_json保存完整骨架字段keypoints_version标记是 COCO 18 点还是 BODY_25防止模型升级后数据无法解析。warning_record表里的trigger_frame_id关联帧表方便编码做数据增删改查时直接定位异常发生点。3.3 Python 写入数据库与常见坑用pymysql写一个批量插入函数比逐条插入快一个量级import pymysql import json def batch_insert_frames(conn, user_id, frames): # frames: list of (capture_time, keypoints_list) sql (INSERT INTO pose_frame (user_id, capture_time, keypoints_json) VALUES (%s, %s, %s)) values [ (user_id, ts, json.dumps(kps)) for ts, kps in frames ] with conn.cursor() as cur: cur.executemany(sql, values) conn.commit()executemany在 MySQL 驱动里默认只是循环执行并不是真正的批量插入但减少了 Python 与数据库之间的协议往返。真正提速要开启rewriteBatchedStatementspymysql下可以通过cursor.executemany配合client_flagMySQLdb.constants.CLIENT.MULTI_STATEMENTS处理简单项目不必过度优化。需要注意capture_time如果是字符串一定要先strptime转成 Python datetime 对象否则 MySQL 会报时间格式错误。3.4 数据清理与按需回放预警确认后可以把异常前 5 秒和确认后 2 秒的帧复制到pose_frame_backup表普通帧按天清理。这里可以用一个简单的事件调度器或者 Linux crontab 定期删除但数据库增删改查中很容易忽略索引问题删除高频写入的old数据时如果没有按capture_time建索引全表扫描会拖垮写入。所以建表时idx_user_time已经包含用户和时间清理语句要写成DELETE FROM pose_frame WHERE capture_time DATE_SUB(NOW(), INTERVAL 7 DAY) LIMIT 10000;LIMIT 10000防止一次性锁太多行配合慢查询日志观察清理任务的影响。这种方式比TRUNCATE安全得多。4. Python 实现人体姿态识别与预警规则引擎4.1 环境安装与模型文件放置毕业设计最常见的翻车点不是代码而是依赖装不上。官方 OpenPose 分支依赖 CaffeCMake 编译时间可以超过半小时。如果不想陷进编译推荐用 ONNX 版本或者 PyTorch 重实现的权重。常见做法是conda create -n pose python3.8 -y conda activate pose pip install opencv-python numpy torch torchvision onnxruntime pymysql mkdir -p models/input # 把模型文件放到 models/input/pose_model.onnx 后验证 python -c import onnxruntime; sessonnxruntime.InferenceSession(models/input/pose_model.onnx); print(sess.get_inputs()[0].shape)这里指定 Python 3.8 而不是最新版是因为torch和onnxruntime在新版 Python 上经常出现编译型依赖不匹配。onnxruntime的 CPU 推理在单人画面下约 300ms 一帧GPU 能压到 50ms 以内。如果显卡支持 CUDA安装时改成pip install onnxruntime-gpu并在代码里设置providers[CUDAExecutionProvider]。提示如果 conda 创建环境速度很慢可以改用pip install配合虚拟环境但请保证 Python 版本与模型导出时的版本一致否则 ONNX 运行时会报算子不匹配。4.2 推理循环与关键点归一化推理循环里不能只看 18 个点的绝对坐标。不同摄像头分辨率下同一动作的像素坐标差异巨大所以要先按人体框高度做归一化。下面这段代码抽取躯干关键点并计算角度import cv2, math COCO_THRESHOLD 0.2 BODY_PARTS {Nose:0, Neck:1, RShoulder:2, RElbow:3, RWrist:4, LShoulder:5, LElbow:6, LWrist:7, RHip:8, RKnee:9, RAnkle:10, LHip:11, LKnee:12, LAnkle:13, REye:14, LEye:15, REar:16, LEar:17} def angle_between(p1, p2, p3): v1 (p1[0]-p2[0], p1[1]-p2[1]) v2 (p3[0]-p2[0], p3[1]-p2[1]) len1 math.hypot(*v1); len2 math.hypot(*v2) if len1*len2 0: return 0.0 cos_a max(-1, min(1, (v1[0]*v2[0]v1[1]*v2[1])/(len1*len2))) return math.degrees(math.acos(cos_a)) def angle_vertical(p1, p2): # 从 p1 指向 p2 的向量与竖直向下方向的夹角0~180 dx p2[0] - p1[0] dy p2[1] - p1[1] length math.hypot(dx, dy) if length 0: return 0.0 cos_a dy / length return math.degrees(math.acos(max(-1, min(1, cos_a)))) def extract_angles(kps): neck kps[BODY_PARTS[Neck]] rhip kps[BODY_PARTS[RHip]] lhip kps[BODY_PARTS[LHip]] rknee kps[BODY_PARTS[RKnee]] rankle kps[BODY_PARTS[RAnkle]] lknee kps[BODY_PARTS[LKnee]] lankle kps[BODY_PARTS[LAnkle]] hip_mid ((rhip[0]lhip[0])/2, (rhip[1]lhip[1])/2) return { body_angle: angle_vertical(hip_mid, neck), right_knee: angle_between(rhip, rknee, rankle), left_knee: angle_between(lhip, lknee, lankle), }angle_between里用math.acos计算夹角注意要先将余弦值夹到[-1,1]避免浮点误差导致math domain error。body_angle是两髋中点到脖子的躯干向量与竖直方向的夹角站立时接近 0°跌倒时接近 90°膝关节角度则是大腿和小腿的夹角用于识别“姿势异常”场景。4.3 预警规则引擎阈值与状态机直接对每一帧做阈值判断会产生连续报警体验很差。我一般会建立一个三状态状态机NORMAL、TRIGGERING、ALARMED。只有连续 M 帧超过阈值才进入TRIGGERING再持续 N 帧才产生报警报警后进入冷却时间。下面是核心逻辑class WarningState: def __init__(self, confirm_frames3, cooldown_sec60): self.confirm confirm_frames self.cooldown cooldown_sec self.counter 0 self.last_alarm 0 def update(self, is_abnormal, now_ts): if is_abnormal: self.counter 1 else: self.counter max(0, self.counter-1) if self.counter self.confirm: self.counter 0 if now_ts - self.last_alarm self.cooldown: self.last_alarm now_ts return True return Falseconfirm_frames建议设为 3对应 0.15 秒帧率 20fps设置太大虽然减少误报但会错过快速跌倒。cooldown_sec用于防止同一事件重复报警老人跌倒后如果一直躺在地上不会每秒都触发一次新报警而是由人工确认后重置状态。参数推荐值调节方向body_angle 跌倒阈值60°越大越难触发侧视角可调到 50°膝关节过屈阈值150° 以下用于“姿态异常”报警confirm_frames3越大误报越少但反应越慢置信度 threshold0.05小于 0.05 会出现抖动点4.4 报警触发后的数据落库报警触发时要把当前帧写入warning_record同时把关键点 JSON 和效果截图路径写到pose_frame。这里注意 Python 类型转换np.float32不能直接写入 MySQL需要先转成 Python float否则pymysql会抛TypeError。建议在写库前统一做一次float(...)处理。预警截图保存到本地captures/文件名用user_id_时间戳.jpg。def save_warning(conn, user_id, kps, score, capture_path): now_ts datetime.now() frame_id insert_pose_frame(conn, user_id, now_ts, kps, capture_path) sql (INSERT INTO warning_record (user_id, warning_type, current_score, trigger_frame_id, warning_time) VALUES (%s, %s, %s, %s, %s)) with conn.cursor() as cur: cur.execute(sql, (user_id, 1, float(score), frame_id, now_ts)) conn.commit()这里没有贴insert_pose_frame的完整定义留给读者补全连数据库的细节。设计上的关键是trigger_frame_id与pose_frame.id的关联让答辩演示时能一秒定位到报警画面。5. 调优、验证与部署收尾技巧5.1 阈值标定用录像回放代替拍脑袋最可靠的阈值设置方法是收集 10 分钟真实场景录像逐帧标注“正常”和“异常”然后计算正常动作的body_angle分布把阈值设为正常均值加 3 倍标准差。这里提供一段标定辅助代码def calibrate_threshold(frame_gen, label_fn): angles [] for frame in frame_gen: kps run_pose(frame) ang extract_angles(kps)[body_angle] if label_fn(frame) normal: angles.append(ang) mean sum(angles)/len(angles) var sum((a-mean)**2 for a in angles)/len(angles) return mean 3*(var**0.5)label_fn可以是人工按空格标记的简化版本。实际运行时用 OpenCV 的waitKey读取按键将当前帧标记为 normal 或 abnormal最后得到与场景强相关的阈值而不是抄论文里的固定值。5.2 效果截图的自动化生成预警报警后用 OpenCV 把关键点和骨架画到原图上叠加时间戳与报警类型然后保存为 JPEG。这比在答辩现场截屏更可控。绘图时注意关键点坐标需要乘回原图缩放比否则会画偏。def draw_skeleton(frame, kps, parts): for idx, (x, y, score) in enumerate(kps): if score 0.2: cv2.circle(frame, (int(x), int(y)), 3, (0, 255, 0), -1) # 连接左肩到左肘示意性连接完整连接表请按 COCO 骨架规范补全 cv2.line(frame, kps[parts[LShoulder]][:2], kps[parts[LElbow]][:2], (255,0,0), 2) return frame这里演示连接一条线实际项目里要补全 19 条肢体连接。连接表可以直接用 OpenPose 官方的POSE_BODY_25_PAIRS或 COCO 的 19 对连接关系。5.3 数据库回放验证脚本答辩前最稳的验证方式是查warning_record表取出所有报警记录的trigger_frame_id再从pose_frame表读出对应的keypoints_json和frame_image_url将结果按时间顺序渲染成新的截图。这比重新对视频跑一遍推理要快也能证明数据库设计闭环了。核心 SQL 是SELECT w.warning_time, w.warning_type, p.keypoints_json, p.frame_image_url FROM warning_record w LEFT JOIN pose_frame p ON w.trigger_frame_id p.id WHERE w.user_id 1 ORDER BY w.warning_time;回放脚本只需要循环读取这条查询结果用cv2.imread加载截图并叠加文本然后写入report/文件夹。可以把is_handled置 1 来标记已确认避免下次回放重复展示。本文还有配套的精品资源点击获取
返回列表