← Retour au portfolio

Projet n°8

Projet 8 – Apple Music Hand Tracker

1. Présentation

Dans ce nouveau projet, après avoir vu des gens sur Instagram contrôler toutes sortes de choses, comme des photos et des vagues, avec leurs mains, j'ai fait quelques recherches sur le sujet et découvert MediaPip, une bibliothèque qui utilise l'apprentissage automatique pour suivre la position des mains, des visages et du corps humain avec un nuage de points plus ou moins précis. C’est à ce moment-là que j’ai eu l’idée de contrôler la musique avec mes mains, du volume au changement de chanson. Avec un peu d'aide, j'ai pu écrire un script Python qui me permet de faire cela. Appelé Suivi des mains Apple Music, dans mon cas, je l’ai fait avec Apple Music car c’est ma plateforme musicale. Cela aurait tout aussi bien fonctionné avec Spotify, à condition d'avoir Premium, car le contrôle de la musique à distance est payant. Pour le code, il suffit de créer un compte développeur. Je vous renvoie à mon premier projet, qui l'explique : Ici.

J'espère que ce projet vous plaira. Dans les prochains jours, je vais essayer de créer une interface épurée pour la rendre plus agréable que de compiler du code Python !

En plus, on peut s'amuser avec toutes sortes de choses, comme contrôler l'angle d'un servomoteur avec la main (mais je trouve ça plutôt basique). J’aimerais aller plus loin dans l’utilisation de MediaPipe, par exemple en associant un mot de passe à une expression faciale, comme les nouvelles clés d’authentification d’Apple pour les connaisseurs ^^…

1. Courte vidéo avec journaux

2. Coder

import cv2
import mediapipe as mp
import os
import time
import math

# Initialisation MediaPipe
mp_hands = mp.solutions.hands
hands = mp_hands.Hands(
    static_image_mode=False,
    max_num_hands=2,
    min_detection_confidence=0.75,
    min_tracking_confidence=0.75
)

mp_draw = mp.solutions.drawing_utils

# FaceMesh
mp_face = mp.solutions.face_mesh
face_mesh = mp_face.FaceMesh(
    static_image_mode=False,
    max_num_faces=1,
    refine_landmarks=True,
    min_detection_confidence=0.7,
    min_tracking_confidence=0.7
)

# Initialisation webcam
cap = cv2.VideoCapture(0)

# État du dernier geste
last_gesture = None
last_time = 0
gesture_delay = 2

# Volume initial
volume = 50  # Entre 0 et 100
previous_volume = volume  # Pour éviter les appels trop fréquents

def count_fingers(hand_landmarks):
    fingers = []
    tips_ids = [8, 12, 16, 20]

    # Pouce
    if hand_landmarks.landmark[4].x < hand_landmarks.landmark[3].x:
        fingers.append(1)
    else:
        fingers.append(0)

    # Autres doigts
    for tip_id in tips_ids:
        if hand_landmarks.landmark[tip_id].y < hand_landmarks.landmark[tip_id - 2].y:
            fingers.append(1)
        else:
            fingers.append(0)

    return sum(fingers)

def send_command_to_apple_music(command):
    if command == "playpause":
        os.system("osascript -e 'tell application \"Music\" to playpause'")
    elif command == "next":
        os.system("osascript -e 'tell application \"Music\" to next track'")
    elif command == "previous":
        os.system("osascript -e 'tell application \"Music\" to previous track'")

def get_distance(lm1, lm2, frame_width, frame_height):
    x1, y1 = int(lm1.x * frame_width), int(lm1.y * frame_height)
    x2, y2 = int(lm2.x * frame_width), int(lm2.y * frame_height)
    distance = math.hypot(x2 - x1, y2 - y1)
    return distance, (x1, y1), (x2, y2)

while True:
    success, frame = cap.read()
    if not success:
        break

    frame = cv2.flip(frame, 1)  # effet miroir
    frame_height, frame_width = frame.shape[:2]

    rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)

    hand_results = hands.process(rgb)
    face_results = face_mesh.process(rgb)

    if hand_results.multi_hand_landmarks:
        for hand_landmarks, hand_info in zip(hand_results.multi_hand_landmarks, hand_results.multi_handedness):
            label = hand_info.classification[0].label  # 'Left' ou 'Right'

            mp_draw.draw_landmarks(frame, hand_landmarks, mp_hands.HAND_CONNECTIONS)

            # MAIN DROITE : contrôle de la musique
            if label == 'Right':
                finger_count = count_fingers(hand_landmarks)
                cv2.putText(frame, f"Main droite: {finger_count} doigts", (10, 50), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 0, 0), 2)

                current_time = time.time()
                if current_time - last_time > gesture_delay:
                    if finger_count == 5 and last_gesture != 5:
                        print("🖐 Lecture / Pause")
                        send_command_to_apple_music("playpause")
                        last_gesture = 5
                        last_time = current_time
                    elif finger_count == 2 and last_gesture != 2:
                        print("✌️ Suivant")
                        send_command_to_apple_music("next")
                        last_gesture = 2
                        last_time = current_time
                    elif finger_count == 3 and last_gesture != 3:
                        print("🤟 Précédent")
                        send_command_to_apple_music("previous")
                        last_gesture = 3
                        last_time = current_time

            # MAIN GAUCHE : contrôle du volume (écartement pouce/index)
            elif label == 'Left':
                lm_list = hand_landmarks.landmark
                dist, pt1, pt2 = get_distance(lm_list[4], lm_list[8], frame_width, frame_height)

                # Mapping distance -> volume
                min_dist, max_dist = 30, 200
                dist_clamped = min(max(dist, min_dist), max_dist)
                volume = int((dist_clamped - min_dist) / (max_dist - min_dist) * 100)

                # Envoie volume système si changement
                if abs(volume - previous_volume) > 2:
                    os.system(f"osascript -e 'set volume output volume {volume}'")
                    previous_volume = volume

                # Affichage visuel entre pouce/index
                cv2.line(frame, pt1, pt2, (0, 255, 0), 3)
                cv2.circle(frame, pt1, 8, (255, 0, 0), -1)
                cv2.circle(frame, pt2, 8, (255, 0, 0), -1)

    else:
        last_gesture = None

    # Affichage FaceMesh
    if face_results.multi_face_landmarks:
        for face_landmarks in face_results.multi_face_landmarks:
            mp_draw.draw_landmarks(
                frame, face_landmarks, mp_face.FACEMESH_TESSELATION,
                landmark_drawing_spec=None,
                connection_drawing_spec=mp_draw.DrawingSpec(color=(0, 255, 255), thickness=1, circle_radius=1)
            )

    # Affichage volume
    cv2.rectangle(frame, (50, 100), (80, 400), (200, 200, 200), 2)
    volume_height = int(300 * (volume / 100))
    cv2.rectangle(frame, (50, 400 - volume_height), (80, 400), (0, 255, 0), -1)
    cv2.putText(frame, f"{volume}%", (45, 90), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2)


    cv2.imshow("Contrôle Apple Music + Volume + Face", frame)

    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

cap.release()
cv2.destroyAllWindows()

J'espère que vous aimerez ^^. Je viendrai avec des nouveaux projets !!