这是一次偏探索性质的实验:尝试用摄像头识别手势,并将其映射成真实键盘输入,用于简单游戏或交互控制。
目标不是做完整产品,而是验证几个问题:
-
MediaPipe 手部关键点是否足够稳定?
-
能否在不加复杂滤波的情况下实现可用输入?
-
如何避免误触与抖动?

🎯 实验目标
实现最小可用交互模型:
| 手势 | 行为 |
|---|---|
| 食指指向方向 | 方向键单击 |
| 食指 + 中指 | 方向键长按 |
| 手收回 | 松键 |
🔧 技术选型
-
MediaPipe Hand Landmarker:手部关键点检测
-
OpenCV:摄像头输入与画面展示
-
pynput:模拟系统键盘输入
-
Python 状态机逻辑:抑制抖动与重复触发
🧠 核心思路
整体流程非常直接:
摄像头 → 手部关键点 → 手指方向判断 → 输入状态机 → 键盘事件
重点不在识别算法,而在:
-
如何把连续视觉信号变成离散输入事件
-
如何保证按键只触发一次或稳定长按
🧩 实验代码
import cv2
import numpy as np
import mediapipe as mp
from mediapipe import solutions
from mediapipe.framework.formats import landmark_pb2
from mediapipe.tasks import python
from mediapipe.tasks.python import vision
from pynput.keyboard import Controller, Key
class HANDPLAY:
def __init__(self, model_path="hand_landmarker.task",
num_hands=2,
mirror=True):
base_options = python.BaseOptions(model_asset_path=model_path)
options = vision.HandLandmarkerOptions(
base_options=base_options,
num_hands=num_hands
)
self.detector = vision.HandLandmarker.create_from_options(options)
self.keyboard = Controller()
self.key_map = {
"Up": Key.up,
"Down": Key.down,
"Left": Key.left,
"Right": Key.right
}
self.mode = "idle" # idle | click | hold
self.active_dir = None
self.mirror = mirror
# -------------------------
# 工具函数
# -------------------------
def _finger_direction(self, lm, tip_id, base_id):
tip = lm[tip_id]
base = lm[base_id]
dx = tip.x - base.x
dy = tip.y - base.y
if self.mirror:
dx = -dx
if abs(dx) > abs(dy):
return "Right" if dx > 0 else "Left"
else:
return "Up" if dy < 0 else "Down"
def _finger_extended(self, lm, tip_id, pip_id):
return lm[tip_id].y < lm[pip_id].y
# -------------------------
# 输入状态机
# -------------------------
def _handle_input(self, lm):
index_ext = self._finger_extended(lm, 8, 6)
middle_ext = self._finger_extended(lm, 12, 10)
if not index_ext:
self._reset()
return
direction = self._finger_direction(lm, 8, 5)
key = self.key_map[direction]
# 长按
if middle_ext:
if self.mode != "hold" or self.active_dir != direction:
self._reset()
self.keyboard.press(key)
print(f"[HOLD] {direction}")
self.mode = "hold"
self.active_dir = direction
return
# 单击
if self.mode != "click" or self.active_dir != direction:
self._reset()
self.keyboard.press(key)
self.keyboard.release(key)
print(f"[CLICK] {direction}")
self.mode = "click"
self.active_dir = direction
def _reset(self):
if self.mode == "hold" and self.active_dir:
self.keyboard.release(self.key_map[self.active_dir])
print(f"[RELEASE] {self.active_dir}")
self.mode = "idle"
self.active_dir = None
# -------------------------
# 绘制 + 主流程
# -------------------------
def _draw_landmarks_on_image(self, rgb_image, detection_result):
annotated = np.copy(rgb_image)
if detection_result.hand_landmarks:
for lm in detection_result.hand_landmarks:
proto = landmark_pb2.NormalizedLandmarkList()
proto.landmark.extend([
landmark_pb2.NormalizedLandmark(x=p.x, y=p.y, z=p.z)
for p in lm
])
solutions.drawing_utils.draw_landmarks(
annotated,
proto,
mp.solutions.hands.HAND_CONNECTIONS,
solutions.drawing_styles.get_default_hand_landmarks_style(),
solutions.drawing_styles.get_default_hand_connections_style()
)
self._handle_input(lm)
else:
self._reset()
return annotated
def do(self, frame, device=None):
if frame is None:
return None
mp_image = mp.Image(
image_format=mp.ImageFormat.SRGB,
data=cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
)
result = self.detector.detect(mp_image)
annotated = self._draw_landmarks_on_image(mp_image.numpy_view(), result)
return cv2.cvtColor(annotated, cv2.COLOR_RGB2BGR)
🔍 关键技术尝试点
1️⃣ 如何判断方向?
用食指指尖(8)与指根(5)构成向量:
dx = tip.x - base.x dy = tip.y - base.y
再比较水平和垂直分量大小来分类方向。
2️⃣ 如何判断手指是否伸直?
lm[tip].y < lm[pip].y
在 MediaPipe 坐标中,y 越小表示越靠上,因此可用于检测伸展状态。

3️⃣ 为什么需要状态机?
如果直接在每一帧触发按键,会导致:
-
每秒几十次点击
-
长按无法稳定释放
-
手抖造成方向跳变
于是引入:
idle → click → hold
状态流转模型来稳定输入。
4️⃣ 为什么要镜像修正?
摄像头画面通常左右翻转,如果不处理:
👉 你向右指,系统认为你向左。
所以加入:
if self.mirror: dx = -dx
🧪 实验结果
在普通笔记本摄像头下:
| 项目 | 表现 |
|---|---|
| 延迟 | ≈30ms |
| 稳定度 | 可连续控制游戏角色 |
| 误触 | 偶发,主要来自遮挡 |
整体已经达到"可玩"的实验级效果。
🧠 一些未解决 / 可改进点
-
❌ 没做时间滤波,快速抖动仍可能误触
-
❌ 没区分左右手
-
❌ 没做手势组合识别(如拳头/OK/张掌)
🏁 总结
这是一次偏 技术验证型实验:
不追求产品级完整度,而是验证
👉 MediaPipe + Python 能否快速搭建可用的隔空输入系统。
事实证明:
-
算法层不复杂
-
输入稳定性关键在状态机设计
-
小量工程控制就能达到可用体验
对 PiscTrace or PiscCode感兴趣?更多精彩内容请移步官网看看~🔗 PiscTrace