Nei precedenti tutorial (parte 1, parte 2) ho illustrato come realizzare un Widget Qt OpenGL per visualizzare le immagini OpenCV in una GUI, è ora di realizzare un’applicazione pratica che sfrutti tutta la potenza della libreria OpenCV.
Il tutorial che segue illustrerà come realizzare un semplice Face Detector: un programma in grado di identificare tutte le facce presenti in ogni frame di un flusso video proveniente da un camera digitale (una webcam in questo caso particolare).
Per ogni faccia individuata sarà inoltre possibile scegliere se riconoscere anche la zona degli occhi, del naso e della bocca.
Il codice che andrò a illustrarvi è la base degli strumenti automatici (autoscatto sul sorrisi, autoscatto con occhi aperti, ecc) che ormai si trovano in tutte le macchine fotografiche digitali di ultima generazione.
Il tutorial vi permetterà di realizzare un’applicazione come quella mostrata nel seguente video:
La Face Detection sfrutta l’algoritmo “Cascade Classification” implementato in OpenCV e addestrato a riconoscere facce, occhi, nasi e bocche.
In questo caso particolare un primo classificatore si occuperà innanzitutto di identificare tutte le facce presenti nell’immagine analizzata, quindi per ogni faccia tre diversi classificatori identificheranno gli occhi, il naso e la bocca.
Iniziamo dal codice sorgente realizzato per la seconda parte del tutorial sul Widget Qt, che potete reperire qui.
Prima di tutto aggiungiamo al progetto il modulo OpenCV di “Object Detection”, apriamo il file “pro” e aggiungiamo il modulo della libreria:
LIBS += -lopencv_core \
-lopencv_objdetect \
-lopencv_highgui
Nel file della finestra principale della GUI inseriamo il seguente codice di inclusione:
#include "opencv2 /objdetect/objdetect.hpp"
e le seguenti variabili private:
int mCameraEventId; cv::Mat mOrigImage; cv::Mat mElabImage; cv::VideoCapture mCapture; // ---> Face detectors cv::CascadeClassifier mFaceDetector; cv::CascadeClassifier mEyeDetector; cv::CascadeClassifier mMouthDetector; cv::CascadeClassifier mNoseDetector; // < --- Face detectors
Nella funzione di "Start" della camera dobbiamo verificare che i classificatori siano inizializzati:
void CMainWindow::on_actionStart_triggered()
{
if( mFaceDetector.empty() )
mFaceDetector.load( FEAT_FACE_FILE );
if( mEyeDetector.empty() )
mEyeDetector.load( FEAT_EYE_FILE );
if( mNoseDetector.empty() )
mNoseDetector.load( FEAT_NOSE_FILE );
if( mMouthDetector.empty() )
mMouthDetector.load( FEAT_MOUTH_FILE );
if( !mCapture.isOpened() )
{
mCapture.open(0);
mCameraEventId = startTimer( 50 );
}
}
I classificatori per essere inizializzati hanno bisogno di quattro file, definiti con delle MACRO:
#define FEAT_FACE_FILE "haarcascade_frontalface_default.xml" #define FEAT_EYE_FILE "haarcascade_mcs_eyepair_big.xml" #define FEAT_NOSE_FILE "haarcascade_mcs_nose.xml" #define FEAT_MOUTH_FILE "haarcascade_mcs_mouth.xml"
i quattro file devono essere copiati dalla cartella “[OpenCV]/data/haarcascades” nella cartella in cui viene generato l’eseguibile del nostro programma.
Inizializzati i classificatori è possibile utilizzarli ogni volta che la webcam ci fornirà una nuova immagine, cioè all’interno della funzione di gestione del timer (vd tutorial precedente):
void CMainWindow::timerEvent(QTimerEvent *event)
{
if( event->timerId() == mCameraEventId )
{
// Stop Timer to stop receiving data from camera during elaboration
killTimer( mCameraEventId );
mCapture >> mOrigImage;
mOrigImage.copyTo( mElabImage );
if( ui->checkBox_fullFace->isChecked() )
{
vector< cv::Rect > faceVec;
float scaleFactor = 1.5f; // Change Scale Factor to change speed
mFaceDetector.detectMultiScale( mOrigImage, faceVec, scaleFactor );
//mFaceDetector.detectSingleScale() mOrigImage, rectVec );
for( size_t i=0; i Eye Detection
if( ui->checkBox_eyes->isChecked() )
{
vector< cv::Rect > eyeVec;
mEyeDetector.detectMultiScale( face, eyeVec );
for( size_t j=0; j Nose Detection
if( ui->checkBox_nose->isChecked() )
{
vector< cv::Rect > noseVec;
mNoseDetector.detectMultiScale( face, noseVec, 3 );
for( size_t j=0; j Mouth Detection
// [Searched in the bottom half face]
if( ui->checkBox_mouth->isChecked() )
{
vector< cv::Rect > mouthVec;
cv::Rect halfRect = faceVec[i];
halfRect.height /= 2;
halfRect.y += halfRect.height;
cv::Mat halfFace = mOrigImage( halfRect );
mMouthDetector.detectMultiScale( halfFace, mouthVec, 3 );
for( size_t j=0; jcameraWidget->showImage( mElabImage );
// Timer reactivation
mCameraEventId = startTimer( 50 );
}
}
Il codice è molto articolato, di seguito una descrizione riga per riga:
- [3]: Verifica dell’ID del Timer che ha scatenato l’evento
- [6]: Interruzione momentanea del timer in modo che la webcam non scateni nessun nuovo evento durante l’elaborazione dell’ultima immagine acquisita
- [7]: Acquisizione dell’immagine
- [8]: Copia dell’immagine originale in modo che le elaborazioni non la rovinino
- [9]: Ricerca delle facce solo se abilitata (vd filmato di esempio)
- [11]: Vettore contenente i Bounding Rect delle facce identificate
- [12]: Parametro di scala per l’identificazione delle facce (vd documentazione OpenCV per maggiori dettagli
- [13]: Ricerca delle facce nelle immagini. Questa funzione popolerà il vettore “faceVec” con tutti i rettangoli che contengono le facce identificate.
- [15]: il “for” serve ad eseguire le elaborazioni di seguito descritte per ogni faccia identificata.
- [17]: disegno in rosso del rettangolo della faccia corrente. Il rettangolo è disegnato su “mElabImage” in modo che l’immagine originale non venga sporcata per le successive elaborazioni.
- [18]: estrazione dall’immagine originale della porzione che contiene unicamente la faccia analizzata. In questo modo possono essere estratte le “sottoparti” solo dalla faccia corrente.
- [21]: verifica dell’attivazione della ricerca degli occhi
- [23]: vettore contenente i rettangoli relativi alla zona degli occhi.
- [24]: ricerca degli occhi
- [25-31]: disegno dei rettangoli degli occhi (normalmente il rettangolo è uno solo visto che ognuno di noi ha solo due occhi
) - [34-68]: analogamente a quanto fatto per gli occhi, sono ricercati il naso e la bocca.
- [53-56]: per migliorare il risultato finale la bocca è ricercata solo nella metà inferiore della faccia.
- [69]: visualizzazione del risultato
- [71]: riattivazione del timer per l’acquisizione dell’immagine successiva
Il tutorial è giunto alla conclusione. Il codice dell’applicazione è disponibile qui.
Grazie a OpenCV un’operazione molto complessa come quella di riconoscere le facce e le loro sotto-parti può essere implementata con solo 70-80 righe di codice.
Dietro queste 80 righe di codice ci sono anni e anni di studi sull’intelligenza artificiale e il machine learning e vi invito a leggere la documentazione OpenCV per riendervi conto cosa effettivamente ci sia dietro.
Se volete discutere le tecniche utilizzate non esitate a contattarmi via email, su Facebook, Twitter o LinkedIn.





