简介:基于Python与Mediapipe的手势数字识别项目源码,是面向计算机视觉及机器学习初学者的完整实现,涵盖手部追踪、关键点提取、模型推理与实时显示等环节。资源包共3个文件,含两个py脚本和一个md说明文档;一个脚本将Mediapipe手部关键点检测逻辑封装为可复用模块,另一个主程序负责启动摄像头、调用模型并输出数字结果,说明文档则提供项目介绍、安装步骤和运行指南;整体压缩包仅3KB,结构简洁清晰。已有416人学习下载。该项目的学习价值不止于代码本身,还体现在对Mediapipe框架原理、手部关键点特征工程(如关节间距、几何角度)以及支持向量机、随机森林或CNN等机器学习模型训练与调优思路的完整梳理。适合用于课程设计、毕业设计或快速搭建实时手势交互演示,是一份轻量且可直接运行的入门参考。
1. 手势数字识别,为什么用 mediapipe 而不是自己训一个模型
很多拿到“python 基于 mediapipe 实现手势数字识别机器学习项目源码.zip”这类压缩包的同学,第一反应是打开里面的模型文件,想看看网络结构。我的建议正好相反:先别碰模型,把手部关键点(hand landmark)这一步吃透。mediapipe 已经把“手在哪、关节怎么排”这个最脏最累的活做完了,你要解决的其实是剩下的特征工程和分类决策。
这个标题对应的方案,核心价值不在训练一个多深的神经网络,而是用 python 把开源检测模型、数据预处理和轻量分类器串成一个实时可跑的手势数字识别 demo。它能解决的是:摄像头前伸出一只手,实时告诉你这是 0 到 9 中的哪个数字。适合课程设计、自动化答辩演示、以及掏出来就能用的个人小工具。
接下来我按拿到源码后真正要走的流程讲:先看 mediapipe 到底给了你什么,怎么把环境跑通,特征怎么做,数据怎么采,以及最容易被忽略的五个坑。
2. 看源码前先立住模型认知:mediapipe 手势方案不是黑匣子,它有边界
2.1 从 palm detection 到 hand landmark:一个模型解决两个问题
MediaPipe Hands 不是一个端到端的“手势分类器”,它内部是一条两段式管线:第一段用 palm detection 在整张图上找手掌区域,第二段把找到的区域送进 hand landmark 模型,回归出 21 个手部关键点。
为什么要拆成两段?因为人手是一个可以在画面里平移、旋转、远近变化的刚性目标。直接在全图上回归 21 个点,模型要同时应付“找手”和“找关节”两个任务,精度和速度都吃亏。先定位手掌,相当于给第二阶段一个准确的 region proposal,后面只需要在裁剪区域里做回归,难度大幅下降,速度也更快。
另一个值得注意的点:在视频流里,palm detection 并不是每帧都跑。mediapipe 会先做一次检测,之后每帧用 hand landmark 模型跟踪关键点,只有跟踪丢了才重新检测。这个机制直接决定了源码里static_image_mode参数怎么设置,也决定了为什么手短暂出画再回来,画面还能接得上。
所以拿到源码先别急着跑,找到Hands(...)的初始化位置,看看传入的是static_image_mode=True还是False。前者适合单张图片,后者适合视频流,选错模式会带来两种完全不同的体验:图片模式每帧都做全图检测,视频里就会明显变卡;视频模式拿静态图一张张喂,反而会因为缺少连续帧信息而丢手。
2.2 为什么只回传 21 个关键点:数据简化才是它适合轻量落地的本质
21 个关键点意味着什么?一只手从自由度极高的连续曲面,被压成了 21 个离散锚点。每个点带 x、y、z 三个值,x 和 y 是相对图像宽高的归一化坐标,范围在 0 到 1 之间;z 是相对腕关节的深度,不是真实相机距离,而且不同手型下量级差异很大。
这里建议先熟悉一张索引表,后面做特征工程全靠它:
| 手指 | 指尖点索引 | 根部参考点 |
|---|---|---|
| 拇指 | 4 | 2 / 1 |
| 食指 | 8 | 5 |
| 中指 | 12 | 9 |
| 无名指 | 16 | 13 |
| 小指 | 20 | 17 |
写一段最基础的代码,把当前帧的 21 个点打印出来,确认拿到的数据长什么样:
import mediapipe as mp hands = mp.solutions.hands.Hands( static_image_mode=False, # 视频流场景必须用 False,会复用上一帧跟踪结果加速 max_num_hands=1, # 先只识别一只手,减少耗时 min_detection_confidence=0.5, min_tracking_confidence=0.5, ) def print_landmarks(image_rgb): results = hands.process(image_rgb) if not results.multi_hand_landmarks: print("no hand") return for lm in results.multi_hand_landmarks[0].landmark: print(round(lm.x, 4), round(lm.y, 4), round(lm.z, 4))这段代码的逻辑很简单:传入一帧 RGB 图像,mediapipe 返回手部关键点列表,每个点分别是 x、y、z。注意lm.x和lm.y不是像素坐标而是归一化坐标,后续要转回像素必须乘当前帧的宽和高,很多新手就是栽在这里:画出来的点全挤在画面左上角。
数据简化到这种程度,最大的收益是后续机器学习建模变得非常轻。你不用处理纹理、光照、背景这些对 CNN 影响巨大的因素,只需要在 63 维坐标上做文章。这也是为什么这类源码里几乎不会真的训练一个深度网络,逻辑回归、随机森林甚至简单规则就够了。
2.3 数字识别不靠端到端:规则、经典分类器与特征表达的取舍
明确了输入是 21 个关键点之后,下一个问题是怎么从关键点得到“这是数字几”。常见做法有三类,源码包里大概率是其中一种:
| 方案 | 代码量 | 新动作扩展性 | 适用场景 |
|---|---|---|---|
| 纯规则阈值 | 少,几十行 | 差,每个数字要单独定规则 | 只识别 0 到 5,实时性要求极高 |
| 逻辑回归 / 随机森林 | 中,特征提取加训练 | 中,换数字需重新采数据 | 固定数字集合,本标题场景 |
| 端到端 CNN | 大,需要大量标注数据 | 好,但成本太高 | 关键点检测不可靠的复杂场景 |
我自己的选择是第二种。纯规则看起来简单,但真实摄像头下手指弯曲程度千奇百怪,一个阈值很难覆盖不同手型和远近;端到端 CNN 在这个任务里属于杀鸡用牛刀,而且数据量根本喂不饱。逻辑回归配合好的特征,在这个任务上精度已经完全够用。
真正拉开效果差距的,是特征工程怎么做。原始坐标直接丢给分类器也能跑,但手在画面里的位置一动、离摄像头远近一变,坐标整体就变了,分类器就会犯迷糊。所以下一步必须做归一化和角度特征,这也是整个源码里最值得细读的部分。
3. 把源码跑起来:环境搭建、推理脚本与数据流这四件事先做对
3.1 先从 zip 里的源码倒推 dependencies:用 uv 还是 pip 安装
解压源码包之后,第一步是看目录结构。通常能看到main.py、train.py、data/、models/这类常见布局。如果压缩包里带了requirements.txt,直接用 pip 安装;如果没带,pip 安装这四件套基本能覆盖绝大多数情况:
unzip "一个python基于mediapipe实现手势数字识别机器学习项目源码.zip" -d hand_digits cd hand_digits # 创建虚拟环境,避免污染系统 Python python -m venv .venv source .venv/bin/activate # Windows 用 .venv\Scripts\activate pip install --upgrade pip pip install mediapipe opencv-python numpy scikit-learn为什么专门用虚拟环境?因为 mediapipe 对 numpy、opencv 的版本比较挑,系统里如果已经装了别的版本,很可能出现module has no attribute这类让人摸不着头脑的错误。虚拟环境相当于给这个项目一个后悔药,装坏了直接删掉重建,不影响其他工作。
如果你用的 Python 版本很新,比如 3.12 以上,安装 mediapipe 时可能会找不到对应 wheel。这是常见的翻车点,我一般会先降到 3.9 或 3.10,等所有依赖装好了再考虑升级。这一步不做,后面每跑一步都要和依赖搏斗,非常影响心情。
3.2 最小推理脚本:读摄像头、取手部关键点、打印坐标
装好依赖后,先不要碰源码里的业务逻辑,自己写一个最小脚本验证 whole pipeline 是通的。下面这段代码能打开摄像头、检测手、画关键点,是整个项目能跑起来的最小闭环:
import cv2 import mediapipe as mp mp_hands = mp.solutions.hands mp_draw = mp.solutions.drawing_utils hands = mp_hands.Hands( static_image_mode=False, max_num_hands=1, min_detection_confidence=0.5, min_tracking_confidence=0.5, ) cap = cv2.VideoCapture(0) while cap.isOpened(): ok, frame = cap.read() if not ok: break # 镜面翻转,让画面方向和屏幕一致 frame = cv2.flip(frame, 1) rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) results = hands.process(rgb) if results.multi_hand_landmarks: for hand in results.multi_hand_landmarks: # 归一化坐标转回像素坐标,用于绘图 h, w, _ = frame.shape for idx, lm in enumerate(hand.landmark): cx, cy = int(lm.x * w), int(lm.y * h) cv2.circle(frame, (cx, cy), 3, (0, 255, 0), -1) mp_draw.draw_landmarks(frame, hand, mp_hands.HAND_CONNECTIONS) cv2.imshow("hand tracking", frame) if cv2.waitKey(1) & 0xFF == ord("q"): break cap.release() cv2.destroyAllWindows()这段代码里有两个参数值得专研。第一个是min_detection_confidence,当手部检测置信度低于 0.5 时就认为没有手,调大能减少误检但会丢手;第二个是min_tracking_confidence,控制跟踪阶段的阈值,调得太高手离开画面后很难快速找回。后续做数字识别时,这两个参数几乎是必调的,但它们之间没有绝对最优解,得配合你的摄像头环境。
另一个关键是cv2.flip(frame, 1)。摄像头出来的画面默认是镜像的,如果不翻转,你伸出左手,屏幕里看到的却是右手,后面做左右手判定或者画坐标系时很容易被绕进去。
3.3 特征工程一:静态数字用归一化坐标,别用原始像素
跑通了摄像头,下一步就是把 landmark 变成能喂给分类器的特征。你可能会问机器学习中的数据处理是什么?这段就是答案:不是洗数据,而是把坐标换到不随手的位置、大小变化的参考系。
手在画面里忽左忽右、忽远忽近,原始像素坐标的绝对数值变化巨大,分类器会把这些变化当作有效信息去学习,结果就是同一只手换个位置就识别错。标准做法是以腕关节作为原点做相对归一化:
import numpy as np def normalize_by_wrist(landmarks): # landmarks: 21 个关键点,每个点带 x, y, z pts = np.array([[lm.x, lm.y, lm.z] for lm in landmarks]) wrist = pts[0] # 0 号点是腕关节 pts = pts - wrist # 以腕关节为原点 scale = np.max(np.linalg.norm(pts, axis=1)) + 1e-6 pts = pts / scale # 尺度归一化,抵消手离摄像头远近的影响 return pts.flatten() # 转成 63 维向量这里用np.linalg.norm算每个点到腕关节的距离,取最大值作为尺度因子。这样做的好处是手靠近摄像头时整体坐标变大,除以这个尺度后又被拉回来,分类器看到的特征相对稳定。1e-6是防分母为零的保险,镜头前没手时不会调用这个函数,但万一某帧坐标全为 0,不至于直接除零崩溃。
注意一个边界:这个归一化假设手腕是整只手的固定点。实际做数字手势时手腕确实不怎么动,但如果你想识别包含手腕大幅度旋转的动作,这个方案就不够用了,得换更复杂的对齐方式。
3.4 特征工程二:指尖夹角与几何特征,解决“比个耶”被误判
只做坐标归一化还不够。数字 1 和 2 的手型非常接近,一个只伸食指,一个伸出食指和中指,两者原始坐标的欧氏距离很小,分类器很容易混淆。真正有区分度的特征是指尖夹角——用“指尖、中间关节、指根”三个点算出一个角度,判断手指伸得直不直。
import math def angle_between(a, b, c): """计算以 b 为顶点,向量 ba 和 bc 的夹角,单位:度""" v1 = (a[0] - b[0], a[1] - b[1]) v2 = (c[0] - b[0], c[1] - b[1]) dot = v1[0] * v2[0] + v1[1] * v2[1] norm1 = math.hypot(v1[0], v1[1]) norm2 = math.hypot(v2[0], v2[1]) if norm1 == 0 or norm2 == 0: return 0.0 cos_theta = max(-1.0, min(1.0, dot / (norm1 * norm2))) return math.degrees(math.acos(cos_theta))逻辑就是向量点积求余弦再转角度。用中指举例:点 12 是指尖,点 10 是中间关节,点 9 是指根,三点夹角越接近 180 度,说明这根手指伸得越直;越接近 90 度甚至更小,说明手指是弯的。
在实际特征拼接时,我会对五根手指各取一组角度,拇指用 4-2-1,食指用 8-6-5,中指用 12-10-9,无名指用 16-14-13,小指用 20-18-17,得到 5 个角度值,直接追加到归一化坐标后面。这样特征从 63 维变成 68 维,分类器区分 1 和 2、2 和 3 这类相近数字的能力会明显提升。
4. 让数字识别从“能动”到“可用”:训练数据、后处理与实时性调优
4.1 自己录一遍数据:为什么通用手势数据救不了你的摄像头
先回到机器学习应用流程的第一环:数据。有人会想,既然 mediapipe 输出的是抽象关键点,那用公开手势数据集训练不就行了?理论上可以,但实际效果往往很差。公开数据集里的摄像头角度、手大小比例、光照和你本机完全不一致,关键点坐标分布也差很远。我见过太多人拿开源数据集训练,指标很漂亮,一接自己摄像头就翻车的情况。
通用做法是自己花十分钟录一份数据。脚本不需要多复杂,核心是让每个数字都有足够的样本,并且手部有轻微移动、旋转:
import cv2 import numpy as np label = int(input("输入当前手势数字(0-9),按回车开始采集:")) features = [] while len(features) < 120: ok, frame = cap.read() if not ok: continue frame = cv2.flip(frame, 1) rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) results = hands.process(rgb) if results.multi_hand_landmarks: lm = results.multi_hand_landmarks[0].landmark feat = normalize_by_wrist(lm) # 3.3 节的坐标归一化 angle_feat = extract_angles(lm) # 3.4 节的角度特征 features.append(np.hstack([feat, angle_feat])) print(f"已采集 {len(features)} / 120") cv2.imshow("collect", frame) if cv2.waitKey(1) & 0xFF == ord("q"): break np.save(f"data/{label}.npy", np.array(features))采集时注意控制数据质量,不要对着摄像头一动不动,那样录进去的 120 帧几乎是一模一样的重复样本,模型学不到任何变化。正确做法是让手轻微左右翻转、前后移动一点,模拟真实使用时的状态。每个数字 120 帧是我个人比较推荐的起点,太少容易过拟合,太多采集过程会让人失去耐心。
4.2 数据增强与类别均衡:轻微旋转、平移和缩放就够了
如果你的某个数字只采了五六十帧,或者发现不同类别的样本量差距很大,可以在坐标层面做增强。坐标增强比图像增强便宜得多,不需要过 GPU,直接在 numpy 数组上做变换就行:
def augment(points, rot=0.1, shift=0.02, scale_range=(0.9, 1.1)): # points: (63,) 或 (68,) 的向量,先还原成 (21, 3) 再做几何变换 p = points.reshape(21, 3).copy() # 绕 z 轴小角度旋转,模拟手腕转动 theta = np.random.uniform(-rot, rot) R = np.array([[np.cos(theta), -np.sin(theta)], [np.sin(theta), np.cos(theta)]]) p[:, :2] = p[:, :2] @ R.T # 轻微平移,模拟手在画面中位置变化 p[:, :2] += np.random.uniform(-shift, shift, size=2) # 随机缩放,模拟手离摄像头远近变化 scale = np.random.uniform(*scale_range) p *= scale return p.flatten()参数选择上,旋转幅度不要超过 0.1 弧度,大概 6 度左右,因为正常人做数字手势时手腕不会扭得特别夸张;平移 0.02 对应画面宽度的 2%,已经能覆盖日常位置抖动;缩放范围 0.9 到 1.1,相当于手在摄像头前不超过 10% 的距离变化。
必须提醒一句:坐标增强做的是小扰动,不是把样本变魔术。旋转 30 度、平移半个画面,出来的样本根本不符合真实手势分布,反而会把模型训坏。这步调参多少有点玄学,但核心原则是“增强后的样本仍然像一个真人在摄像头前做手势”。
4.3 推理延迟的三个瓶颈:模型载入、图像缩放、每帧处理
实时数字识别最怕的就是画面卡顿。很多源码 demo 跑起来只有十几帧,问题往往不在 mediapipe 模型本身,而在数据流链路。
第一个瓶颈是图像分辨率。如果你的摄像头默认输出 1080p,每一帧要处理约 200 万像素,即使 mediapipe 内部会缩放,前处理开销依然很大。常规做法是先resize到 640x480 再送入模型。第二个瓶颈是逐帧推理,其实手势在连续两帧之间变化极小,完全可以通过跳帧把推理频率降下来。第三个瓶颈是显示链路,cv2.imshow本身在高分辨率下也会拖慢主循环。
frame = cv2.resize(frame, (640, 480)) frame_count += 1 if frame_count % 2 == 0: # 偶数帧不做推理,直接沿用上一帧的关键点结果 results = last_results else: results = hands.process(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)) last_results = results跳帧相当于在延迟和平滑度之间做权衡。30fps 的视频流下跳一帧,引入的延迟大约 33 毫秒,体感上基本无感,但推理负载直接减半。如果跳帧后跟踪容易丢,可以把min_tracking_confidence适当调高,让模型更依赖上一帧的几何信息去接续。
4.4 训练脚本与混淆矩阵:认真看哪些数字在互相打架
数据采集和增强做完,就可以训练分类器了。我用 Logistic Regression 而不是随机森林,因为逻辑回归的决策边界更平滑,在特征维度不高时不容易过拟合,而且训练和推理都快。核心代码:
from sklearn.linear_model import LogisticRegression from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler from sklearn.metrics import confusion_matrix # load_all_data 负责读取 data/ 下所有 npy 文件,返回特征矩阵和标签 X, y = load_all_data("data") X_train, X_val, y_train, y_val = train_test_split( X, y, test_size=0.2, random_state=42, stratify=y ) scaler = StandardScaler().fit(X_train) X_train_scaled = scaler.transform(X_train) X_val_scaled = scaler.transform(X_val) clf = LogisticRegression(max_iter=500, C=1.0) clf.fit(X_train_scaled, y_train) print(confusion_matrix(y_val, clf.predict(X_val_scaled)))逻辑说明:StandardScaler把特征缩放到零均值单位方差,逻辑回归对特征尺度敏感,这步不能省。stratify=y保证训练集和验证集的类别比例一致,避免某个数字在验证集里恰好没有样本。C=1.0是正则化强度的默认值,过拟合时调小,欠拟合时调大。
混淆矩阵的价值在于告诉你哪些数字在互打架。如果 3 和 8 频繁互判,说明这两个手型的角度特征太接近,这时候该做的不是盲目堆数据,而是回去检查特征设计;如果 1 和 2 互判,先确认是否加了指尖角度特征,只靠坐标归一化很难分开它们。
5. 避坑与常见问题排查:mediapipe 手势识别最容易翻车的五个地方
5.1 现象:摄像头能开但没画上手部连线
摄像头画面正常,但 imgshow 里始终没有手部关键点连线,或者画出来的点全堆在画面边缘。
原因:最常见的是没有做 BGR 到 RGB 的转换。mediapipe 的Hands.process()要求输入 RGB 图像,而 OpenCV 读取视频帧得到的是 BGR,直接把 BGR 矩阵送进去,模型看到的颜色通道是反的,关键点定位自然失败。另一个原因是显示用错了图像,你把转换后的 RGB 图像直接imshow,颜色会整体偏蓝。
解决:先打印results.multi_hand_landmarks,如果一直是 None,检查cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)是否执行;然后确认展示时用的是原始frame(BGR),而不是转换后的rgb。mediapipe 只管推理,不管显示,很多源码在这两行之间切换时搞混了。
5.2 现象:数字 1 和 2 频繁互判,怎么调都压不下去
识别结果在 1 和 2 之间来回跳,尤其是手指微微弯曲时,模型一会儿判成 1,一会儿判成 2。
原因:手型太接近了。只伸食指和同时伸出食指、中指,两者在归一化坐标上的差异很小。如果只用了 63 维坐标特征,分类器很难找到一个稳定的决策边界。
解决:把指尖夹角特征加上,用食指和中指的伸直程度作为区分依据。还可以在训练数据里专门加入一些“手指半弯”的样本,让模型知道 1 和 2 的边界在哪里。这里不建议直接调逻辑回归的C值,问题出在特征空间,不是分类器复杂度。
5.3 现象:手一靠近摄像头就乱跳
手离摄像头越近,关键点位置就越不稳定,识别结果在几个数字之间跳来跳去,根本没法用。
原因:手靠近时,手掌在画面里占的面积过大,部分手指可能超出画面边缘,landmark 模型只能靠猜测补全缺失关键点。同时近景下同一个关键点在相邻帧之间的像素位移被放大,任何微小抖动都会被模型放大。
解决:把min_detection_confidence和min_tracking_confidence同时提到 0.6 到 0.7,让模型在置信度不够时直接不输出结果,而不是硬给一个错误预测。再加一层指数平滑,让关键点坐标不会因为单帧误差剧烈跳变:
smoothed = smoothed * 0.7 + current_point * 0.3平滑系数 0.7 表示更信任历史值,0.3 表示接受当前值。系数越大越平滑但延迟越高,实时场景里 0.7/0.3 是一个比较平衡的起点。这本质上是拿延迟换平滑,不可能完全消除代价。
5.4 现象:CPU 占用拉满,风扇狂转
摄像头一开,CPU 占用率直接到 100%,画面帧率却只有十几帧。
原因:摄像头默认输出 1080p,每一帧都在全分辨率上跑推理。很多人忽略了resize,觉得反正 mediapipe 内部会处理,但前处理的开销是实打实的。再加上显示窗口和主循环在同一线程,加载一旦跟不上就开始掉帧。
解决:先resize到 640x480 再送process(),这一步能省掉一大半计算量;然后按 4.3 节的思路做跳帧;如果还是不够,把推理单独丢到一个线程里跑,主线程只负责显示结果。线程方案写起来麻烦一点,但这是树莓派这类低功耗设备上能不能跑起来的决定性因素。
5.5 现象:模型自己玩得转,一换电脑就出错
同一个项目在自己电脑上跑得好好的,换一台电脑装完依赖就报错,比如 numpy 版本冲突、cv2 属性找不到。
原因:依赖没有被锁定。源码包里只写了pip install mediapipe,没锁版本,换电脑时装到的是最新版,而最新版往往和旧代码不兼容。mediapipe 版本一升,API 参数名变了;opencv 版本一升,某些图像处理函数的返回值类型变了。
解决:把requirements.txt中每个包的版本写死,比如mediapipe==0.10.x、numpy==1.24.x。这个操作看起来不起眼,但能避免你花半天时间排查一个根本不是自己代码的问题。我一般还会在项目根目录放一个setup_env.sh,把建环境、装依赖、验证摄像头三步都写进去,换机器后一键执行。
6. 最后一层包装:把单帧识别结果变成时间序列输出
单帧分类的结果出现偶发抖动,是靠调参消不掉的,因为摄像头输入本身有噪声,手再稳也会有微米级的位移。与其和每一帧死磕,不如换一个思路:把“当前显示什么数字”当成一个时间序列问题,用滑动窗口做多数表决。
from collections import deque class VoteFilter: def __init__(self, window_size=5): self.window = deque(maxlen=window_size) def push(self, pred): self.window.append(pred) # 返回窗口内出现次数最多的类别 return max(set(self.window), key=self.window.count)用法很直接,每一帧模型输出一个预测结果,不是立刻显示,而是先塞进这个队列,然后取队列里出现次数最多的数字当作最终输出。窗口大小 5 时大约引入 5 帧的延迟,按 30fps 算不到 200 毫秒,体感上可以接受;如果你发现抖动还是很明显,就把窗口加到 9,代价是延迟接近 300 毫秒,按下和出结果之间有明显的迟钝感。
我还习惯让这个过滤器额外承担一个任务:把手离开画面时输出一个特殊值,而不是沿用上一个数字。否则手刚离开摄像头,分类器可能还会给出一个置信度很低的随机预测,再加上投票窗口的滞后作用,屏幕上会停留一个明显错误的结果。
我最早做手势识别时,被单帧忽大忽小的分类结果坑了很多次,后来养成一个习惯:先把单帧识别当成一个信号,再交给时间维度去纠偏。这个习惯改变的不只是识别率,更是排查问题的思路——当你不再纠结每一帧的对错,而是看一段时间的输出是否一致,很多问题会自己浮出来。希望帮到你。
本文还有配套的精品资源,点击获取