Riconoscimento Facciale con Python

Pubblicato da Michele Saba

Oggi vedremo assieme come usare Python per effettuare operazioni di riconoscimento facciale facilmente tramite un package chiamato face_recognition. Vedremo come verificare se una foto ritragga o meno una determinata persona scrivendo pochissime righe di codice.

Nel tutorial dedicato vedremo poi come fare ciò anche direttamente tramite la webcam, ovvero vedremo come effettuare riconoscimento facciale in real time. Partire prima dalle immagini permetterà di acquisire maggiore familiarità col package, anche per i developer con meno esperienza!

Iniziamo come sempre dall'installazione del software necessario. In questo tutorial useremo Linux, ma le istruzioni e i comandi che daremo sono gli stessi per qualsiasi sistema operativo, dobbiamo solo assicurarci di avere installato i pacchetti e le dipendenze necessari.


Come installare face_recognition

La prima cosa che volete provare a fare, sia che vi troviate su Windows, Linux o Mac OS è creare un ambiente virtuale e dare il comando:

pip install face_recognition

Qualora non riusciate ad installare il package per via di un errore, dovrete allora assicurarvi di installare tutto il software necessario al suo utilizzo nell'ambiente virtuale che avete creato.

Dovrete assicurarvi di installare dlib, una libreria scritta in C++ contenente algoritmi di Machine Learning, utilizzata da face recognition, e prima di poterla installare avrete bisogno anche di cmake e (forse), gcc. Su Manjaro Linux ad esempio ho dovuto installare cmake e python-dlib (con AUR abilitato) e quest'ultimo ha installato tutti i moduli Python nell'ambito dell'installazione principale a livello di sistema.

Quindi, prima di installare face_recognition, dovremo digitare nella nostra shell anche i seguenti comandi in questo stesso ordine:

pip install cmake
pip install dlib

A questo punto possiamo installare face_recognition con PIP: verranno installati automaticamente anche numpy, pillow, click.


Come creare un programma di riconoscimento facciale con face_recognition

La facilità d'utilizzo dell'API fornita da face recognition è davvero sorprendente, soprattutto considerando il fatto che questo genere di operazioni sono tutt'altro che semplici da effettuare manualmente. Come contesto, supponiamo di avere due immagini: una contenente per certo il volto di una persona nota (nel nostro caso Enrico Mentana), e di voler verificare che questa persona sia presente o meno anche nella seconda immagine.

Una volta importato il modulo face_recognition, vogliamo caricare le due immagini da comparare usando la funzione load_image_file:

import face_recognition

known_image = face_recognition.load_image_file("enrico_mentana.jpg")
unknown_image = face_recognition.load_image_file("other.jpg")

load_image_file caricherà le due immagini come array numpy:

print(type(known_image))
print(f"{known_image=}")

# output
<class 'numpy.ndarray'>
known_image=array([[[ 55, 164, 167],
        [ 53, 162, 165],
        [ 56, 165, 168],
        ...,
        [169, 189, 200],
        [169, 189, 200],
        [169, 189, 200]],
(...)
        [ 34,  50,  66],
        [ 33,  49,  65],
        [ 31,  47,  63]]], dtype=uint8)

Ora che le immagini sono caricate e pronte per esser quindi comparate facilmente dobbiamo domandarci: come fare ciò? Come può un algoritmo riconoscere i volti presenti in due foto, e dirci se si tratti della stessa persona?

Le misurazioni della funzione face_encodings di face_recognition

Come esseri umani, facciamo tutto ciò a livello inconscio, grazie a un livello di intuizione affinato da milioni di anni di evoluzione. Ebbene, nel nostro caso, l'algoritmo di Machine Learning utilizzato da face recognition è allenato in maniera tale da riconoscere 128 punti (detti measurements, misurazioni) in ogni viso.

Non sappiamo esattamente quali questi punti siano noi esseri umani valutiamo, ad esempio occhi, orecchie, bocca o naso, ma le cose che per noi sono banali si dimostrano spesso complesse da insegnare ad un algoritmo, e per questo motivo lasciamo a lui il compito di trovare questi punti. Per una spiegazione più approfondita ti consiglio di dare uno sguardo a questo articolo, citato anche nel repo GitHub di face_recognition.

Passando uno di questi array numpy alla funzione face_encodings, possiamo quindi ottenere questa rappresentazione in 128 misurazioni del volto:

import face_recognition

known_image = face_recognition.load_image_file("enrico_mentana.jpg")
unknown_image = face_recognition.load_image_file("other.jpg")

known_encoding = face_recognition.face_encodings(known_image)[0]
unknown_encoding = face_recognition.face_encodings(unknown_image)[0]

Volendo, possiamo dare uno sguardo all'encoding così ottenuto, per farci un'idea più chiara di come questo appaia visivamente:

print(known_encoding)

# output:
[-0.11233788  0.0851004   0.1110497  -0.01893739 -0.08053697 -0.0858957
 -0.10455699 -0.07527749  0.0749362  -0.05087133  0.25046924 -0.08364171
 -0.22168593 -0.05065609 -0.03268618  0.13122961 -0.11662673 -0.11585327
 -0.11603787 -0.06964761 -0.01104837 -0.06330365  0.00256769  0.00111833
 -0.08768467 -0.35832414 -0.13881631 -0.15070605  0.05952905 -0.07453616
 -0.03482205  0.00854951 -0.13885909 -0.06617728  0.04388525  0.05016357
  0.02835207  0.03263851  0.24390836 -0.01745271 -0.14750871  0.0548184
  0.04611176  0.23021351  0.23018567  0.1045877   0.0168637   0.00165874
  0.07412874 -0.28702334  0.08039071  0.17411457  0.16432548  0.09768818
  0.12948139 -0.07740101  0.06346712  0.1018469  -0.1588065   0.06241526
  0.05848081 -0.12720881 -0.04169943  0.02881771  0.22134973  0.13912779
 -0.05311121 -0.04999805  0.22321707 -0.13465768 -0.01776722  0.02407345
 -0.11769787 -0.0827048  -0.26931885  0.03207767  0.42904216  0.13900565
 -0.23185894  0.00872655 -0.08789653 -0.01616874  0.0526951  -0.08056282
 -0.12515272 -0.10174762 -0.08823015  0.0268474   0.20508151 -0.04043303
  0.07147699  0.14764462  0.00624189  0.06535596  0.06227911  0.02255582
 -0.01452096 -0.1229032  -0.09229601  0.01187559  0.05720782 -0.10336162
 -0.00334224  0.06183746 -0.24879934  0.06217309 -0.01809638 -0.07467485
 -0.04503675  0.08669685 -0.00523551  0.00384739  0.19368017 -0.20686358
  0.2585125   0.23501292 -0.07462282  0.06183482  0.00615327  0.02624974
 -0.04922679 -0.0724382  -0.08653736 -0.16384755 -0.01922923 -0.00742123
 -0.00526905  0.05108431]

Ottenuti gli encoding di entrambe le immagini, possiamo ora usare la funzione compare_faces per scoprire se effettivamente si tratti o meno della stessa persona in entrambe le foto.

Di seguito, il codice completo che potete eseguire:

import face_recognition

known_image = face_recognition.load_image_file("volto_noto.jpg")
unknown_image = face_recognition.load_image_file("other.jpg")

known_encoding = face_recognition.face_encodings(known_image)[0]
unknown_encoding = face_recognition.face_encodings(unknown_image)[0]

results = face_recognition.compare_faces([known_encoding], unknown_encoding)
answer = "Si" if results[0] else "No"
print(f"L'immagine sconosciuta raffigura il volto noto? { answer }")


Per rendere questo tutorial e il video allegato quanto più piacevole possibile, ho diviso il tutto in due parti e qui di seguito puoi trovare la seconda parte!

Come funziona l'algoritmo di comparazione tra due volti in face_recognition?

Il codice scritto finora ci fornisce una risposta sicura e secca: ci dice se le due immagini che stiamo comparando contengono in la stessa persona oppure se sono presenti due volti che appartengono a due persone diverse.

Gli algoritmi responsabili per questo genere gli analisti in realtà non forniscono risposte tanto esatte e precise: ciò che l'algoritmo sta facendo è una valutazione di quella che è la distanza tra i due volti, quindi per prima cosa viene analizzata l'immagine per definire l'area della foto in cui è presente un viso, poi, una volta individuati i volti si ottiene l'encoding di ciascuno di questi ed è possibile effettuare una comparazione tra il volto presente in un'immagine e quello presente in un'altra immagine.

La comparazione avrà come risultato un valore che rappresenta la differenza tra i due volti presenti nelle immagini. Questo valore è chiamato distanza e varia in un range da 0.0 a 1.0 e valori uguali o inferiori a 0.6 triggerano il True, quindi danno come risultato che l'immagine contiene la stessa persona.

Se andiamo a vedere il codice della funzione compare_faces, vediamo infatti che tolerance è impostato di default su 0.6:

def compare_faces(known_face_encodings, face_encoding_to_check, tolerance=0.6):
    """
    Compare a list of face encodings against a candidate encoding to see if they match.

    :param known_face_encodings: A list of known face encodings
    :param face_encoding_to_check: A single face encoding to compare against the list
    :param tolerance: How much distance between faces to consider it a match. Lower is more strict. 0.6 is typical best performance.
    :return: A list of True/False values indicating which known_face_encodings match the face encoding to check
    """
    return list(face_distance(known_face_encodings, face_encoding_to_check) <= tolerance)

Possiamo quindi modificare modificare il nostro codice per riflettere questa caratteristica del software di face recognition.


Modifichiamo il programma per mostrare il range di distanza tra due volti

Sostituiamo le variabili results e answer con face_distance e match, con cui specifichiamo la tolleranza per la distanza tra i due volti:

face_distance = face_recognition.face_distance([known_encoding], unknown_encoding)
match = True if face_distance[0] <= 0.6 else False

Con la funzione print mostriamo il range della distanza per rendere più chiara la funzionalità del programma, con una f-string mostriamo il valore di face_distance approssimata a due valori decimali e con un’altra il valore booleano match:

print("Range Distanza: [0.0 - 1.0]")
print(f"L'immagine testata ha una distanza di {face_distance[0]:.2} dall'immagine conosciuta")
print(f"Match: {match}")

Se eseguiamo questo script comparando l’immagine campione con other.jpg vedremo che l'immagine testata una distanza di 0.75 dall'immagine conosciuta e quindi in questo caso il match è uguale a False:

# output
Range Distanza: [0.0 - 1.0]
L'immagine testata ha una distanza di 0.75 dall'immagine conosciuta
Match: False

Se testiamo la stessa immagine che contiene lo stesso identico volto la distanza sarà di 0.0:

# output
Range Distanza: [0.0 - 1.0]
L'immagine testata ha una distanza di 0.00 dall'immagine conosciuta
Match: True

A questo punto il nostro codice completo è come segue:

import face_recognition

known_image = face_recognition.load_image_file("volto_noto.jpg")
unknown_image = face_recognition.load_image_file("other.jpg")

known_encoding = face_recognition.face_encodings(known_image)[0]
unknown_encoding = face_recognition.face_encodings(unknown_image)[0]

face_distance = face_recognition.face_distance([known_encoding], unknown_encoding)
match = True if face_distance[0] <= 0.6 else False

print("Range Distanza: [0.0 - 1.0]")
print(f"I volti analizzati hanno una distanza di {face_distance[0]:.2} dall'immagine conosciuta")
print(f"Match: { match }")


L’interfaccia a riga di comando di face_recognition

Tra i vari strumenti messi a disposizione da face recognition abbiamo anche un’interfaccia da riga di comando che ci permette di effettuare comparazioni automatiche molto rapidamente tra due cartelle di immagini, una in cui sono presenti volti di persone note e un'altra in cui sono presenti volti sconosciuti.

Vediamo i comandi che possiamo usare con —help:

face_recognition --help

# output
Usage: face_recognition [OPTIONS] KNOWN_PEOPLE_FOLDER IMAGE_TO_CHECK

Options:
  --cpus INTEGER           number of CPU cores to use in parallel (can      
                           speed up processing lots of images). -1 means    
                           "use all in system"
  --tolerance FLOAT        Tolerance for face comparisons. Default is 0.6.  
                           Lower this if you get multiple matches for the   
                           same person.
  --show-distance BOOLEAN  Output face distance. Useful for tweaking        
                           tolerance setting.
  --help                   Show this message and exit.

L’output ci indica che nel comando dobbiamo fornire i nomi di due cartelle: KNOWN_PEOPLE_FOLDER e IMAGE_TO_CHECK. Prepariamo queste due cartelle nella directory root del nostro progetto, chiamandole ad esempio known_people e random_photos, e digitiamo il seguente comando:

face_recognition ./known_people/ ./random_photos/

L’operazione darà come risultato una lista dei file della cartella di immagini sconosciute unite ai nomi delle foto della cartella di volti noti che ha riconosciuto al loro interno.

Nella lezione Come Usare La WEBCAM per il Riconoscimento Facciale con Python continueremo ad esplorare le funzionalità del modulo face_recognition imparando a riconoscere i volti in tempo reale utilizzando la Webcam del nostro computer!

Sul mio profilo GitHub puoi trovare questa lezione nel repository dedicato.

Happy Coding!


Vuoi imparare Python come un/a professionista? Dai uno sguardo ai nostri