上个月跑通了 Mediapipe 手部识别,屏幕上 21 个蓝色关节点跟着我的手指同步移动,说实话挺有成就感的。我盯着那只虚拟手看了好一会儿,食指指尖坐标精确到像素级——忽然想到,既然能拿到指尖的 x、y、z,为什么不让它发出声音?
打开摄像头,手指落下去就是琴键,抬起来就停——空气变钢琴。
下面把这套核心逻辑拆开说清楚,照着写一遍,基本不会踩坑。
原理很简单:三件事拼在一起
第一件:MediaPipe 告诉你指尖在哪。 摄像头画面喂进去,21 个手部关节点坐标吐出来。我们只关心食指指尖——它在 MediaPipe 的定义里是第 8 号关键点 INDEX_FINGER_TIP,输出是一个归一化的 (x, y) 坐标,范围 0 到 1。
第二件:把屏幕当成键盘。 摄像头画面的下半部分等分成 7 列,每列对应一个白键(C D E F G A B)。指尖 x 落到哪一列,就是哪个键。
第三件:判断"按下"就出声。 在画面 40% 高度的位置画一条虚拟键盘线。指尖 y 第一次越过这条线往下,触发对应音符的发音;指尖抬回线上方,停止。用速度变化触发比直接用 y 坐标判断更自然,但为了保持代码简单,这里先用位置判断。
实战步骤
第一步:准备环境
pip3 install opencv-python mediapipe pygame numpy
macOS 直接用,Windows 10 需要确保 Python 版本 ≥ 3.8,命令换成 pip install。
还需要下载手部识别模型文件。在项目目录下执行:
curl -L -o hand_landmarker.task \ "https://storage.googleapis.com/mediapipe-models/hand_landmarker/hand_landmarker/float16/1/hand_landmarker.task"
这个 .task 文件大约 7.6MB,放到和 .py 文件同一个目录就行。
macOS 上运行时控制台会刷一堆 Class SDLApplication is implemented in both... 的 warning,这是 opencv-python 和 pygame 各自内置了 SDL2 库导致的,不影响使用,忽略即可。
第二步:生成 7 个钢琴音符
用 numpy 生成正弦波,用 pygame 播放。不需要下载任何音频文件——7 个 WAV 音符都是数学算出来的。
import numpy as npimport pygamepygame.mixer.init(frequency=44100, size=-16, channels=2, buffer=512)NOTES = [261.63, 293.66, 329.63, 349.23, 392.00, 440.00, 493.88] # C4~B4NAME = ['C', 'D', 'E', 'F', 'G', 'A', 'B']def make_tone(freq, duration=0.3): sr = 44100 t = np.linspace(0, duration, int(sr * duration), False) wave = (np.sin(2 * np.pi * freq * t) * 0.3 * 32767).astype(np.int16) return np.column_stack((wave, wave))sounds = [pygame.sndarray.make_sound(make_tone(f)) for f in NOTES]
第三步:接入手部识别
MediaPipe 从 0.10.30 版本起移除了旧版 solutions API,现在用 Tasks API。先下载模型文件(第一步已经做了),然后初始化手部识别器:
import cv2import mediapipe as mpfrom mediapipe.tasks.python.vision import HandLandmarker, HandLandmarkerOptions, RunningModefrom mediapipe.tasks.python.vision.hand_landmarker import HandLandmarkfrom mediapipe.tasks.python import BaseOptionsoptions = HandLandmarkerOptions( base_options=BaseOptions(model_asset_path='hand_landmarker.task'), running_mode=RunningMode.VIDEO, # 逐帧处理模式 num_hands=1, # 只追踪一只手 min_hand_detection_confidence=0.3, min_tracking_confidence=0.3,)detector = HandLandmarker.create_from_options(options)cap = cv2.VideoCapture(0)W, H = 640, 480cap.set(cv2.CAP_PROP_FRAME_WIDTH, W)cap.set(cv2.CAP_PROP_FRAME_HEIGHT, H)
和旧版最大的区别:不需要 with ... as hands 上下文管理器了,直接创建 detector 对象,用完记得 detector.close()。
第四步:组装——虚拟键盘 + 指尖触发
分两块:先在画面上画 7 个键位、接着判断指尖是否"按下":
KEYBOARD_Y = int(H * 0.40) # 键盘线在画面 40% 高度(太靠下手指容易出画)KEY_W = W // 7 # 每个键的宽度was_down = Falsetimestamp = 0while cap.isOpened(): ok, frame = cap.read() if not ok: continue frame = cv2.flip(frame, 1) # 画 7 个琴键 for i in range(7): x0 = i * KEY_W cv2.rectangle(frame, (x0, KEYBOARD_Y), (x0 + KEY_W - 2, H), (50, 50, 50), -1) cv2.putText(frame, NAME[i], (x0 + 25, H - 30), cv2.FONT_HERSHEY_SIMPLEX, 1.2, (255, 255, 255), 2) cv2.line(frame, (0, KEYBOARD_Y), (W, KEYBOARD_Y), (0, 255, 200), 2)
# MediaPipe 推理(新 Tasks API) rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) mp_image = mp.Image(image_format=mp.ImageFormat.SRGB, data=rgb_frame) results = detector.detect_for_video(mp_image, timestamp) timestamp += 1 if results.hand_landmarks: landmarks = results.hand_landmarks[0] # 画 21 个关节点(蓝色小圆)+ 食指指尖(红/绿色大圆) for lm in landmarks: cx, cy = int(lm.x * W), int(lm.y * H) cv2.circle(frame, (cx, cy), 3, (255, 0, 0), -1) tip = landmarks[HandLandmark.INDEX_FINGER_TIP] tip_x, tip_y = int(tip.x * W), int(tip.y * H) color = (0, 255, 0) if tip_y > KEYBOARD_Y else (0, 0, 255) cv2.circle(frame, (tip_x, tip_y), 8, color, -1) is_down = tip_y > KEYBOARD_Y # 指尖越过键盘线 = 按下 if is_down and not was_down: idx = min(tip_x // KEY_W, 6) sounds[idx].play() was_down = is_down cv2.imshow('Air Piano', frame) if cv2.waitKey(10) & 0xFF == ord('q'): breakcap.release()cv2.destroyAllWindows()detector.close()
运行,摄像头打开后手指在键盘区域按下就会响——C 到 B 七个音,能弹出《小星星》。实际操作时手掌保持在画面上半部分,食指像敲桌子一样往下点,指尖穿过绿色横线即触发。首次运行没画面的话检查 macOS 摄像头权限。
还能玩什么
这套骨架有了,换个映射逻辑就能做别的东西:
- • 隔空打碟:把 7 个键换成鼓组采样(底鼓、军鼓、踩镲),手指落点触发节奏
- • 手势番茄钟:一根手指 = 5 分钟、两根 = 15 分钟、三根 = 25 分钟,写代码不碰键盘就能启动计时
- • 娃の魔法阵:手指画圈识别,屏幕上出粒子特效——给小孩变魔术
装好依赖,跑一遍,听到第一个音被手指敲出来的时候,那种"卧槽"的感觉比什么教程都好使。你也可以试试把七键换成八音盒音色,效果更梦幻。遇到问题欢迎留言。
#Mediapipe #手势识别 #OpenCV #Python #创意编程 #AI #隔空弹钢琴