尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
基于CNN与姿态估计的动作识别系统:从关键点序列到实时推理
简介基于卷积神经网络的深度学习人体姿态与动作识别系统是一份面向计算机相关专业毕业设计、课程设计及项目实战练习的Python源码工程。压缩包共6个文件以5个Python脚本为主附带1份Markdown项目说明整体大小仅7KB文件结构紧凑适合快速通读理解深度学习动作识别链路。源码覆盖PoseDetector姿态检测、GetAcitonData动作数据采集、TrainModel模型训练、ModelTest模型测试等核心模块从数据准备到推理输出均有对应实现并非仅演示单一环节。该设计经导师指导并实际运行验证评审得分96.5分代码具备较强的可读性与扩展性。目前已有205人学习下载适合具备一定Python基础、希望将卷积神经网络运用于人体姿态与动作识别场景的在校学生或入门开发者。下载后可通过项目说明文档快速了解目录结构也可在此基础上修改功能用于毕设、课设或项目初期立项演示切勿商用。1. 先把识别链路讲清楚不是端到端图片分类是两段式管线这份基于 CNN 深度学习识别人体姿态和动作系统的 Python 源码第一眼容易被“CNN”带偏以为是把视频帧直接塞进卷积网络做分类。但实际上手拆完文件之后会发现它的核心思路是两段式先靠姿态估计把每帧视频里人的关键点坐标提取出来再让 CNN 去吃这些关键点序列输出动作类别。这种方案在本科毕设和课程设计里非常常见因为数据量要求小、训练稳定、效果可解释比起端到端方案更适合从零复现。源码包里五个 py 文件正好覆盖了一条完整链路GetAcitionData.py 管数据采集和 CSV 落地TrainModel.py 管模型训练ModelTest.py 管离线评估PoseDetector.py 封装姿态检测main.py 则是把模型接到摄像头实时推理。对正在做人姿态识别、动作识别相关毕设或者课设的人来说这份资源的价值在于“数据、训练、评估、推理”四个环节都能直接跑通不用自己拼凑。2. GetAcitionData.py 与数据管线关键点序列是怎么落成 CSV 的2.1 先搞清楚 CNN 吃进去的到底是什么这是整个项目最容易误读的地方。CNN 在这里不直接处理原始图像像素而是处理由 PoseDetector.py 抽取出来的人体关键点。常见做法是用 MediaPipe 的 Pose 模块做姿态估计一帧图像输入输出 33 个关键点每个关键点包含 x、y、z 和 visibility 四个值。把 33 个关键点的数据平铺下来一帧就得到一个长度为 132 的特征向量。如果模型一次看连续 64 帧那么单个训练样本的形状就是(64, 132)。这个形状对 CNN 来说非常友好132 可以当作通道数64 是时间维长度。特征向量再往后拼接一个类别标签就形成 CSV 的一行。整个动作识别任务实际上变成了一个关键点坐标序列的分类任务而不是传统图像分类。这也是为什么这份代码的训练速度和收敛效果都明显好于端到端视频分类。2.2 采集脚本的关键逻辑与参数GetAcitionData.py 的典型工作流程如下打开摄像头或视频文件逐帧送入姿态检测模块提取关键点归一化后拼成一行特征写入 CSV 文件中。每个动作类别一个 CSV 文件或者一个目录下同类样本连续存放。代码示意如下import cv2 import csv import mediapipe as mp pose mp.solutions.pose.Pose( static_image_modeFalse, model_complexity1, min_detection_confidence0.5, min_tracking_confidence0.5 ) def extract_features(frame, width, height): rgb cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) result pose.process(rgb) if result.pose_landmarks is None: return None features [] for lm in result.pose_landmarks.landmark: # x、y 按图像宽高归一化z 以固定深度范围归一化 features.append(lm.x / width) features.append(lm.y / height) features.append(max(0.0, min(1.0, lm.z / 0.5 0.5))) features.append(lm.visibility) return features def collect_action(csv_path, action_label, video_pathNone, fps_target30): cap cv2.VideoCapture(video_path if video_path else 0) writer csv.writer(open(csv_path, a, newline)) frame_idx 0 while cap.isOpened(): ok, frame cap.read() if not ok: break # 只按目标帧率采样避免摄像头 30fps 下数据冗余 if frame_idx % (int(cap.get(cv2.CAP_PROP_FPS)) // fps_target) ! 0: frame_idx 1 continue feats extract_features(frame, frame.shape[1], frame.shape[0]) if feats is not None: writer.writerow(feats [action_label]) cv2.imshow(collect, frame) if cv2.waitKey(1) 0xFF ord(q): break frame_idx 1 cap.release() cv2.destroyAllWindows()这段逻辑里有几个参数值得特别说明。min_detection_confidence和min_tracking_confidence分别控制初始检测和跟踪阶段的最低置信度值设太低会把无意义的抖动也收进数据设太高则容易出现“丢帧”也就是检测不到人导致那一帧被跳过。在室内固定摄像头场景下我一般用 0.5 到 0.6。z 坐标的归一化是另一处容易翻车的地方。MediaPipe 输出的 z 是相对坐标单独看没有明确物理单位。常见做法是除以一个固定参考深度值再压缩到 0 到 1 之间这样 CNN 拿到的输入量纲是一致的不会出现 x、y 在 0 到 1 之间、z 却横跨好几个数量级的情况。2.3 一个动作类别的样本组织方式采集完多个动作之后CSV 文件里的数据还需要进一步组装成训练样本。常见做法是滑窗切片比如 seq_len 取 64就用滑动窗口在 CSV 上每次截 64 行作为一个样本步长取 32这样样本数量能翻倍动作边界附近也能学到过渡状态。需要特别注意的是滑窗方向必须沿着时间轴也就是 CSV 每一行的先后顺序。很多人在这一步踩坑采集时动作做乱了或者 CSV 行序被改动过导致“连续帧”根本不存在时间连续性CNN 看到的序列内部是跳跃的。我在拆这类项目时习惯先做一步检查打印一个 CSV 的前三行和后三行确认所有行属于同一个动作标签并确认相邻行的关键点坐标差异不大。如果差异过大优先怀疑摄像头采集时人跑出画面或者滑窗混入了不同动作的数据。3. CNN 模型设计与输入组织为什么用一维卷积吃序列3.1 先把“图像分类 CNN”的思路放下很多第一次接触这份源码的人会习惯性去套图像分类的网络结构比如把 64 帧堆叠成一张假图像然后扔进二维卷积。这种做法不是不能跑但会把问题复杂化而且很容易忽视一个事实人体关键点序列的空间结构远没有图像复杂动作类别主要靠关键点坐标随时间的变化来区分。这里更合理的做法是用一维卷积。输入形状是(64, 132)其中 132 是每个时间步的特征数64 是时间长度。一维卷积核在时间维度上滑动每个卷积核看到的是“连续几帧内的关键点变化模式”。这正好对应了挥手、蹲下这类动作的判别特征。换句话说CNN 在这里并不是在识别“人长什么样”而是在识别“人体骨架结构在时间轴上的运动模式”。3.2 一个可用的 Conv1d 主干结构基于这个思路模型结构可以按下面这种方式组织这也是我在类似项目中验证过比较稳的一版import torch import torch.nn as nn class PoseActionCNN(nn.Module): def __init__(self, feat_dim132, num_classes5, seq_len64): super().__init__() self.conv_block nn.Sequential( nn.Conv1d(feat_dim, 64, kernel_size3, padding1), nn.BatchNorm1d(64), nn.ReLU(inplaceTrue), nn.Dropout(0.3), nn.Conv1d(64, 128, kernel_size3, padding1), nn.BatchNorm1d(128), nn.ReLU(inplaceTrue), nn.Dropout(0.3), nn.Conv1d(128, 256, kernel_size3, padding1), nn.BatchNorm1d(256), nn.ReLU(inplaceTrue), nn.Dropout(0.3), ) self.pool nn.AdaptiveAvgPool1d(1) self.classifier nn.Linear(256, num_classes) def forward(self, x): # x: (batch, seq_len, feat_dim) x x.permute(0, 2, 1) # (batch, feat_dim, seq_len) x self.conv_block(x) x self.pool(x).squeeze(-1) # (batch, 256) return self.classifier(x)这段代码的输入标注是(batch, seq_len, feat_dim)也就是数据加载器从 CSV 滑窗读出来的原始形状。permute把特征维度换到通道位Conv1d 才能在时间轴上滑卷积。kernel_size 取 3padding 取 1是为了保持卷积前后的序列长度不变让深层网络的感受野逐步扩大而不丢失时间信息。三层卷积的通道数从 64 加到 256足够区别人体动作的时间模式又不至于像图像分类网络那样动辄上百万参数。AdaptiveAvgPool1d(1)把整个时间序列压成一个特征向量这一步非常关键它让模型不依赖输入序列长度。如果是基础入门的同学我建议先不要改网络深度把这个结构原样跑通把精力花在数据质量和训练参数上效果往往比盲目加深网络更明显。3.3 关于 LSTM 的对比看到序列数据很多人第一反应是 LSTM。这份源码选 CNN 而不是 LSTM原因也很实际LSTM 训练不稳定对学习率敏感而且在这类短时间动作识别上CNN 的平移不变性反而更占优。比如“向右挥手”和“向右偏一点挥手”关键点坐标整体有偏移CNN 在时间维度上的滑窗对这类位置变化天然有一定容忍度。LSTM 则容易把位置信息当作强特征记住训练集里采集位置固定的话测试时换个站位容易翻车。对于毕设场景来说CNN 的稳定性和可复现性更值得优先考虑。4. TrainModel.py 与 ModelTest.py训练、早停与离线评估4.1 数据划分不能随机切行训练脚本里最常见的错误是随机打乱所有行再划分数据集。这种做法在这类序列数据上是致命的同一个滑窗的相邻窗口很可能会被同时分进训练集和验证集验证精度虚高真正跑摄像头时效果断崖式下跌。正确做法是按视频片段划分也就是说同一个动作片段产生的所有窗口要么全部进训练集要么全部进验证集。更稳妥一点是按采集批次划分先采集的动作进训练后采集的进验证。我给训练脚本加数据加载器时的做法是先读 CSV 文件记录每一行的来源视频或采集批次 ID再按 ID 分组划分。资源里的 TrainModel.py 如果只做了随机切分建议你复现时改成下面这种片段级逻辑。4.2 训练超参与模型保存训练阶段直接用 Adam 优化器起步即可学习率设 1e-3配合 StepLR 每 30 个 epoch 衰减到原来的 0.5。batch size 看显存和数据量一般 32 足够。整个训练用早停机制patience 设 15 个 epoch验证集精度不再上升就停止同时保存验证集指标最好的那一次模型权重。from torch.optim.lr_scheduler import StepLR optimizer torch.optim.Adam(model.parameters(), lr1e-3) scheduler StepLR(optimizer, step_size30, gamma0.5) criterion nn.CrossEntropyLoss() best_val_acc 0.0 patience 15 bad_epochs 0 for epoch in range(100): model.train() for x_batch, y_batch in train_loader: optimizer.zero_grad() logits model(x_batch) loss criterion(logits, y_batch) loss.backward() optimizer.step() model.eval() val_acc evaluate(model, val_loader) if val_acc best_val_acc: best_val_acc val_acc torch.save(model.state_dict(), best_model.pt) bad_epochs 0 else: bad_epochs 1 if bad_epochs patience: print(early stop at epoch, epoch) break scheduler.step()这里保存的是state_dict()而不是整个模型对象是为了后续在 ModelTest.py 和 main.py 里灵活加载。保存时最好同时存一份类别映射表比如{0: wave, 1: squat}这样的 JSON 文件否则推理时输出数字标签无法对应到动作名。Adam 的初始学习率在这个模型规模下我很少直接改先跑通为主。如果 loss 在前几个 epoch 不下降优先怀疑数据加载器里 x 的方向搞错了也就是是否把时间维和特征维搞反。这种 bug 在训练初期几乎看不出来因为 loss 初始值正常但 val_acc 会一直卡在随机水平。4.3 ModelTest.py 里要看的是混淆矩阵不是准确率ModelTest.py 的定位是用训练好的权重在测试集上做离线评估。这里强烈建议不要只打印一个整体准确率要输出分类报告和混淆矩阵。原因很简单动作数据集大概率不均衡某一个动作采集多了整体准确率就会被拉高看起来 90% 很好实际上某个低样本类别完全没学会。评估脚本里加上这段就能直观看到每个类别的 Precision、Recall 和 F1-scoreimport json from sklearn.metrics import classification_report, ConfusionMatrixDisplay import matplotlib.pyplot as plt model.load_state_dict(torch.load(best_model.pt)) model.eval() all_preds, all_labels [], [] for x_batch, y_batch in test_loader: logits model(x_batch) preds torch.argmax(logits, dim1) all_preds.extend(preds.tolist()) all_labels.extend(y_batch.tolist()) with open(class_names.json) as f: class_names json.load(f) print(classification_report(all_labels, all_preds, target_namesclass_names)) ConfusionMatrixDisplay.from_predictions( all_labels, all_preds, display_labelsclass_names, cmapBlues ) plt.savefig(confusion_matrix.png, dpi150)在真实项目里我经常发现confusion_matrix.png 比训练过程里的 loss 曲线有说服力得多。它能直接看出哪两个动作互相混淆——比如“蹲下”和“坐下”如果频繁混在一起说明这两类动作在关键点序列特征本身就比较接近优先加数据而不是调模型结构。5. main.py 实时推理模型部署到摄像头时的三个关键设计5.1 推理框架与帧率控制main.py 把前面所有环节串成实时演示系统摄像头读帧姿态检测拿关键点归一化后拼成特征向量放入一个固定长度的队列集齐 64 帧后交给 CNN 前向推理输出动作类别和置信度。这个流程本身不复杂真正影响体验的是队列的维护和输出平滑。常见做法是维护一个 deque每来一帧就 append 新特征向量超过 64 帧就 popleft 掉最早的帧。这样每帧都做一次推理输出频率等于摄像头帧率。需要注意控制推理频率。摄像头一般 30fps而 CNN 前向传播加上姿态检测在普通 CPU 上可能只有 10fps 到 15fps如果每帧都等模型出结果视频画面会明显卡顿。我一般会让推理循环单独跑实际推理频率限制在 10fps 以内画面显示和推理互不阻塞。5.2 置信度阈值与状态保持实时推理最容易出现的问题是“动作状态抖跳”明明做了一个连续动作预测结果在几个类别之间反复横跳演示时看起来就是要么反应迟钝要么乱跳。解决这个问题的常见做法是加入一个阈值等待机制连续 N 帧预测为同一类别且置信度超过阈值才真正更新屏幕上显示的动作标签。下面是一种简短可靠的实现方式current_label idle stable_count 0 required_stable_frames 5 confidence_threshold 0.7 for frame in camera_stream(): feats extract_features(frame) queue.append(feats) if len(queue) seq_len: continue sample torch.tensor([list(queue)], dtypetorch.float32) logits model(sample) prob torch.softmax(logits, dim1) score, pred torch.max(prob, dim1) if score.item() confidence_threshold: stable_count 0 continue if pred.item() current_label: stable_count 1 else: stable_count 1 current_label pred.item() if stable_count required_stable_frames: display_label class_names[pred.item()]这里不需要复杂的状态机一个稳定计数字段就够了。要注意的是current_label和display_label是两回事前者是模型输出在试探性切换后者是真正展示给用户的结果。别把模型输出直接打到屏幕上。置信度阈值 0.7 是在我的使用习惯下比较稳的起点。如果动作之间差异很小比如只是左右手挥手阈值可能要提到 0.8如果动作幅度大、特征明显0.6 就不容易漏检。这个参数没有固定值建议按自己的测试数据调两三轮。6. 避坑这项毕设最容易翻车的五个问题6.1 关键点坐标没有统一归一化训练 loss 降不下去现象loss 在训练前期下降后一直在大区间波动val acc 始终在 50% 上下转悠怎么调学习率都没用。原因采集时摄像头分辨率不一致x、y 坐标像素量纲和 z 坐标相对值混在一起CNN 的卷积核很难从这种量纲混乱的输入里学出稳定特征。尤其是 z 坐标MediaPipe 输出范围并不固定直接被模型吸收后会让 BN 层的统计量不稳定。解决统一走 2.2 小节里的归一化写法。x、y 除以图像宽高z 除以固定参考值并压缩到 0 到 1。归一化逻辑写进数据集加载器里而不是采集脚本里这样以后换数据源也能保证规则一致。6.2 训练和验证集按行随机切分评估结果虚高现象离线测试集准确率 95%但 main.py 跑摄像头实时识别时明显感觉到延迟大、识别错误率高前后结果对不上。原因滑窗后相邻窗口高度重叠同一段动作的数据被同时划进训练集和验证集模型在验证集上相当于“背答案”。解决按视频片段或采集批次的 ID 划分数据集保证同一个片段的时间窗口不会跨训练和验证集合。如果 CSV 里没有记录来源需要在采集时顺手写入一个 clip_id 字段。6.3 类别标签映射错位训练对了推理全错现象训练集和测试集准确率都不错但实时推理时发现“蹲下”显示成“挥手”而且错得很有规律每次都错到同一个类别。原因类别名到数字标签的映射不一致。训练时可能是按文件夹字符串排序生成映射表推理时又按 CSV 读取顺序映射两边排序规则不一致。解决训练完成后把类别映射表导出成 JSON 文件推理脚本从 JSON 加载映射。永远不要依赖“记得顺序 ”或者重新用 list 排序推导。6.4 动作类别数据量相差太大整体指标失真现象某个动作只采集了 60 帧其他动作都上千帧训练出的模型这个动作从未被正确预测过。原因类别不均衡时交叉熵损失会被大多数样本主导少样本类别梯度占比太小模型直接选择忽略它。解决最简单的是加类别权重CrossEntropyLoss(weightclass_weights)权重按样本数的倒数归一化。更好的做法是在采集阶段就注意每个动作采集时长尽量一致比如每个动作固定录 30 秒按帧数清洗后取最少类别的帧数作为统一长度。6.5 遮挡时关键点置信度低脏数据被直接送进模型现象监控画面里人偶尔侧身、手放到背后模型输出越来越不稳定同一个动作不同时间段测试结果差异很大。原因MediaPipe 在部分关键点被遮挡时仍然会输出估计坐标但 visibility 很低。这些置信度低的关键点坐标往往漂移严重成了训练数据里的噪声。解决提取特征时判断 visibility低于阈值的关键点用上一帧的坐标填充或者直接把这一整帧丢弃跳过去不写入 CSV。这样模型看到的序列里不会混入大量漂移点。7. 复用这份源码最值得做的一件事换一套自己的动作集拿到这份资源不建议直接改网络结构更值得做的是把动作集换成自己的四个动作完整走一遍数据采集到实时演示的流程。换动作集时要同步动三个地方seq_len、采集时长、类名映射表。seq_len 代表模型看多长一段序列才能判断动作。我自己的经验值是快速动作如挥手、拍手64 帧在 30fps 下大约两秒足够慢速动作如太极拳起势、瑜伽动作64 帧容易把动作切在半路建议把采集帧率降到 15fps 并保持 seq_len64相当于看四秒这样既不会把窗口拉太长导致响应迟钝也能覆盖完整动作。善用关键点层面的数据增强比调模型结构对效果的提升更明显。常见做法包括坐标加高斯抖动、时间轴随机缩放、以骨盆关键点为原点做旋转扰动。这类增强是在关键点坐标上做的计算量极小一个简单的抖动增强可能就把验证集准确率拉高几个点。下面是一个简洁示例import random def jitter_features(feats, noise_scale0.01): noisy list(feats) for i in range(len(noisy) - 1): # 最后一列是标签不增强 noisy[i] random.gauss(0, noise_scale) return noisy注意噪声幅度不能太大0.01 左右对归一化后的坐标已经是比较明显的扰动加太大会让动作类别边界变得模糊反而拉低效果。增强要在滑窗采样时随机触发而不是固定所有样本都加同样的噪声。验证阶段我会录一段完整的长视频里面依次做完所有动作然后用 ModelTest 的脚本对这段视频逐帧预测把每帧的预测标签和置信度输出到一个日志文件里再人工对着日志检查每个动作片段的响应时间。这个习惯从那以后每次复现这类基于关键点的动作识别项目都会强制做一遍能很快暴露数据采集姿势不规范或者阈值设置不合理的问题比对着训练曲线空想有效得多。希望这份源码的拆解过程能帮你少走几步弯路。本文还有配套的精品资源点击获取
RELATED

相关推荐

AZ-104题库精讲:标签、条件访问与ARM模板的实战避坑指南

AZ-104题库精讲:标签、条件访问与ARM模板的实战避坑指南

简介:AZ-104备考题库对应微软MCP认证体系中Azure解决方案专家方向,专为准备参加Azure管理员认证考试的IT从业者设计,尤其适合云运维与架构人员快速验证知识掌握程度。PDF内含经过专家验证的在线题目,支持自定义视图设置&#xff0…

📅 2026/9/24 20:15:46
Griffin:面向空地协同检测与跟踪的双视角数据集与基准

Griffin:面向空地协同检测与跟踪的双视角数据集与基准

1. 为什么需要空-地协同检测与跟踪数据集很多人第一次看到 Griffin 这个名字,第一反应可能是某个神话生物,但在视觉感知圈子里,它指的是一个专门为 Aerial-Ground Cooperative Detection 和 Tracking 设计的数据集与评测基准 Dataset & B…

📅 2026/9/24 20:15:46
23中GOF设计模式之工厂方法模式

23中GOF设计模式之工厂方法模式

工厂方法模式:抽象创建者 多个具体创建者(子类工厂);加产品新建子类,不改老代码。抽象创建者:作为各个子类工厂的父类,负责提供通用容器,逻辑,预留抽象工厂方法&#xf…

📅 2026/9/24 20:15:46
MORE NEWS

更多资讯

📰

YOLOV9安全帽与反光背心检测:数据集构建与训练全流程指南

简介:面向建筑工地、工厂车间等需要强制个人防护装备(PPE)的作业场景,这份数据集已对安全帽、安全服与反光背心完成 2000 多张图像的 YOLOv9 格式标注,可直接用于安全穿戴检测模型的训练与评估,也可迁移到其…

📰

DHCP服务器设计与实战:从IP分配到网络智能中枢

1. 什么是DHCP服务器:它不是“配IP的工具”,而是网络的呼吸中枢很多人第一次听说DHCP服务器,脑子里浮现的是“自动给电脑发IP地址的那个东西”。这没错,但太轻描淡写了——就像说心脏只是“泵血的肌肉”,忽略了它每分钟…

📰

Zed AI代理驾驶舱实测:从Redux到Zustand的智能重构实践

过去两年我几乎把主流编辑器的AI能力都折腾了一遍,从Copilot到各种IDE插件,但真正让我觉得“AI开始像个同事而不是打字机”的,是Zed编辑器在2026年初落地的这套Agent能力。我花了两周时间,用它把一个中型React项目的状态管理从Red…

📰

Python GIL深度解析:全局解释器锁的原理、影响与绕开方案

面试的时候被问到“Python的GIL是什么”,很多人的第一反应是:“全局解释器锁,多线程没法利用多核。”这个回答不能说错,但它就像把一座冰山描述成“水面上那块白色物体”。GIL背后牵扯到CPython的内存管理模型、垃圾回收机制、多线…

📰

Cline 接入 Agnes AI 完整教程:从密钥配置到参数调优

最近我一直在折腾 AI 编码助手的接入方案,之前一直用各家编辑器自带的默认模型,总觉得差点意思。直到我把 Cline 接到了 Agnes AI 模型上,完整跑通了账号申请、密钥配置、参数调试这一条链路,才发现原来换一个模型服务对日常写代码…

📰

AI工程全景地图:从模型到系统落地,六大能力域与工程实践解析

去年我在一个制造业客户的会议室里,听他们IT负责人讲了一个特别典型的事:算法团队花三个月训练了一个设备故障预测模型,准确率看着不错,但真到了产线上,数据接入要重新写管道,特征口径跟早会报表对不上&…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬