资讯动态

YOLOv5+MediaPipe+BiLSTM:构建中文手语识别系统

发布时间:2026/9/8 11:18:59 来源:尧图企业网站定制
简介面向计算机视觉方向学习与毕业设计的实用源码包基于USTC手语数据集结合MediaPipe关键点提取与YOLOv5目标检测实现手语视频的实时识别与翻译覆盖数据预处理到界面展示的完整流程。项目包含19个Python算法脚本、图形界面设计、模型配置及示例视频涉及手部检测、姿态分类、RNN序列建模与交互逻辑等模块适合需要快速搭建手语识别原型或进行算法对比的开发者参考。压缩包共40个文件其中包括4个.ui界面布局文件、5个.xml项目配置、2个.avi测试视频以及说明文档与字典文件整体仅13.77MB结构清晰便于拆解学习。当前已有319人学习下载代码注释与目录组织较完整可作为课程设计、毕业设计或科研预研的基座其模块划分清晰便于在此基础上扩展其他手势或动作识别场景。 前阵子整理手语识别相关代码翻了半天发现好多人被卡在技术选型上到底是用逐帧图像分类还是用 MediaPipe 直接提手部关键点又或者干脆上 3D 卷积。其实从效果和落地难度综合看一套比较稳的方案是「YOLOv5 找手 MediaPipe 提关键点 时序模型做分类」。这次要拆的项目就是基于 USTC 中文手语数据集用 Python 串起来的这套手语视频识别系统。它能做的是输入一段打手语的视频输出对应的手语词汇标签整个过程不需要特殊硬件普通笔记本就能跑推理。这套架构对正在做毕设、准备 AI 相关实践项目或者对手势/动作识别感兴趣的朋友都挺有参考价值。难点不在单个算法而在怎么把检测、关键点、时间序列这三层粘成一个能稳定工作的系统。我下面按自己的实操经历把各个环节的设计思路、具体代码和踩过的坑都过一遍。1. 整体设计思路与关键选型1.1 为什么是「YOLOv5 先找手MediaPipe 再提点」很多人看到这个组合的第一反应是MediaPipe 自己就带 hand detection为什么还要叠一个 YOLOv5我一开始也是这么想的直接拿 MediaPipe Hands 跑了 USTC 数据集里几个视频结果在快速运动、手部轻微遮挡或者背景杂乱的情况下漏检率一下就上来了。漏检带来的问题很致命后面的时序模型输入序列会断裂你总不能靠脑补把缺的关键点补上。而且 MediaPipe 的检测是跟着视频流走的一旦某一帧丢手它要重新初始化经常导致接下来的几十帧都不稳定。YOLOv5 在这里的角色不是替代 MediaPipe而是做一个前置的「手部区域检测器」。先把每帧画面里的手部 ROI 剪出来送进 MediaPipe 的关键点网络时输入已经是干净、居中的手部特写提取的 21 个关键点会稳定很多坐标抖动也小。简单说YOLOv5 负责回答「手在哪」MediaPipe 负责回答「手的长相和动作细节」两个模型干各自擅长的事互不抢活。有人可能会问直接用 YOLOv5 做端到端的手语分类不行吗技术上可行但 USTC 这类数据集的类别数动辄几十上百视频帧里手部动作之外还有大量背景和人体姿态干扰端到端卷积网络要吃很多数据才能压住过拟合。而 MediaPipe 把高维图像压缩成 21 个关键点的 3D 坐标时序模型只需要在这 63 维或加 visibility 变成 84 维的序列上做分类数据需求和训练难度都小了一个量级。1.2 USTC 数据集的特点与预处理思路USTC 数据集是中文手语词汇数据采集的是日常交流中高频使用的手语词视频以单人手部动作为主。这个数据集最大的特点是「词汇量大、动作边界难切」。视频里一个词对应一段连续动作但起止帧往往没有明确标注而且不同人打同一个词的节奏差异挺大。预处理阶段最核心的一件事是把所有视频统一到相同的帧率和采样策略。USTC 原始视频的帧率不算高如果直接按帧索引抽帧不同视频的长度差异会直接干扰时序模型的输入结构。我的做法是固定每秒抽 10 帧不足的部分做线性插值补到固定长度多出来的部分从中间截断。另外数据划分必须按「视频」而不是「帧」进行否则同一个视频里高度相关的相邻帧会漏进训练集导致验证指标虚高。预处理流程我最终固定成四步视频拆帧统一采样到固定帧率YOLOv5 逐帧检测手部 ROI置信度低于 0.5 的帧标记为缺失对缺失帧做前后线性插值减少序列中断对每个 ROI 用 MediaPipe 提取 21 个关键点保存为 numpy 序列这套流程跑一遍数据就从「一堆视频文件」变成了「若干条长度一致的时序特征」后面的模型训练和预测就有了统一入口。2. 数据预处理与关键点序列构造2.1 YOLOv5 手部检测落地细节YOLOv5 版本很多我实际用的是 v6.0 的 yolov5s 权重做微调。之所以没直接拿官方 COCO 预训练权重硬跑是因为 COCO 类别里没有 hand 这一类直接推理会漏检。正确做法是先准备一份手部检测数据集格式转成 YOLOv5 的 labels 格式每行class x_center y_center width height坐标全部归一化到 0~1。微调时超参数不用大改主要是把输入分辨率降到 320因为手部目标在视频里往往占画面比例不大320 输入能明显提升小目标召回率还顺带提速。训练轮次我控制在 30 轮左右学习率 0.01批量大小 16。手部检测是一个相对简单的任务30 轮之后 mAP 已经能到 0.9 以上再多容易过拟合。检测完还要做一步后处理框抖动平滑。手部检测器在视频相邻帧之间会出现边界框轻微抖动而这会直接影响后续 ROI 剪裁的稳定性。我用的是一次指数移动平均def smooth_bbox(new_box, old_box, alpha0.6): if old_box is None: return new_box return [alpha * nb (1 - alpha) * ob for nb, ob in zip(new_box, old_box)]alpha 取 0.6 是我试下来比较平衡的值太大响应慢太小等于没平滑。2.2 MediaPipe 关键点提取与归一化MediaPipe Hands 只需要一段很短的代码就能跑通import cv2 import mediapipe as mp hands mp.solutions.hands.Hands( static_image_modeFalse, max_num_hands2, min_detection_confidence0.5, min_tracking_confidence0.5 ) def extract_landmarks(roi): rgb cv2.cvtColor(roi, cv2.COLOR_BGR2RGB) results hands.process(rgb) if not results.multi_hand_landmarks: return None lm_list results.multi_hand_landmarks[0].landmark return [(lm.x, lm.y, lm.z) for lm in lm_list]这里要注意两点。第一max_num_hands要设成 2中文手语里有大量需要双手配合的表达只留一只手会把这类样本的信息直接丢掉。第二关键点坐标本身是相对图像尺寸归一化的 0~1 浮点数但如果直接拿它训练手的绝对位置变动会干扰模型。我的做法是以 wrist也就是 0 号点为原点做一次相对坐标变换再用手掌区域对角线长度做尺度归一化这样得到的 63 维特征对平移和尺度都不敏感。import numpy as np def normalize_landmarks(landmarks, bbox_size): pts np.array(landmarks) # shape: (21, 3) wrist pts[0] pts - wrist pts / max(bbox_size, 1e-6) return pts.flatten() # 63 dims2.3 时序样本的窗口构造关键点序列不能整条丢给模型。手语词的动作长度通常不超过两秒按 10 帧/秒算就是 20 帧左右。我用固定 16 帧的滑窗切样本窗口重叠 50%对应 1.6 秒的时间跨度基本覆盖 USTC 里大多数词的完整动作。切完的样本形状是(batch, 16, 63)这也是后面时序模型的输入维度。窗口长度是一个特别值得调的参数。太短一个词的动作只截了一半模型没法区分相近词汇太长动作结束后的一大段静止帧混进来反而稀释有效信息。我在 USTC 上对比过 8、16、32 三种窗口16 帧的验证准确率比 8 帧高约 6 个百分点比 32 帧只低不到 1 个百分点但训练速度快不少。所以固定 16 帧是性价比最高的选择。3. 模型训练与系统实现3.1 时序分类模型的设计对比拿到(batch, 16, 63)的序列后剩下的问题就是找一个能捕捉动作随时间变化的分类器。我对比过三种方案单层 LSTM、BiLSTM Attention、1D TCN。表格里的数据是在 USTC 一个 50 类子集上跑的验证准确率。模型输入维度参数量验证准确率推理耗时/样本LSTM (hidden128)63约 30 万82.4%0.8msBiLSTM Attention63约 55 万87.1%1.3ms1D TCN (3 层)63约 25 万85.6%0.6ms最终我选了 BiLSTM Attention。1D TCN 速度最快但对动作边界的敏感度稍差单层 LSTM 表达力不太够相近词汇容易混。BiLSTM 能从两个方向看整个窗口内的动作变化Attention 又能自动聚焦到动作最强烈的几帧效果最稳。简易实现如下import torch.nn as nn class SignLanguageClassifier(nn.Module): def __init__(self, num_classes, input_dim63, hidden_dim128): super().__init__() self.lstm nn.LSTM( input_dim, hidden_dim, num_layers2, bidirectionalTrue, batch_firstTrue, dropout0.3 ) self.attention nn.Linear(hidden_dim * 2, 1) self.fc nn.Linear(hidden_dim * 2, num_classes) def forward(self, x): out, _ self.lstm(x) # (batch, seq_len, hidden*2) attn self.attention(out).squeeze(-1) attn torch.softmax(attn, dim1) ctx (out * attn.unsqueeze(-1)).sum(dim1) return self.fc(ctx)训练时优化器用 Adam学习率 3e-4batch 64加 Cosine Annealing 调低学习率。类别权重用torch.nn.CrossEntropyLoss(weightclass_weight)因为 USTC 里不同词汇的样本数并不均匀。训练 30 轮左右验证准确率基本收敛再用 Early Stopping 防过拟合。3.2 YOLOv5 部分的训练配置如果你只需要跑通系统微调 YOLOv5 手部检测器的步骤可以压缩到最小。关键配置是数据集的 yaml 文件train: ./hand_dataset/train/images val: ./hand_dataset/val/images nc: 1 names: [hand]训练命令也很直接python train.py --data hand.yaml --weights yolov5s.pt --img 320 --epochs 30 --batch 16 --device 0训练完之后导出成 TorchScript 或 ONNX 做推理加速。CPU 环境下建议用 int8 量化推理速度能提升 2~3 倍精度损失很小手部检测这种任务完全够用。3.3 端到端推理流程把整条链路串起来的主循环长这样def predict_video(video_path, yolo_model, hands_model, classifier, window_size16): cap cv2.VideoCapture(video_path) seq [] old_box None predictions [] while True: ret, frame cap.read() if not ret: break # 1. YOLOv5 检测手部并平滑 boxes yolo_model(frame).xyxy[0].tolist() if len(boxes) 0: x1, y1, x2, y2 boxes[0][:4] old_box smooth_bbox([x1, y1, x2, y2], old_box) # 2. 裁剪 ROI提取关键点 roi frame[int(old_box[1]):int(old_box[3]), int(old_box[0]):int(old_box[2])] lm extract_landmarks(roi) else: old_box None lm None # 缺失帧用零向量占位后续做插值 if lm is None: lm np.zeros((21, 3)) seq.append(normalize_landmarks(lm, 1.0)) # 3. 攒够窗口就做一次预测 if len(seq) window_size: x torch.tensor([seq[-window_size:]], dtypetorch.float32) pred classifier(x) predictions.append(pred.argmax(dim1).item()) seq seq[1:] # 多数投票得到视频级结果 return max(set(predictions), keypredictions.count)推理实时性方面YOLOv5s 在 GPU 上单帧 10ms 左右MediaPipe 单帧 5ms 左右加上预处理开销整体能跑到 30 帧以上。如果部署在无 GPU 的机器上建议把 YOLOv5 替换成 yolov5n 并做 int8 量化MediaPipe 本身在 CPU 上就跑得很轻松。4. 常见问题与排查技巧实录4.1 检测不到手或漏检严重YOLOv5 在快速运动场景下漏检最常见的解决办法是下调置信度阈值到 0.3配合 IOU 后处理。还有一种情况是手部区域在画面里占比太小模型干脆没识别出来这时候要把输入分辨率从 320 提到 640但推理速度会掉一截。我的处理是先降阈值效果不够再把 ROI 放大两倍送进 MediaPipe。4.2 关键点跳变导致预测结果乱跳这是整套系统里最影响体验的问题。关键点本身在相邻帧之间只有微小位移但如果某一帧检测失败补了零向量序列里就会出现一个明显异常值BiLSTM 会把这个异常当成重要特征预测结果瞬间跳走。排查时我在一个测试视频上把每一帧的关键点坐标都打印出来观察才意识到问题出在补零。解决方案是检测失败时不急着补零而是用前两帧坐标的线性插值填充连续 5 帧都失败再降级用零向量。同时视频级预测不能直接用单帧 argmax我用「连续 5 次预测的滑动平均概率」做判决概率低于阈值的帧直接不输出避免动作间隙乱报。4.3 模型过拟合和类别不平衡USTC 不同词汇的样本数差距可以到数倍。一开始我直接拿原始数据训练验证集准确率只有 60% 出头而且低频类几乎全部预测错。除了加权损失函数数据增强也很关键。我用的增强手段包括关键点小幅度高斯扰动、时间轴随机缩放 0.9~1.1 倍、序列随机倒序注意倒序只用于训练推理时禁用。这套增强组合把低频类的 F1 提升了约 8 个百分点。4.4 环境配置和依赖冲突MediaPipe 依赖的 OpenCV 版本和 YOLOv5 有时会冲突典型表现是import cv2报 symbol not found。我建议在独立的虚拟环境里装依赖opencv-python统一用 4.5.x 版本MediaPipe 用 0.10 之后的版本基本能避开这个坑。另外MediaPipe 在部分旧版 numpy 上会报类型错误顺手升级numpy1.21就行。问题可能原因解决方案手部检测置信度始终低于阈值目标太小提高输入分辨率到 640关键点序列出现 NaNROI 越界裁剪前做边界 clamp验证准确率高但真实视频差训练测试同分布按视频而非帧划分数据推理速度只有 10 帧/秒模型没量化YOLOv5 换 int8 或换 yolov5n整套流程跑通之后我再回头看这套架构感觉技术选型反而是最简单的一步。真正值钱的是把检测、关键点、时间序列这三层粘合在一起的那些细节——漏检帧怎么补、关键点要不要归一化、窗口取多长、预测结果怎么平滑。这些在论文里往往一句话带过但恰恰是决定系统能不能在真实场景里稳定工作的关键。手语识别这个方向后续还能往上加连续语句识别、跨人泛化、甚至结合人体姿态做全身动作理解但从一个能跑的孤立词识别系统起步比一开始就追求大而全的方案要靠谱得多。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价