简介:这份资源是基于Mediapipe实现动作识别的Python毕业设计源码包,面向计算机视觉、机器学习方向的高校学生与自学者,可用于智能家居、健身指导、游戏交互等场景的动作识别课题实践。压缩包共7个文件,约97KB,以3个py脚本为核心,配合2个csvi与2个csv动作样本数据,覆盖视频抽帧、姿态关键点采集与动作分类等环节,代码结构清晰便于二次开发。项目围绕Mediapipe的Pose模块展开,可实时检测并跟踪头部、肩部、肘部、手腕、髋部、膝盖、脚踝等关键关节,再结合SVM、随机森林或CNN等模型完成动作类别判定,同时涉及OpenCV视频流处理与matplotlib结果可视化,完整呈现数据预处理、模型训练、实时检测与结果展示的工程流程。目前已有242人学习,适合作为毕业设计参考方案,帮助读者快速理解Mediapipe管道机制、姿态估计与动作识别整合思路,并锻炼Python编程与问题解决能力。
1. 从一份「基于 MediaPipe 实现的动作识别 Python 毕业设计源码」说起
如果你正在搜「基于 MediaPipe 实现的动作识别 Python 毕业设计源码」,大概率是三种人之一:要交毕设的本科生、想快速跑通一个能演示的 CV 小项目、或者想拿它当自己产品原型的开发者。这个标题拆开看其实就三件事:MediaPipe 负责从摄像头里把人体的关键点抠出来,动作识别负责把这些关键点序列翻译成「挥手 / 深蹲 / 举手」这类语义标签,Python 负责把整条链路串起来。它不需要训练大模型,不需要 GPU 服务器,一台普通笔记本的摄像头就能跑,这正是它适合当毕业设计和入门实战的原因。
但「源码」两个字最容易骗人。网上流传的压缩包里,很多只是把 MediaPipe 官方示例改了个文件名,动作识别部分要么是写死的 if-else 阈值判断,要么干脆没实现,只画了骨架。真正能用的方案,核心不在 MediaPipe 本身——它已经把检测做到开箱即用——而在于你拿到 33 个关键点之后,怎么构造特征、怎么定义动作、怎么让识别不抖。这篇就按「先跑通骨架 → 再构造特征 → 再做分类 → 再避坑 → 再进阶」的顺序,把这条链路讲透,让你拿到任何一份类似源码都能看懂、改得动、演示得出来。
2. MediaPipe 姿态估计跑通:从摄像头到 33 个关键点
2.1 为什么选 MediaPipe 而不是自己训检测模型
做动作识别,第一步永远是「人现在是什么姿势」。传统路线是 YOLO 检测人 + 姿态估计网络回归关键点,两步走,模型加起来几百 MB,还要配环境、下权重、调 NMS。MediaPipe 的 Pose 方案把检测和关键点回归压进一个轻量管线,CPU 上单帧几毫秒,模型文件随包安装,pip install完就能用。对毕业设计这种「要演示、要答辩、时间有限」的场景,选它是理性的:你省下的环境调试时间,可以全部投到动作逻辑上。
代价也要说清楚。MediaPipe Pose 输出的是 33 个关键点的归一化坐标(x、y 在 0~1,z 是相对深度),它不直接告诉你「这个人在干嘛」。而且它对遮挡、快速运动、多人场景的处理有限——默认只跟踪画面里置信度最高的那个人。所以它适合单人、正面或侧面对着摄像头、光照正常的场景,这也正是毕设演示的标准环境。
2.2 最小可运行代码:打开摄像头画出骨架
先别管动作识别,把骨架跑出来,确认环境和摄像头没问题。下面这段是能直接抄的最小版本:
import cv2 import mediapipe as mp # 初始化 MediaPipe Pose 模块 mp_pose = mp.solutions.pose mp_draw = mp.solutions.drawing_utils # static_image_mode=False 表示走视频流模式,会做帧间跟踪,更快更稳 # model_complexity=1 是精度和速度的平衡点,0 最快、2 最准 pose = mp_pose.Pose( static_image_mode=False, model_complexity=1, smooth_landmarks=True, # 关键点平滑,减少抖动 min_detection_confidence=0.5, min_tracking_confidence=0.5 ) cap = cv2.VideoCapture(0) # 0 是默认摄像头,外接摄像头可试 1 while cap.isOpened(): ret, frame = cap.read() if not ret: break frame = cv2.flip(frame, 1) # 镜像,符合照镜子的直觉 rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) # MediaPipe 要 RGB result = pose.process(rgb) if result.pose_landmarks: mp_draw.draw_landmarks( frame, result.pose_landmarks, mp_pose.POSE_CONNECTIONS) cv2.imshow('pose', frame) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows()逻辑说明:pose.process()接收 RGB 帧,返回pose_landmarks,里面landmark是一个长度 33 的列表,每个元素有x, y, z, visibility。draw_landmarks只是可视化,真正做识别时你要的是那 33 组坐标,不是画出来的图。
参数说明:model_complexity是第一个该调的参数,笔记本卡就设 0,追求精度设 2;smooth_landmarks=True会让关键点在时间上做平滑,代价是动作切换时有一点点延迟,做快速动作识别时可以关掉对比效果;两个 confidence 阈值低于 0.5 会引入大量误检,高于 0.7 又容易在侧身时丢点,0.5 是常用起点。
2.3 关键点索引:动作识别只用到其中一部分
33 个点不用全用。做上肢动作(挥手、举手、比心)主要看 11~22 号点(肩、肘、腕、手);做下肢动作(深蹲、抬腿)看 23~28 号点(髋、膝、踝)。记住几个关键索引能省很多事:
| 索引 | 部位 | 索引 | 部位 |
|---|---|---|---|
| 11 / 12 | 左 / 右肩 | 23 / 24 | 左 / 右髋 |
| 13 / 14 | 左 / 右肘 | 25 / 26 | 左 / 右膝 |
| 15 / 16 | 左 / 右腕 | 27 / 28 | 左 / 右踝 |
提示:不要用绝对坐标做判断。人站得离摄像头远近不同,x、y 的绝对值会变,但关节之间的相对角度不变。这是后面特征工程的核心。
3. 从关键点到动作标签:特征构造与分类器选型
3.1 用关节角度代替原始坐标
直接把 33×2 个坐标丢给分类器,模型会学到「人站在画面左边还是右边」这种和动作无关的信息,换个位置就翻车。稳妥做法是算角度。以肘关节为例,用肩、肘、腕三点算夹角:
import numpy as np def calc_angle(a, b, c): """计算 b 点处的夹角,a/b/c 为 (x, y) 坐标""" a, b, c = np.array(a), np.array(b), np.array(c) ba = a - b bc = c - b # 点积公式求夹角,加 1e-6 防止除零 cosine = np.dot(ba, bc) / (np.linalg.norm(ba) * np.linalg.norm(bc) + 1e-6) return np.degrees(np.arccos(np.clip(cosine, -1.0, 1.0))) def extract_features(landmarks): """从 33 个关键点里抽出对动作敏感的角度特征""" lm = [(p.x, p.y) for p in landmarks.landmark] feats = [] # 左右肘角度 feats.append(calc_angle(lm[11], lm[13], lm[15])) feats.append(calc_angle(lm[12], lm[14], lm[16])) # 左右肩角度(躯干与上臂) feats.append(calc_angle(lm[23], lm[11], lm[13])) feats.append(calc_angle(lm[24], lm[12], lm[14])) # 左右膝角度 feats.append(calc_angle(lm[23], lm[25], lm[27])) feats.append(calc_angle(lm[24], lm[26], lm[28])) return np.array(feats)逻辑说明:calc_angle用向量点积求夹角,返回 0~180 度。extract_features把 6 个角度拼成一个 6 维向量,这就是每一帧的特征。相比 66 维原始坐标,6 维角度对位置、体型、距离都不敏感,泛化好得多。
参数说明:np.clip把余弦值夹到 [-1, 1],避免浮点误差导致arccos出 NaN,这是血泪经验,不加偶尔会崩。角度特征的数量可以按动作扩展,比如加手腕相对肩膀的高度差来判断「举手」。
3.2 时序窗口:单帧不够,要攒一段
动作是随时间变化的,单帧角度只能说明「此刻姿势」,说明不了「正在做动作」。常见做法是滑动窗口:维护一个长度为 N 的队列,每帧算完特征就入队,队列满了就取出来做一次分类。N 一般取 15~30,对应 0.5~1 秒。
from collections import deque WINDOW = 20 buffer = deque(maxlen=WINDOW) def on_frame(landmarks): feats = extract_features(landmarks) buffer.append(feats) if len(buffer) < WINDOW: return None # 窗口没满,先不判断 # 把 20 帧 × 6 维拼成 120 维,或者做统计量 seq = np.array(buffer) # shape: (20, 6) mean = seq.mean(axis=0) # 均值反映整体姿态 std = seq.std(axis=0) # 标准差反映动作幅度 return np.concatenate([mean, std])逻辑说明:均值 + 标准差是性价比最高的时序压缩方式。均值告诉你「这段时间平均什么姿势」,标准差告诉你「动得厉害不厉害」。挥手和静止举手的均值可能接近,但挥手的标准差明显更大,这一维就能区分开。
参数说明:WINDOW太小(<10)动作没做完就判断,容易误触发;太大(>40)延迟明显,演示时感觉卡顿。20 帧在 30fps 下约 0.67 秒,是常用值。如果摄像头帧率不稳,建议按时间而不是帧数切窗口。
3.3 分类器:从阈值规则到轻量模型
特征有了,分类器有三档选择,按你的时间和数据量挑:
第一档是阈值规则,比如「肘角 < 90 且腕高于肩 → 举手」。优点是零训练、可解释、答辩好讲;缺点是动作一多就写成一堆 if-else,调参调到怀疑人生。适合只做 2~3 个动作的毕设。
第二档是 scikit-learn 的 SVM 或随机森林。你手动采集每个动作几十段样本,每段算一个特征向量,训练几秒钟就完事,准确率对 5~8 个动作通常能到 90% 以上。这是我最推荐的毕设方案,代码量小、效果稳、能讲清楚。
from sklearn.ensemble import RandomForestClassifier import joblib # X: (样本数, 特征维度) y: 对应动作标签 0/1/2... clf = RandomForestClassifier(n_estimators=100, max_depth=10, random_state=42) clf.fit(X_train, y_train) joblib.dump(clf, 'action_model.pkl') # 存下来,演示时直接加载第三档是 LSTM 或 MediaPipe Model Maker 自定义训练。前者要 PyTorch/TensorFlow 环境,后者能直接吃视频片段训练,但环境配置和训练时间对毕设来说偏重。除非导师明确要求深度学习,否则没必要。
注意:采集训练数据时,每个动作至少 30 段,每段之间要有明确的「回到中立姿势」的间隔,否则窗口会跨动作,标签就脏了。
4. 避坑与排查:动作识别跑起来后最容易翻车的五件事
4.1 现象:识别结果疯狂跳变,同一动作一会儿 A 一会儿 B
原因:单窗口独立判断,没有做时间上的平滑。相邻两个窗口特征接近,分类器边界附近就会来回横跳。
解决:加一个投票或计数机制。维护最近 K 次预测结果,取众数;或者要求同一标签连续出现 M 次才确认输出。M 取 3~5 效果明显,代价是响应慢一点点。
from collections import Counter history = deque(maxlen=5) def stable_predict(label): history.append(label) return Counter(history).most_common(1)[0][0]4.2 现象:换个房间、换个人,准确率断崖下跌
原因:用了绝对坐标或像素距离做特征,训练时人在画面中央,测试时人偏左,特征分布就变了。
解决:坚持用角度和相对量。如果已经用了坐标,做归一化:以髋部中心为原点,用肩宽做尺度归一。这一步不做,模型就是「认环境」而不是「认动作」。
4.3 现象:MediaPipe 频繁丢点,骨架一闪一闪
原因:光照不足、背景杂乱、人离摄像头太远或太近,导致检测置信度低于阈值。
解决:先调min_detection_confidence到 0.4 试试;改善正面光照;让人占画面高度的 1/2 到 2/3。如果还丢,在丢点帧用上一帧的关键点做插值,别让特征向量出现空值。
4.4 现象:程序跑几分钟越来越卡,内存一直涨
原因:每帧都新建Pose对象,或者把每帧图像存进列表没释放。
解决:Pose对象在循环外初始化一次,全程复用;不要缓存原始帧,只缓存特征向量;cap.release()和destroyAllWindows()一定要在 finally 里执行。
4.5 现象:答辩现场摄像头打不开或画面全黑
原因:摄像头被其他程序占用,或者VideoCapture(0)的索引不对,或者权限没给。
解决:提前用系统相机确认设备正常;代码里加cap.isOpened()判断并打印提示;准备一段录好的视频文件作为兜底输入,VideoCapture('demo.mp4')一样能跑,现场翻车时能救场。
5. 进阶技巧:让毕设从「能跑」变成「能打」
5.1 用状态机管住动作的起止
纯分类器输出的是「当前像哪个动作」,但真实动作有开始和结束。加一个简单状态机:中立态 → 检测到动作特征持续 N 帧 → 进入动作态并记录 → 特征回落到中立 → 输出一次完整动作。这样输出的是「完成了一次挥手」而不是「连续 30 帧都在挥手」,演示效果和逻辑清晰度都上一个台阶。
5.2 用 MediaPipe Model Maker 做自定义动作
如果你确实需要更多动作、又不想自己搭训练框架,Model Maker 支持喂视频片段训练自定义分类头。流程是:每个动作录 20~30 段短视频,按文件夹分好,调用训练脚本,导出 tflite 模型,再用 MediaPipe 的 Tasks API 加载。它对环境要求比手写 LSTM 低,但训练时间取决于数据量,毕设周期内要提前排期,别拖到最后一周。
5.3 验证方法:别只看准确率
答辩时导师最爱问「你怎么证明它真的行」。准备三样东西:一是混淆矩阵,看哪些动作容易混;二是实时演示,现场做动作看响应;三是边界测试,比如快速做、慢速做、背对摄像头做,主动说出局限在哪。能讲清楚「它在什么条件下不可靠」,比吹准确率 99% 更让人信服。
| 验证项 | 方法 | 合格线 |
|---|---|---|
| 单动作准确率 | 每动作 20 段测试样本 | > 85% |
| 响应延迟 | 从动作开始到输出标签 | < 1 秒 |
| 抗抖动 | 静止时误触发次数 | 每分钟 < 2 次 |
| 鲁棒性 | 换人 / 换位置重测 | 下降 < 15% |
5.4 我自己的习惯
我做完任何一个动作识别 demo,第一件事不是加动作,而是先录一段自己「乱动」的视频跑一遍,看它会不会乱报。乱报的根源八成在窗口边界和阈值,不在分类器。把误触发压下去,再往上加功能,顺序反了会一直返工。这套东西不难,难的是耐心调那几个参数,希望帮到你。
本文还有配套的精品资源,点击获取