当前位置:首页>python>手指就是琴键:用 Python 隔空弹钢琴

手指就是琴键:用 Python 隔空弹钢琴

  • 2026-09-10 06:19:13
手指就是琴键:用 Python 隔空弹钢琴

上个月跑通了 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 #隔空弹钢琴

最新文章

随机文章