Python e OpenCV: riconoscere le mani e la gestualità

progetto di computer vision con python, opencv e mediapipe: riconoscere le mani e la loro gestualità

In questo articolo vediamo come realizzare un progetto di Computer Vision usando:

  • Python come linguaggio di programmazione
  • Le librerie OpenCV e MediaPipe

In particolare scriveremo del codice per riconoscere le mani: in particolare la gesture e il loro orientamento nello spazio.

Questo progetto di Computer Vision prende il nome di Hand Tracking: dando in pasto al computer un’immagine o un video questo sarà in grado di riconoscere la mano destra e la mano sinistra e il loro orientamento nello spazio (tenendo in considerazione che stiamo sempre parlando di pixel per i possibili calcoli di trigonometria, sulle rette e sul piano cartesiano).

Un progetto di Hand Tracking ci permetterà di inviare input specifici al computer per eseguire determinate funzioni con il solo movimento delle nostre mani.

Un esempio potrebbe essere che muovendo la mano sinistra da un punto a un altro eseguiamo una funzione per spegnere o accendere la luce della nostra cameretta.

Oppure potremmo sfruttare l’Hand Tracking per ruotare un modello 3D, muovere il cursore del mouse o rilevare specifici segni fatti con le mani. Le idee sono limitate solo dove la nostra fantasia può arrivare.

Oppure potremmo far dialogare Python e Arduino (e viceversa) ed effettuare automazioni ancora più potenti ed estese.

Segui passo dopo passo tutti i procedimenti che sto per illustrarti. Alla fine di questa guida ti invito a riflettere sul codice che abbiamo scritto assieme per comprenderlo al meglio e espanderlo per realizzare le funzionalità che desideri.

Prima di partire assicurati di avere installati sul tuo computer:

Detto questo iniziamo subito!

Installiamo le librerie OpenCV e MediaPipe

Non realizzeremo da zero una libreria per riconoscere una mano da un’immagine o tracciarne l’orientamento, ma utilizzeremo delle ottime librerie come OpenCV e MediaPipe.

Per cui esegui questo comando per installare OpenCV:

pip install opencv-python

E poi quest’altro per installare MediaPipe:

pip install mediapipe

Nota bene: per poter eseguire questi comandi utilizza il prompt dei comandi di windows cercandolo dal menù. Su MacOS avrai il Terminale. Altrimenti utilizza tranquillamente il terminale del tuo IDE preferito che può essere Visual Studio Code.

Fatto ciò iniziamo a scrivere il codice!

Scriviamo il codice per la funzionalità di Hand Tracking

Segui in ordine numerico tutti gli step che ti presenterò qui sotto. In questo modo ti ritroverai col codice intero del programma che ti lascerò comunque alla fine dell’articolo.

Segui attentamente ogni singolo step per capire meglio tutta la procedura.

1) Importare i moduli OpenCV, MediaPipe e Time

La prima cosa da fare è importare i moduli necessari per poter tracciare le mani attraverso la webcam, questi sono: opencv e mediapipe.

In più importiamo anche il modulo “time” per aggiungere una funzionalità in più molto semplice: contare gli FPS. Ma questo lo faremo soltanto alla fine della guida.

Quindi iniziamo il codice con:

# Importiamo i moduli
import cv2                                # Importiamo OpenCV
import mediapipe as mp       # Importiamo MediaPipe
import time                              # Importiamo il modulo time

2) Scegliere quale videocamera utilizzare

Uno step fondamentale è selezionare la videocamera da utilizzare per permettere al programma di leggere un video in entrata e quindi analizzare l’immagine per effettuare le sue operazioni di riconoscimento.

Per assegnare una videocamera al programma scriviamo questo codice continuando dopo ciò che abbiamo scritto prima:

# Selezioniamo la webcam principale
camera = cv2.VideoCapture(0)

Ciò che stiamo facendo è assegnare una webcam alla variabile cam. In seguito utilizziamo la funzione cv2.VideoCapture() per selezionare una videocamera.

Ma come decidiamo quale videocamera utilizzare? E se ne avessi più di una?

Per farlo è tutto molto semplice. Basta inserire un numero che parte da 0 all’interno delle parentesi tonde della funzione cv2.VideoCapture(0).

Il numero zero (0) sta a indicare la primissima videocamera che il computer rileva, nel caso di un computer portatile questa è la webcam principale se disponibile. Altrimenti, se il pc non ha una webcam, sarà quella connessa tramite USB.

Quando avvieremo il programma allo step 5 ricordati di questa linea di codice e se non dovesse essere rilevata la webcam al posto di zero (0) inserisci (1) e se non va incrementa ancora di uno.

Se non dovesse andare anche durante questi tentativi controlla che la webcam funzioni o che la connessione USB non abbia problemi.

In ogni caso, se hai un computer portatile e la webcam principale, quella normalmente preinstallata sulla parte superiore dello schermo, ha sempre funzionato non dovresti avere problemi (la sua posizione è sempre (0)).

3) Impostare MediaPipe per il riconoscimento delle mani

Le prossime righe di codice ci serviranno a dire al modulo MediaPipe quale elemento vogliamo riconoscere. Con esso è possibile riconoscere oggetti, persone, simboli e molto altro, ma noi vogliamo concentrarci sul riconoscimento delle mani e la loro gestualità.

Quindi continuiamo il programma scrivendo:

# Utilizziamo il modulo di riconoscimento delle mani
mpHands = mp.solutions.hands
hands = mpHands.Hands()
mpDraw = mp.solutions.drawing_utils

4) Avviare un loop infinito

Il riconoscimento delle mani attraverso una videocamera richiede un loop infinito: perché il programma dovrà analizzare ogni singolo frame del video proveniente dalla webcam.

Per avviarlo è necessaria una semplice riga di codice la cui condizione è sempre vera:

while True:

In poche parole se la condizione è vera il loop farà un ciclo. Dato che la parola True in programmazione è una condizione sempre vera, questo codice darà un loop infinito, cioè non si fermerà mai.

5) Scrivere il codice per il riconoscimento delle mani all’interno del loop infinito

Ora che abbiamo avviato un loop infinito, dobbiamo scrivere del codice all’interno di esso per leggere ogni frame (o immagine) in entrata dalla webcam:

while True:
    # Leggi l'immagine della webcam
    success, img = camera.read()
    imgRGB = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    results = hands.process(imgRGB)

    # Riconosci se ci sono mani nell'immagine ed esegui il loop per ogni mano prendendo i landmarks (x, y)
    if results.multi_hand_landmarks:
        for handLms in results.multi_hand_landmarks:
            mpDraw.draw_landmarks(img, handLms, mpHands.HAND_CONNECTIONS)

    cv2.imshow("Riconoscimento mani - Progetto Computer Vision", img)
    cv2.waitKey(1)

Con questo abbiamo terminato!

Non ti resta altro che avviare l’intero programma premendo F5 o la combinazione CTRL+F5.

Se hai fatto tutto correttamente dovrebbe aprirsi una finestra sul tuo schermo con il video della webcam. Poi portando la tua mano o entrambe davanti alla webcam dovresti vedere punti e segmenti che le tracciano.

Ecco il risultato che dovresti ottenere:

Ma adesso voglio spiegarti il codice che abbiamo scritto nel loop infinito:

  • success, img = cap.read() avvia la lettura della webcam selezionata in precedenza.
  • imgRGB = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) converte l’immagine in entrata dalla webcam (che si trova sulla variabile img) da BGR a RGB.
  • results = hands.process(imgRGB) dapprima si preoccupa di riconoscere le mani nell’immagine del video in RGB e poi le salva nella variabile results.
  • if results.multi_hand_landmarks: è un blocco condizionale e serve a riconoscere se effettivamente nell’immagine dalla webcam sono state rilevate una o più mani.
  • for handLms in results.multi_hand_landmarks: eseguiamo un loop per prendere il controllo dello scheletro di ogni singola mano rilevata nell’immagine.
  • mpDraw.draw_landmarks(img, handLms, mpHands.HAND_CONNECTIONS) serve a disegnare punti e segmenti sulla mano o sulle mani rilevate nell’immagine.
  • cv2.imshow("Riconoscimento mani - Progetto Computer Vision", img) serve a inserire un titolo alla finestra che si apre per la trasmissione del video.
  • cv2.waitKey(1) permette all’utente di mostrare una finestra per un numero definito di millisecondi tra parentesi tonde o finché un determinato pulsante viene premuto.

6) Codice per il riconoscimento delle mani completo

Se non sei riuscito a seguirmi step dopo step ecco a te il codice completo:

# Importiamo i moduli
import cv2
import mediapipe as mp
import time

# Selezioniamo la webcam principale
camera = cv2.VideoCapture(0)

# Utilizziamo il modulo di riconoscimento delle mani
mpHands = mp.solutions.hands
hands = mpHands.Hands()
mpDraw = mp.solutions.drawing_utils

while True:
    # Leggi l'immagine della webcam
    success, img = camera.read()
    imgRGB = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    results = hands.process(imgRGB)

    # Riconosci se ci sono mani nell'immagine ed esegui il loop per ogni mano prendendo i landmarks (x, y)
    if results.multi_hand_landmarks:
        for handLms in results.multi_hand_landmarks:
            mpDraw.draw_landmarks(img, handLms, mpHands.HAND_CONNECTIONS)

    cv2.imshow("Riconoscimento mani - Progetto Computer Vision", img)
    cv2.waitKey(1)

Vedi questo codice senza il calcolo degli FPS sul mio GitHub!

Funzionalità bonus: calcolare e mostrare gli FPS

Allo step uno ti avevo promesso che avremmo aggiunto anche il calcolo degli FPS e come mostrare questo numero su schermo.

Ti ripropongo il codice di prima in cui ho aggiunto il calcolo degli FPS e come usare la funzione cv2.putText() per mettere su schermo questo valore:

# Importiamo i moduli
import cv2
import mediapipe as mp
import time

# Selezioniamo la webcam principale
camera = cv2.VideoCapture(0)

# Utilizziamo il modulo di riconoscimento delle mani
mpHands = mp.solutions.hands
hands = mpHands.Hands()
mpDraw = mp.solutions.drawing_utils

# Prepariamo delle variabili per il calcolo degli FPS
previousTime = 0
currentTime = 0

while True:
    # Leggi l'immagine della webcam
    success, img = camera.read()
    imgRGB = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    results = hands.process(imgRGB)

    # Riconosci se ci sono mani nell'immagine ed esegui il loop per ogni mano prendendo i landmarks (x, y)
    if results.multi_hand_landmarks:
        for handLms in results.multi_hand_landmarks:
            mpDraw.draw_landmarks(img, handLms, mpHands.HAND_CONNECTIONS)

    # Calcola gli FPS
    currentTime = time.time()
    fps = 1 / (currentTime - previousTime)
    previousTime = currentTime

    # Mostra gli FPS su schermo
    cv2.putText(img, 'FPS ' + str(int(fps)), (10, 30),
                cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)

    cv2.imshow("Riconoscimento mani - Progetto Computer Vision", img)
    cv2.waitKey(1)

Sul mio GitHub trovi anche questa versione del codice.

Avviando questo codice con F5 o CTRL+F5 otterrai anche il numero degli FPS:

Ti spiego al volo ciò che ho fatto. Molto semplicemente, prima del loop infinito (while True:) ho preparato due variabili per il calcolo degli FPS che faremo dopo:

# Prepariamo delle variabili per il calcolo degli FPS
previousTime = 0
currentTime = 0

All’interno del loop infinito, dopo il ciclo for e il blocco condizionale if, ho eseguito il calcolo vero e proprio degli FPS con:

# Calcola gli FPS
currentTime = time.time()
fps = 1 / (currentTime - previousTime)
previousTime = currentTime

Infine stampiamo su schermo gli FPS calcolati nella variabile fps con la funzione di OpenCV cv2.putText():

# Mostra gli FPS su schermo
cv2.putText(img, 'FPS ' + str(int(fps)), (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)

Come vedi, nel secondo argomento della funzione, convertiamo prima gli FPS in un numero intero con la funzione int() e poi in stringa con str(). L’ultimo step è fondamentale per rendere leggibile il dato a opencv.

Conclusioni non conclusive

Spero che questa piccola guida per fare il tuo primo progetto di computer vision ti sia piaciuto.

Questa guida non finisce qui e sarà estesa da altri articoli in cui applicheremo ulteriori concetti di logica, matematica e automazione.

L’obiettivo è far comunicare questo sistema, che alla fine ci permette di dare input al computer in maniera non tradizionale, con Arduino e viceversa per creare progetti davvero interessanti.