拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于MediaPipe的手势控制鼠标:从检测到交互的完整实现

基于MediaPipe的手势控制鼠标:从检测到交互的完整实现

手头这个项目断断续续做了半个月,踩了不少坑,也去掉了一些想当然的设计,最后总算跑出了一套还算稳定的手势控制鼠标方案。今天把完整思路、代码和调试过程一次性整理出来,给同样在玩机器视觉、想做AI视觉交互的朋友一个可复现的参考。

先说结论:方案用的是 OpenCV + MediaPipe Hands + pyautogui,实现食指控制鼠标移动、拇指与食指捏合触发点击,同时带平滑滤波和灵敏度调节。整个代码量不大,但要把"能检测到手"变成"鼠标真的好用",中间差了很大一段距离,这篇文章主要就讲这段距离是怎么填上的。

如果你是刚接触机器视觉的初学者,这篇文章可以帮你把"检测"和"控制"这两件事串起来;如果你已经有手部识别的经验,那重点看第四节,那部分全是实测才会遇到的坑。

1. 为什么做手势控制鼠标:应用场景与技术选型分析

1.1 什么人需要用手势控制鼠标

最早想做这个,是因为看到不少做演示汇报的朋友在台上讲PPT时,总得回头去够翻页笔或者时不时回电脑前点两下。手势控制能让人彻底离开桌面,站在摄像头前就能完成基本的鼠标操作。

更实际的需求其实来自辅助场景。有些用户不方便使用传统鼠标,手部只要还能做一些微小的动作,就能通过摄像头转化为鼠标指令,这种替代方案的成本远低于专门的眼动仪或头控设备。此外,手势交互在智能家居、工业无尘车间(不能碰触控屏的场景)、医疗手术室的非接触式操作里,都是刚需方向。

从技术角度看,这个项目是机器视觉入门到进阶非常典型的一个里程碑。它不涉及训练模型,但完整体验了"图像采集 -> 目标检测 -> 关键点提取 -> 业务逻辑映射 -> 系统层控制"这一整套视觉落地的流程。这套流程学会了,换成识别其他物体、控制其他设备,逻辑完全一致。

1.2 技术方案对比:为什么选 MediaPipe

做手势识别,摆在我面前的有三条路线,我分别做了验证,结果很能说明问题:

方案优点缺点我的实测结论
OpenCV 传统肤色检测 + 轮廓识别无需额外依赖,原理直观光照影响极大,背景稍复杂就崩溃,只能得到轮廓拿不到关键点只在纯色背景下能玩,换到办公室直接不可用
自训练 YOLO / 关键点网络可控性强,可定制自己的手势集需要标注数据、训练时间、GPU资源,部署体积大对本项目来说严重超配,几千张标注换来的提升不值当
MediaPipe Hands开箱即用、CPU 实时运行、直接输出21个手部关键点对遮挡敏感、极端光照有衰减开发效率和稳定性最均衡,最终选用

MediaPipe Hands 是 Google 开源的机器学习方案,它先通过 palm detection 在整张图中找到手掌区域,再在区域内执行 hand landmark 模型回归出 21 个关键点。这种"先检测再定位"的两阶段设计,比直接在全图上回归关键点的方案稳定得多,因为手掌检测的召回率高,给了后续关键点定位一个良好的初始化区域。

需要说明的是,MediaPipe 并不是唯一选择,后来官方还推出了 MediaPipe Tasks 框架里的 Hands 任务,API 更新但思路一致。大家如果看到网上教程用的是mp.solutions.hands,就是我这篇文章用的版本,兼容性最好。

1.3 鼠标控制库的取舍

Python 里控制鼠标主要有 pyautogui 和 pynput 两个库。pyautogui 的优势是 API 极简,跨平台支持 Windows、macOS、Linux,moveTo、click一行搞定,非常适合作为视觉项目的输出端。

pynput 的优势是底层事件粒度更细,可以监听和模拟更复杂的输入。我最终选了 pyautogui,原因在于这个项目只需要最基本的移动和点击,pynput 的事件监听能力用不上。但如果你后续要做"按住拖动"这类操作,pyautogui 的drag参数不够细腻,到时候再考虑换 pynput 也不迟。

2. 控制逻辑设计:从21个关键点映射到鼠标事件

2.1 手部关键点编号与选择策略

MediaPipe Hands 检测到的 21 个关键点有自己的固定编号,理解这些编号是写控制逻辑的第一步。我用到的关键点是:

  • 0:手腕根部
  • 4:拇指指尖
  • 8:食指指尖
  • 12:中指指尖

为什么选这几个?因为鼠标控制的核心动作是"移动"和"点击"。移动适合用单指点位,食指是人类指点动作中最自然的器官,所以食指指尖(8号点)作为鼠标光标的位置来源。点击则用拇指和食指的捏合动作来判定,因为捏合是一个明确的、不会和移动混淆的二元状态。

2.2 坐标转换:摄像头图像坐标到屏幕坐标

摄像头采集到的图像坐标和电脑屏幕坐标之间是两个不同的坐标系。OpenCV读取的帧是左上角为原点的像素坐标系,屏幕也是左上角原点,但两者的宽高不同,直接套用会造成光标飞出屏幕或只在局部区域移动的问题。

我的做法是做一个线性映射,把摄像头画面中手的位置按比例投影到屏幕:

screen_x = int(hand_x_normalized * screen_width) screen_y = int(hand_y_normalized * screen_height)

这里有个细节很多人第一次写会忽略:MediaPipe 输出的关键点坐标是归一化坐标,值域在 0 到 1 之间,表示相对图像宽高的比例。所以不需要拿原始像素坐标来换算,直接用归一化值乘以屏幕宽高即可。

但直接线性映射有个体验问题:手在摄像头画面里稍微动一点,屏幕上的光标会放大地移动,导致手抖被无限放大。这就需要引入平滑和灵敏度控制。

2.3 平滑算法与灵敏度系数

我试过三种处理手抖的方案:

  1. 直接映射:光标剧烈抖动,几乎不可用。
  2. 均值滤波:取最近 N 帧坐标的平均值,能消除抖动但有明显延迟,手停住后光标还要"飘"一会。
  3. 指数移动平均(EMA):current = alpha * target + (1 - alpha) * previous,效果最好。

EMA 的原理用大白话说就是:不让光标瞬间跳到目标位置,而是每帧朝目标靠近一点,alpha 决定了靠近的速度。alpha 越大越跟手,越小越平滑。我实测 alpha = 0.6 左右是一个不错的平衡点。

灵敏度则通过一个缩放系数来实现:

offset_x = (target_x - previous_x) * sensitivity

默认 sensitivity = 1.0,手移动多少光标移动多少;调大到 2.0,手只需移动一半距离就能让光标走完全屏,适合屏幕大或摄像头离得远的情况。我这里把灵敏度变成了一个可以实时调整的参数,用小键盘的加号减号控制,调试手感非常方便。

2.4 点击判定与误触发防护

点击判定用拇指指尖(4号点)和食指指尖(8号点)的欧氏距离实现,小于阈值判定为捏合点击。但直接拿这个判定会在一个状态下连续触发多次点击,因为只要两个手指保持捏合,每一帧距离都小于阈值。

解决方式是用"进入捏合状态的那一帧触发点击,松开的帧只更新状态不触发",也就是边沿触发而非电平触发。伪代码如下:

if distance < threshold and not pinching: pyautogui.click() pinching = True elif distance >= threshold: pinching = False

这个逻辑很关键,不加的话会出现按住鼠标不放的效果,实测中非常明显。

另一个误触发问题是摄像头画面里出现多只手或者手出现在脸颊旁边时,关键点检测可能会跳变。我在实际代码里加入了"只有画面中最大的一只手参与控制"的策略,避免两只手同时在画面里时光标被拉来拉去。

3. Python完整代码实现:每一行都有它的作用

3.1 环境准备与依赖安装

在开始写代码之前,先把依赖装好。推荐用虚拟环境,不要直接往全局环境里装,避免和已有项目打架:

python -m venv gesture_env source gesture_env/bin/activate # Windows: gesture_env\Scripts\activate pip install opencv-python mediapipe pyautogui numpy

版本方面,我用的组合是 opencv-python 4.8+、mediapipe 0.10+、pyautogui 0.9.54。mediapipe 0.10 之后 API 稳定了不少,老教程里如果用的是 0.8 版本,接口基本一致,问题不大。

有一个安装时很容易踩的坑:pyautogui 在 macOS 上需要辅助功能权限(系统设置 -> 隐私与安全性 -> 辅助功能),Windows 和 Linux 一般不需要。第一次运行如果发现鼠标没反应,先检查这个,不要怀疑代码。

3.2 完整代码(可直接复制运行)

下面这份代码是在我实际项目中精简出来的版本,保留全部核心功能,去掉了一些只适用于我特定场景的配置,方便直接复用。

import cv2 import mediapipe as mp import pyautogui import numpy as np import math import time # 初始化 MediaPipe Hands mp_hands = mp.solutions.hands hands = mp_hands.Hands( static_image_mode=False, max_num_hands=1, # 只追踪一只手,避免多手干扰 min_detection_confidence=0.7, min_tracking_confidence=0.5 ) mp_draw = mp.solutions.drawing_utils # 屏幕宽高 screen_w, screen_h = pyautogui.size() # 控制参数(可实时调节) sensitivity = 1.2 # 灵敏度,越大光标移动越快 smoothening = 0.6 # 平滑系数,越大越跟手,越小越稳 pinch_threshold = 0.035 # 捏合判定阈值(归一化距离) click_cooldown = 0.3 # 点击冷却时间,防止连点 # 状态变量 prev_x, prev_y = 0, 0 current_x, current_y = 0, 0 is_pinching = False last_click_time = 0 is_active = True # 手势控制总开关 def get_hand_landmarks(frame): """ 对一帧图像进行手部检测,返回关键点列表。 返回 None 表示未检测到手。 """ rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) rgb_frame = cv2.flip(rgb_frame, 1) # 镜像翻转,让左右手操作符合直觉 results = hands.process(rgb_frame) if not results.multi_hand_landmarks: return None return results.multi_hand_landmarks[0] def get_distance(landmark1, landmark2): """ 计算两个关键点之间的欧氏距离。 输入为 mediapipe 的 landmark 对象,坐标为归一化值。 """ return math.hypot( landmark1.x - landmark2.x, landmark1.y - landmark2.y ) def map_to_screen(landmark): """ 将归一化关键点坐标映射到屏幕坐标。 """ target_x = landmark.x * screen_w target_y = landmark.y * screen_h return target_x, target_y # 打开摄像头 cap = cv2.VideoCapture(0) cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) print("手势控制已启动。") print("按 q 退出 | 按 a 打开/关闭手势控制 | 按 +/- 调节灵敏度") while cap.isOpened(): ret, frame = cap.read() if not ret: break # 镜像显示,保证画面和实际操作方向一致 frame = cv2.flip(frame, 1) frame_h, frame_w, _ = frame.shape # 检测手部关键点 hand_landmarks = get_hand_landmarks(frame) if hand_landmarks: # 画关键点和连线(可视化用) mp_draw.draw_landmarks(frame, hand_landmarks, mp_hands.HAND_CONNECTIONS) # 提取食指指尖(8号点)和拇指指尖(4号点) index_tip = hand_landmarks.landmark[8] thumb_tip = hand_landmarks.landmark[4] if is_active: # 1. 计算目标位置(带灵敏度) raw_x, raw_y = map_to_screen(index_tip) # 用上一帧的位置作为基准,计算偏移量并乘以灵敏度 offset_x = (raw_x - current_x) * sensitivity offset_y = (raw_y - current_y) * sensitivity target_x = current_x + offset_x target_y = current_y + offset_y # 边界保护,防止光标移出屏幕 target_x = max(0, min(screen_w - 1, target_x)) target_y = max(0, min(screen_h - 1, target_y)) # 2. 指数平滑 current_x = current_x + (target_x - current_x) * smoothening current_y = current_y + (target_y - current_y) * smoothening # 3. 控制鼠标移动 pyautogui.moveTo(int(current_x), int(current_y), duration=0) # 4. 捏合检测 -> 点击 pinch_dist = get_distance(thumb_tip, index_tip) current_time = time.time() if pinch_dist < pinch_threshold and not is_pinching: if current_time - last_click_time > click_cooldown: pyautogui.click() last_click_time = current_time is_pinching = True elif pinch_dist >= pinch_threshold: is_pinching = False # 显示捏合状态和距离信息 status = "PINCH" if is_pinching else "MOVE" cv2.putText(frame, f"Status: {status}", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) cv2.putText(frame, f"Dist: {pinch_dist:.3f}", (10, 70), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 1) else: cv2.putText(frame, "GESTURE CONTROL OFF", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2) # 如果检测到手,显示当前模式 if hand_landmarks: cv2.putText(frame, f"Sensitivity: {sensitivity:.1f}", (10, frame_h - 40), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 0, 0), 2) else: cv2.putText(frame, "No Hand Detected", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2) # 显示画面 cv2.imshow("AI Gesture Mouse Control", frame) # 键盘控制 key = cv2.waitKey(1) & 0xFF if key == ord('q'): break elif key == ord('a'): is_active = not is_active print("手势控制已开启" if is_active else "手势控制已关闭") elif key == ord('+') or key == ord('='): sensitivity = min(3.0, sensitivity + 0.1) print(f"灵敏度: {sensitivity:.1f}") elif key == ord('-') or key == ord('_'): sensitivity = max(0.5, sensitivity - 0.1) print(f"灵敏度: {sensitivity:.1f}") cap.release() cv2.destroyAllWindows()

3.3 代码关键部分的逐段拆解

上面这段代码一共分为几个模块,我挑几个容易写错的点单独讲。

为什么必须做镜像翻转。摄像头采集的画面相当于从你的视角看过去,但如果你不处理,抬手往左动,光标会往右跑。cv2.flip(frame, 1)和关键点提取前对 RGB 帧做同样的翻转,能保证"我往左抬手,光标往左走"这个直觉对应关系。新手最容易漏掉的就是只翻转了显示帧,没有翻转送入检测器的帧,导致显示是对的但控制是反的。

为什么 max_num_hands=1。多手检测在视觉上好看,但对控制类应用是灾难。两只手同时在画面里时,模型输出的手掌 ID 可能漂移,光标会被另一只手抢走。我测试时发现,只要设置max_num_hands=1,MediaPipe 会优先输出检测置信度最高的那只手,稳定性有明显提升。

为什么 click_cooldown 要有 0.3 秒。虽然前面做了边沿触发,但实际捏合动作不是瞬间完成的,手指从分开到捏合再到分开的过程中,距离阈值附近可能出现多次抖动,表现为一次捏合触发两三次点击。加冷却时间是最粗暴有效的方法,把 0.3 秒内的重复触发全部屏蔽掉。

为什么 map_to_screen 函数里没有直接用原始像素坐标。归一化坐标的一个重要好处是分辨率无关。你换一个摄像头,或者把采集分辨率从 640x480 改成 1280x720,归一化坐标值不变,映射逻辑一行都不用改。这也是在做视觉项目时一个值得养成的好习惯:模型输出的中间结果尽量用归一化坐标在业务层流转,最后一步再换算到具体物理坐标系。

3.4 初次运行时的参数基线

如果你的运行环境和我差不多(普通笔记本摄像头、室内灯光、摄像头距离人手 30-50 厘米),建议用下面的参数作为起点:

  • min_detection_confidence:0.7
  • min_tracking_confidence:0.5
  • smoothening:0.6
  • sensitivity:1.2
  • pinch_threshold:0.035

这组参数跑起来之后,先不要追求快,把手放在画面里慢慢移动,感受光标的跟手程度,再微调 smoothening 和 sensitivity。捏合阈值我给的 0.035 是归一化距离,换算到 640x480 画面里大约是 22-26 个像素,具体的拇指食指实际距离在 3-4 厘米左右。如果你的手比较大或者摄像头距离远,这个阈值需要适当上调。

4. 实测踩坑记录:从"能检测到手"到"鼠标好用"的距离

4.1 坑一:摄像头分辨率过高导致延迟掉帧

一开始我把采集分辨率设成了 1280x720,想着画面越清晰检测越准。跑起来发现帧率只有 15 FPS 左右,光标有明显的迟滞感,而且手指快速移动时检测结果会闪现跳变。

原因很简单:MediaPipe 虽然优化得很好,但 720p 的画面在 CPU 上推理耗时还是明显超过 480p。而且高分辨率并不会让关键点更准,因为 MediaPipe 内部会先把图像缩放成固定输入尺寸(通常是 256x256),你给它的分辨率再高,它也只会先缩小再检测,等于做了无用功。

最终把分辨率压在 640x480,帧率稳定在 30 FPS,检测精度没有可感知的下降。如果你的 CPU 比较弱,甚至可以降到 480x360,照常可用。

4.2 坑二:光照变化导致检测突然丢失

办公室靠窗的位置,下午阳光斜着照进来,手背上会形成高光区域。这种情况下 MediaPipe 偶尔会出现检测框抖动甚至完全丢失手部的情况。

排查后发现两个问题:一是背景阳光直射摄像头的方向,导致整体过曝;二是手部肤色在强光下和浅色桌面背景对比度下降。

解决方案非常朴素:调整位置让光源从侧前方而不是正对摄像头方向照射;同时把min_detection_confidence从 0.5 提高到 0.7,过滤掉低置信度的误检,宁可不检测也不错检。

从视觉检测的通用规律来说,光照永远是传统 CV 机器学习模型最敏感的外部扰动,MediaPipe 虽然对光照做了大量泛化,但极端的明暗对比依然会让它失效。做实际项目时,控制光照环境比调模型参数的成本低得多,也有效得多。

4.3 坑三:手和脸同框时检测结果被"抢走"

这是个很有画面感的坑:当你坐在电脑前,手抬起来准备控制鼠标,脸和手同时出现在摄像头画面里。MediaPipe 的手部检测用的是全身关键点模型中独立训练的手掌检测器,理论上不会和脸混淆,但当手距离脸很近(比如手放在下巴附近)时,模型偶尔会把脸部区域误判为手掌区域,输出一个非常不稳定的关键点集合。

我的策略分三层:

  1. 设置max_num_hands=1,减少多目标竞争。
  2. 在业务层不做额外过滤,因为 MediaPipe 输出已经不包含目标类别置信度。
  3. 在物理层约束用户习惯:尽量让手在身体侧前方操作,不要贴近脸部。

第三层听起来很"不技术",但实际效果最好。很多视觉项目的稳定性问题,最后都是通过使用习惯约束解决的,这也算是一个行业共识。

4.4 坑四:pyautogui 在 macOS 上没有反应

代码本身没问题,但 macOS 上运行时光标就是不动。查了很久才发现是权限问题:终端或 IDE 没有被授予"辅助功能"权限,pyautogui 的 moveTo 调用被系统静默拒绝,连错误都不报。

这种情况在 Windows 和 Linux 上基本不会遇到,但 macOS 用户需要专门去系统设置里勾选权限。另外,如果你是在虚拟环境里运行的,授权对象是承载 Python 进程的终端应用(比如 Terminal 或 iTerm),不是 Python 本身。

4.5 坑五:捏合点击误触发率居高不下

初期测试时,明明只是想移动鼠标,手指稍微靠近一点就触发了点击,误触率高到没法正常使用。

定位到两个原因:一是 pinch_threshold 设得太宽松,0.05 的阈值对于我的摄像头距离来说,手指分开 5 厘米就会触发;二是 thumb_tip 和 index_tip 的坐标在拇指和食指交叉时会出现极短时间的距离塌缩。

解决方式是同时收紧阈值和调整手指动作习惯,要求大拇指和食指明确接触到一定程度才触发点击。经过实测,0.03 到 0.04 之间的阈值是安全区间,低于 0.03 会出现想点击却点不上的情况,高于 0.04 就会频繁误触发。

4.6 坑六:"悬空移动"时的微小抖动

即使加了 EMA 平滑,手指悬停时还是会有几个像素的抖动,导致鼠标无法精准点击小按钮。

进一步提高 smoothening 会增加延迟,不太可取。我的解决思路是增加一个"静止判定":如果目标位置在连续 5 帧内的变化不超过 3 个像素,就认为手部处于静止状态,此时光标冻结在当前坐标,直到检测到超过阈值的移动再解锁。这个方案在精准点击场景下效果显著,代码实现也不复杂:

# 伪代码:静止冻结 if abs(current_x - prev_x) < freeze_threshold and abs(current_y - prev_y) < freeze_threshold: freeze_counter += 1 else: freeze_counter = 0 if freeze_counter > 5: # 光标位置不再更新 move_mouse = False else: move_mouse = True

不过这个逻辑在最终发布的代码里被我删掉了,因为会增加参数数量,对新手来说理解成本偏高。如果你想追求更好的点击体验,可以自行加回去,freeze_threshold设 3-5 像素、freeze_counter设 5 帧是比较稳妥的组合。

5. 从"能用"到"好用":交互体验细节打磨

5.1 总开关的必要性

如果你只是演示给朋友看,没有总开关问题不大。但实际在电脑前正常办公、聊天、写代码时,手势控制如果一直在后台运行,手部稍微动一下就会抢走鼠标控制权。

我加的is_active开关用键盘的a键切换,默认启动时不激活,需要时按一下开启,用完了再按一下关闭。这个小设计让这个项目从"演示玩具"变成了"日常工具"。键盘与手势的配合也是实际交互系统中常见的设计思路:手势擅长做空间控制,键盘擅长做模式切换,两者结合才是完整的交互闭环。

5.2 灵敏度动态调节与记忆功能

不同使用场景对灵敏度的需求差异很大。做 PPT 演示时,鼠标在屏幕上大幅移动,灵敏度要高;做精细的截图标注时,灵敏度要低,让光标慢慢挪。

除了键盘加减号实时调节,我在后续版本里做了一层配置持久化,把灵敏度、平滑系数和捏合阈值存到一个 json 配置文件里,启动时读取。参数不落到本地文件,每次重开程序都回默认值,反复调试会很崩溃。

5.3 FPS 监控与性能基线

为了让性能问题可视化,我加了 FPS 显示到画面左上角。实测数据如下:

采集分辨率MediaPipe 推理耗时整体帧率体感
1280x720约 40ms15-18 FPS有明显迟滞
640x480约 20ms28-32 FPS流畅可用
480x360约 15ms33-35 FPS流畅,但小范围移动精度下降

如果你的设备帧率低于 20 FPS,优先调低分辨率,而不是去优化代码。MediaPipe 的推理耗时占了大头,业务逻辑本身开销很小。

5.4 交互热区与屏幕边缘处理

还有一个容易被忽略的细节:手在摄像头画面里移动时,光标映射到屏幕边缘会产生"撞墙"感,因为手的移动范围是有限的,但光标到达屏幕边缘后继续移动不会产生任何反馈。

我做了两个优化:一是把手的移动范围从整个摄像头画面缩小到中间的 80% 区域,这样手的微小动作就能覆盖整个屏幕;二是加入边界保护代码,防止光标出现负坐标或超出屏幕宽度。第二个优化在完整代码里已经包含,第一个优化需要你根据实际使用距离来调整,核心是在 map_to_screen 里加一个缩放比例。

6. 顺着这个项目还能继续做:进阶扩展方向

6.1 更多手势指令:右键、滚动、拖拽

目前的代码只做了左键点击和光标移动,但实际使用中右键、滚轮、拖拽都是高频操作。基于手势状态机很容易扩展:

  • 食指+中指同时伸出:进入拖拽模式,捏合触发选中并拖动窗口
  • 双指上下移动:模拟鼠标滚轮滚动
  • 五指张开后快速握拳:触发右键菜单

难度主要在于精确区分手势状态,双指和三指的状态判断需要额外计算指尖之间的空间关系。好在这部分逻辑和捏合判定完全同构,学会了距离判定,其他手势只是多算几个关键点之间的距离。

6.2 接入 minicursor 实现跨屏操作

这是我后来玩出的一个扩展方向。pyautogui 只能控制当前系统的鼠标,如果你有多台电脑或平板设备,可以通过网络把手势指令转发给其他设备,实现一套手势控制多台设备的效果。本质上是把鼠标控制抽象成指令协议,手势识别端只负责产生指令,执行端负责消费。

6.3 结合姿态估计实现"凌空操作"

手势控制鼠标只是手部交互的一个子集。如果把 MediaPipe 换成 Pose 姿态估计,把关键点从手扩展到全身,就能实现体感游戏、虚拟形象驱动、运动姿态分析等更丰富的应用。底层逻辑完全一样:关键点提取 -> 动作语义映射 -> 输出反馈。

对于想深入机器视觉的朋友,我的建议是不要止步于"跑通代码",可以试着回答三个问题:为什么用归一化坐标?为什么用指数平滑而不是均值滤波?为什么 max_num_hands 会影响稳定性?这三个问题想通了,你对视觉项目的理解会上升一个台阶。

6.4 加入手势录制与回放功能

最后分享一个有意思的小功能:在代码里加一个手势录制模式,把一段时间内的关键点坐标序列保存为 numpy 数组,然后可以离线回放这段手势序列,用于调试或者生成演示视频。我是在调点击阈值时做了这个功能,发现回放比实时复现手势要稳定得多,能大幅提升调试效率。实现也不复杂,鼠标事件发生时把关键点坐标和时间戳追加到一个列表,结束时存为.npy文件,需要回放时用 pyautogui 重现这些坐标即可。

返回列表