Python+OpenCV实现用MediaPipe手势数字识别一键打开下载夹里的图片
一、想要做的事
我电脑屏幕上经常一边写代码一边盯刚下载的截图。每次都要打开"下载"文件夹、按时间排序、双击 ——这个动作太小却很烦。
那干脆让摄像头来干:
- 比划"1" → 自动打开下载夹里最新的那张 PNG;
- 比划"2" → 打开第二新的那张;
- ……一直到 5;
- 握拳(0 根手指)= 解除武装,准备下一次触发。
最终效果是一个 200 行不到的 Python 脚本,靠 MediaPipe 的 HandLandmarker 模型识别 21 个关键点,再用一条非常朴素的几何规则数手指。
本文从 0 到 1 拆开它,讲清四件事:
- MediaPipe Tasks 在 Python 里到底怎么用?
LIVE_STREAM模式和回调长什么样? - 给定 21 个关键点,怎么数"伸出几根手指"?
- 怎样防止识别抖动一下就疯狂连发?
- Windows 下用
os.startfile调起默认看图程序的小坑。

二、环境准备
MediaPipe 当前对 Python 3.13 还没有发布的 wheel,必须用 3.10(3.11/3.12 也行,但 3.10 是最稳的)。我直接装在虚拟环境里:
py -3.10 -m venv venv venv\Scripts\activate pip install mediapipe opencv-python pillow numpy
模型文件自己下一次到本地
https://storage.googleapis.com/mediapipe-models/hand_landmarker/hand_landmarker/float16/latest/hand_landmarker.task
放到项目根目录的 models/hand_landmarker.task。整体目录长这样:
MediaPipe/
├── models/hand_landmarker.task
├── demo_hand_landmarker.py
└── venv/
三、MediaPipe Tasks 的最小可用骨架
新版 MediaPipe(0.10.x)已经全面切到 Tasks API,老博客里的 mp.solutions.hands 在 0.10.35 之后不存在了。所以请直接用:
import mediapipe as mp
from mediapipe.tasks import python as mp_python
from mediapipe.tasks.python import vision
options = vision.HandLandmarkerOptions(
base_options=mp_python.BaseOptions(model_asset_path="models/hand_landmarker.task"),
running_mode=vision.RunningMode.LIVE_STREAM, # 摄像头流式推理
num_hands=1,
min_hand_detection_confidence=0.5,
min_hand_presence_confidence=0.5,
min_tracking_confidence=0.5,
result_callback=on_result, # 异步结果回调
)
LIVE_STREAM 模式下,detect_async() 立刻返回,结果通过你注册的 result_callback 异步给你。这意味着主循环千万不要在调用之后阻塞等结果——直接读"上一次最新结果"就好:
class LatestResult:
def __init__(self):
self.result = None
latest = LatestResult()
def on_result(result, output_image, timestamp_ms):
latest.result = result
主循环里每帧把 BGR→RGB 包成 mp.Image,再带一个单调递增的时间戳调 detect_async:
start_ns = time.perf_counter_ns() # ... rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) mp_image = mp.Image(image_format=mp.ImageFormat.SRGB, data=rgb) ts_ms = (time.perf_counter_ns() - start_ns) // 1_000_000 landmarker.detect_async(mp_image, ts_ms)
时间戳必须递增,否则 MediaPipe 会直接抛错把帧丢掉。这是新手最容易踩的坑之一。
四、HandLandmarker 的输出长什么样
模型给你的是每只手 21 个 3D 归一化坐标(x,y 在 [0,1],z 是相对于手腕的深度)。索引按官方文档:
0 WRIST
1-4 THUMB
5-8 INDEX
9-12 MIDDLE
13-16 RING
17-20 PINKY
每根手指有 4 个点:MCP(指根)、PIP、DIP、TIP(指尖)。这次我们只关心 TIP 和 PIP。
绘制的连线表(之前从 mp.solutions.hands.HAND_CONNECTIONS 拿,现在自己写死):
HAND_CONNECTIONS = (
(0,1),(1,2),(2,3),(3,4),
(0,5),(5,6),(6,7),(7,8),
(5,9),(9,10),(10,11),(11,12),
(9,13),(13,14),(14,15),(15,16),
(13,17),(0,17),(17,18),(18,19),(19,20),
)
五、最朴素的"数手指"算法
很多教程会比较 TIP 和 PIP 的 y 坐标,但这只有在手"竖着伸"的时候才对。一旦手歪了或者反过来就崩。
更鲁棒的判定是:TIP 离 WRIST 的距离 > PIP 离 WRIST 的距离 × 1.1,那这根手指就是伸直的。它跟方向无关,因为屈指的时候 TIP 会被卷向手腕,距离一定变小。
FINGER_TIPS = (4, 8, 12, 16, 20)
FINGER_PIPS = (3, 6, 10, 14, 18)
def count_extended_fingers(landmarks) -> int:
wrist = landmarks[0]
def d(a, b):
return math.hypot(a.x - b.x, a.y - b.y)
n = 0
for tip, pip in zip(FINGER_TIPS, FINGER_PIPS):
if d(landmarks[tip], wrist) > d(landmarks[pip], wrist) * 1.1:
n += 1
return n
20 行不到,对所有方向都鲁棒。1.1 是经验阈值,太小会把弯指误判为伸直,太大会漏掉刚伸出的手指。
六、抖动处理:稳定窗口 + armed 状态机
如果直接拿 count_extended_fingers 的瞬时值去开图,识别抖一下立刻连开五张图,体验崩溃。两步处理:
第一步:连续 N 帧都是同一个值才算"稳定"。我用 deque(maxlen=10),10 帧 ≈ 0.3-0.5 秒:
history = deque(maxlen=10) history.append(cur_n) stable = history[0] if len(history) == history.maxlen and all(v == history[0] for v in history) else None
第二步:触发后必须先回到 0 才能再触发——一个标志位 armed:
if stable == 0:
armed = True
elif stable is not None and 1 <= stable <= 5 and armed:
open_nth_png(stable)
armed = False
history.clear() # 清空缓冲,防止同一稳定段被重复识别
这就是 Game UI 里常说的"边沿触发",不是"电平触发"。代价只有一句 armed = False,效果极好。
七、按时间倒序定位"第 N 新"的 PNG
Python 标准库就够:
DOWNLOADS_DIR = Path.home() / "Downloads"
def open_nth_png(n: int):
files = sorted(
DOWNLOADS_DIR.glob("*.png"),
key=lambda p: p.stat().st_mtime,
reverse=True,
)
if len(files) < n:
return False, f"Only {len(files)} PNG(s), need #{n}"
target = files[n - 1]
os.startfile(str(target)) # Windows 专属:相当于双击
return True, f"Opened #{n}: {target.name}"
os.startfile 在 Linux/macOS 没有,要写跨平台的话用 subprocess.run(["open", path])(macOS)/xdg-open(Linux),不过我这是个人 Windows 工具就懒得抽象了。
八、画面镜像与左右手反转
cv2.flip(frame, 1) 让画面像照镜子一样自然,但 MediaPipe 拿到的是镜像后的图,它返回的 Left/Right 也跟实际相反。一句话补回来:
raw = result.handedness[hand_idx][0].category_name label = "Right" if raw == "Left" else "Left"
handedness 这个字段名我每次都拼错,提醒自己。
九、完整主循环
把上面零件拼起来:
with vision.HandLandmarker.create_from_options(options) as landmarker:
while True:
ok, frame = cap.read()
if not ok:
break
frame = cv2.flip(frame, 1)
rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
mp_image = mp.Image(image_format=mp.ImageFormat.SRGB, data=rgb)
ts_ms = (time.perf_counter_ns() - start_ns) // 1_000_000
landmarker.detect_async(mp_image, ts_ms)
res = latest.result
cur_n = count_extended_fingers(res.hand_landmarks[0]) if (res and res.hand_landmarks) else 0
history.append(cur_n)
stable = history[0] if len(history) == history.maxlen \
and all(v == history[0] for v in history) else None
if stable == 0:
armed = True
elif stable is not None and 1 <= stable <= 5 and armed:
ok2, msg = open_nth_png(stable)
armed = False
history.clear()
draw_landmarks(frame, res)
cv2.imshow("Gesture -> Open PNG", frame)
if cv2.waitKey(1) & 0xFF in (ord("q"), 27):
break
十、几个一定会遇到的坑
No module named 'mediapipe':99% 是没激活虚拟环境,或者用的是系统 Python 3.13。先where python确认。module 'mediapipe' has no attribute 'solutions':你装的是 0.10.x,老 API 已经下线,请用mediapipe.tasks.python.vision那一套。- 时间戳报错:
detect_async必须传单调递增的timestamp_ms,重复值会被拒。 - 回调线程:
on_result是另一个线程跑的,不要在里面调 OpenCV GUI 函数;只更新共享变量给主循环读。
十一、还能往哪儿玩
- 把"打开 PNG"换成"启动指定程序"、“切歌”、“切桌面”——一切快捷键能干的事都行;
- 双手(
num_hands=2):左 0 + 右 N,组合表达式更多; - 配合 OBS 摄像头,做不需要触屏的演讲翻页器;
- 21 个关键点也足以训练自定义手势分类器,喂给一个 MLP 就能识别"OK"、"❤"等更花哨的姿势。
代码不到 200 行,跑起来流畅 25-30 fps(CPU 推理,i5 笔记本)。MediaPipe 这一套真正的价值是把"摄像头到关键点"的脏活全干了,剩下的判断都只是几何题。
小结:HandLandmarker + 一个稳定窗口 + 一个 armed 标志 + os.startfile,就是 Windows 上一个能用的手势触发器。下一篇我们会用同一套骨架,把 HandLandmarker 换成 FaceDetector,做一个"按 ‘s’ 一键存所有人脸"的小工具。
到此这篇关于Python+OpenCV实现用MediaPipe手势数字识别一键打开下载夹里的图片的文章就介绍到这了,更多相关Python OpenCV数字识别内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
Python使用Spire.PDF轻松实现压缩PDF文件的大小
PDF 文件因其优秀的兼容性和格式保持能力而被广泛使用,但随着内容的增加,特别是包含大量高清图片时,文件体积往往会变得非常庞大,下面我们就来看看如何使用 Python 和 Spire.PDF 库来压缩 PDF 文件吧2026-04-04


最新评论