# pip install opencv-python mediapipe pygame numpy pillow
# pip install pyinstaller
# pyinstaller --onefile --icon=1.ico main.py
import cv2
import mediapipe as mp
import pygame
import os
import numpy as np
import time
from PIL import Image, ImageDraw, ImageFont # 添加PIL库支持中文显示
# 初始化pygame混音器
pygame.mixer.init(frequency=44100, size=-16, channels=2, buffer=512)
# 手势控制音乐播放器类
class GestureMusicPlayer:
def __init__(self, music_folder):
self.music_folder = music_folder
self.songs = self._load_valid_songs() # 加载验证通过的音乐
self.current_song_index = 0
self.volume = 0.5
self.is_playing = False
self.last_gesture_time = 0
self.gesture_cooldown = 1 # 手势冷却时间(秒)
# 加载中文字体
self.font_path = self._find_chinese_font()
if not self.font_path:
print("警告: 找不到中文字体,将使用英文显示")
# 加载第一首歌(如果有有效歌曲)
if self.songs:
self._load_current_song()
pygame.mixer.music.set_volume(self.volume)
# 初始化MediaPipe手势识别
self.mp_hands = mp.solutions.hands
self.hands = self.mp_hands.Hands(
max_num_hands=1,
min_detection_confidence=0.7,
min_tracking_confidence=0.5)
self.mp_draw = mp.solutions.drawing_utils
# 手势定义
self.GESTURES = {
"THUMBS_UP": "👍 播放/暂停",
"THUMBS_DOWN": "👎 停止",
"INDEX_UP": "👆 音量增加",
"INDEX_DOWN": "👇 音量减小",
"PALM_LEFT": "👈 上一首",
"PALM_RIGHT": "👉 下一首"
}
def _find_chinese_font(self):
"""查找可用的中文字体"""
# 常见中文字体路径
font_paths = [
"simhei.ttf", # 当前目录
"C:/Windows/Fonts/simhei.ttf", # Windows
"C:/Windows/Fonts/msyh.ttc", # Windows 微软雅黑
"/System/Library/Fonts/PingFang.ttc", # macOS
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", # Linux
"/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc", # Linux
]
for path in font_paths:
if os.path.exists(path):
return path
return None
def put_chinese_text(self, image, text, position, font_size=30, color=(0, 255, 0)):
"""在图像上绘制中文文本"""
if self.font_path:
try:
# 转换OpenCV图像到PIL图像
img_pil = Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
draw = ImageDraw.Draw(img_pil)
# 加载字体
font = ImageFont.truetype(self.font_path, font_size)
# 绘制文本
draw.text(position, text, font=font, fill=color)
# 转换回OpenCV格式
return cv2.cvtColor(np.array(img_pil), cv2.COLOR_RGB2BGR)
except Exception as e:
print(f"字体渲染错误: {e}")
# 失败时使用英文显示
cv2.putText(image, text, position, cv2.FONT_HERSHEY_SIMPLEX,
1, color, 2, cv2.LINE_AA)
return image
else:
# 如果没有中文字体,使用英文显示
cv2.putText(image, text, position, cv2.FONT_HERSHEY_SIMPLEX,
1, color, 2, cv2.LINE_AA)
return image
def _load_valid_songs(self):
"""加载并验证文件夹中可播放的音乐文件"""
valid_songs = []
if not os.path.exists(self.music_folder):
return valid_songs
# 遍历文件夹中的音乐文件
for f in os.listdir(self.music_folder):
if f.endswith(('.mp3', '.wav')):
file_path = os.path.join(self.music_folder, f)
# 尝试加载文件验证是否可播放
try:
pygame.mixer.music.load(file_path)
valid_songs.append(f)
print(f"已加载音乐: {f}")
except pygame.error:
print(f"跳过无效/损坏的文件: {f}")
return valid_songs
def _load_current_song(self):
"""加载当前索引对应的歌曲"""
if self.songs:
song_path = os.path.join(self.music_folder, self.songs[self.current_song_index])
pygame.mixer.music.load(song_path)
def detect_gesture(self, landmarks):
# 获取关键点坐标
thumb_tip = landmarks[self.mp_hands.HandLandmark.THUMB_TIP]
index_tip = landmarks[self.mp_hands.HandLandmark.INDEX_FINGER_TIP]
middle_tip = landmarks[self.mp_hands.HandLandmark.MIDDLE_FINGER_TIP]
ring_tip = landmarks[self.mp_hands.HandLandmark.RING_FINGER_TIP]
pinky_tip = landmarks[self.mp_hands.HandLandmark.PINKY_TIP]
wrist = landmarks[self.mp_hands.HandLandmark.WRIST]
# 计算手指是否伸直
def is_finger_extended(finger_tip, finger_mcp):
return finger_tip.y < finger_mcp.y
# 大拇指竖起 (👍)
thumb_extended = thumb_tip.y < landmarks[self.mp_hands.HandLandmark.THUMB_IP].y
other_fingers_closed = all(
landmarks[self.mp_hands.HandLandmark(i)].y > landmarks[self.mp_hands.HandLandmark(i - 2)].y
for i in [self.mp_hands.HandLandmark.INDEX_FINGER_TIP,
self.mp_hands.HandLandmark.MIDDLE_FINGER_TIP,
self.mp_hands.HandLandmark.RING_FINGER_TIP,
self.mp_hands.HandLandmark.PINKY_TIP]
)
if thumb_extended and other_fingers_closed:
return "THUMBS_UP"
# 拇指向下 (👎)
thumb_down = thumb_tip.y > landmarks[self.mp_hands.HandLandmark.THUMB_IP].y
if thumb_down and other_fingers_closed:
return "THUMBS_DOWN"
# 食指向上 (👆)
index_extended = is_finger_extended(index_tip, landmarks[self.mp_hands.HandLandmark.INDEX_FINGER_MCP])
other_fingers_closed = all(
not is_finger_extended(landmarks[self.mp_hands.HandLandmark(i)],
landmarks[self.mp_hands.HandLandmark(i - 2)])
for i in [self.mp_hands.HandLandmark.MIDDLE_FINGER_TIP,
self.mp_hands.HandLandmark.RING_FINGER_TIP,
self.mp_hands.HandLandmark.PINKY_TIP]
)
if index_extended and other_fingers_closed:
return "INDEX_UP"
# 食指向下 (👇)
index_down = index_tip.y > landmarks[self.mp_hands.HandLandmark.INDEX_FINGER_PIP].y
if index_down and other_fingers_closed:
return "INDEX_DOWN"
# 手掌向左 (👈)
fingers_extended = all(
is_finger_extended(landmarks[self.mp_hands.HandLandmark(i)],
landmarks[self.mp_hands.HandLandmark(i - 2)])
for i in [self.mp_hands.HandLandmark.INDEX_FINGER_TIP,
self.mp_hands.HandLandmark.MIDDLE_FINGER_TIP,
self.mp_hands.HandLandmark.RING_FINGER_TIP,
self.mp_hands.HandLandmark.PINKY_TIP]
)
palm_orientation = wrist.x - index_tip.x
if fingers_extended and palm_orientation > 0.1:
return "PALM_LEFT"
# 手掌向右 (👉)
if fingers_extended and palm_orientation < -0.1:
return "PALM_RIGHT"
return None
def handle_gesture(self, gesture):
current_time = time.time()
if current_time - self.last_gesture_time < self.gesture_cooldown:
return
self.last_gesture_time = current_time
if not self.songs:
print("没有可用的音乐文件")
return
if gesture == "THUMBS_UP":
if self.is_playing:
pygame.mixer.music.pause()
self.is_playing = False
else:
pygame.mixer.music.unpause()
if not pygame.mixer.music.get_busy():
pygame.mixer.music.play()
self.is_playing = True
print("播放/暂停")
elif gesture == "THUMBS_DOWN":
pygame.mixer.music.stop()
self.is_playing = False
print("停止")
elif gesture == "INDEX_UP":
self.volume = min(1.0, self.volume + 0.1)
pygame.mixer.music.set_volume(self.volume)
print(f"音量增加: {self.volume:.1f}")
elif gesture == "INDEX_DOWN":
self.volume = max(0.0, self.volume - 0.1)
pygame.mixer.music.set_volume(self.volume)
print(f"音量减小: {self.volume:.1f}")
elif gesture == "PALM_LEFT":
self.current_song_index = (self.current_song_index - 1) % len(self.songs)
self._load_current_song()
if self.is_playing:
pygame.mixer.music.play()
print(f"上一首: {self.songs[self.current_song_index]}")
elif gesture == "PALM_RIGHT":
self.current_song_index = (self.current_song_index + 1) % len(self.songs)
self._load_current_song()
if self.is_playing:
pygame.mixer.music.play()
print(f"下一首: {self.songs[self.current_song_index]}")
def run(self):
if not self.songs:
print("没有可播放的音乐文件,请检查music文件夹")
# 显示提示信息
cap = cv2.VideoCapture(0)
while True:
success, image = cap.read()
if not success:
continue
image = self.put_chinese_text(image, "没有可播放的音乐文件", (50, 100), font_size=40, color=(0, 0, 255))
image = self.put_chinese_text(image, "请将音乐文件放入music文件夹", (50, 150), font_size=30, color=(255, 255, 255))
image = self.put_chinese_text(image, "按ESC退出", (50, 200), font_size=25, color=(255, 255, 255))
cv2.imshow('Gesture Music Player', image)
if cv2.waitKey(5) & 0xFF == 27:
break
cap.release()
cv2.destroyAllWindows()
return
cap = cv2.VideoCapture(0)
while cap.isOpened():
success, image = cap.read()
if not success:
continue
# 转换图像颜色空间
image = cv2.cvtColor(cv2.flip(image, 1), cv2.COLOR_BGR2RGB)
image.flags.writeable = False
results = self.hands.process(image)
# 绘制手势识别结果
image.flags.writeable = True
image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
current_gesture = None
if results.multi_hand_landmarks:
for hand_landmarks in results.multi_hand_landmarks:
self.mp_draw.draw_landmarks(
image, hand_landmarks, self.mp_hands.HAND_CONNECTIONS)
current_gesture = self.detect_gesture(hand_landmarks.landmark)
# 处理手势
if current_gesture:
self.handle_gesture(current_gesture)
# 使用新方法绘制中文文本
gesture_text = self.GESTURES.get(current_gesture, "")
image = self.put_chinese_text(image, gesture_text, (10, 50))
# 显示当前歌曲和状态
song_name = self.songs[self.current_song_index] if self.songs else '无'
# 截断过长的文件名
if len(song_name) > 30:
song_name = song_name[:27] + "..."
status_text = f"歌曲: {song_name} | 状态: {'播放中' if self.is_playing else '暂停/停止'} | 音量: {int(self.volume * 100)}%"
# 使用新方法绘制中文状态文本
image = self.put_chinese_text(image, status_text, (10, image.shape[0] - 30),
font_size=20, color=(255, 255, 255))
cv2.imshow('Gesture Music Player', image)
if cv2.waitKey(5) & 0xFF == 27: # ESC键退出
break
cap.release()
cv2.destroyAllWindows()
if __name__ == "__main__":
music_folder = "music" # 音乐文件夹路径
if not os.path.exists(music_folder):
os.makedirs(music_folder)
print(f"已创建音乐文件夹,请将音乐文件放入: {os.path.abspath(music_folder)}")
# 等待用户放入文件
input("按回车键继续...")
player = GestureMusicPlayer(music_folder)
player.run()
软件截图: