EmguCV nasce con lo scopo di permettere al linguaggio .NET di chiamare le API della celebre libreria OpenCV dedicata alla visione artificiale. EmguCV è una libreria wrapper “cross-platform” in grado di girare su Windows, Linux, MAC OS X, iPad, iPhone e Andorid.
Uno degli utilizzi base e più interessanti della libreria, è la “face detection“, rilevazione dei visi all’interno di video o immagini. In questo articolo vedremo come arrivare a scrivere un semplice esempio funzionante in tempi brevi.
Avendo già introdotto a grandi linee la teoria basata sui filtri Haar (Viola-Jones [1]), in un precedente articolo su OpenCV, passiamo direttamente alla fase di impostazione dell’ambiente e allo sviluppo di un primo esempio (Con Visual Studio 2013 su Windows 7 64 Bit).
Il setup di EmguCV è reperibile sul sito http://sourceforge.net/projects/emgucv/files/latest/download/.
Nel mio caso, l’ultima versione disponibile risulta essere la 2.9.0.1922-beta.
Scaricandola ed eseguendone il setup, tutti i file verranno copiati in una cartella “EmguCV” sotto il percorso di installazione che abbiamo scelto.
La cartella “bin” contiene le dll che dovremo aggiungere al nostro progetto. Inoltre all’interno della sottocartella “x86″ e “x64″ sono presenti le librerie originali di OpenCV. A seconda della versione dobbiamo aggiungere alla PATH di sistema il percorso completo che punta rispettivamente alle versioni x86 o a 64 bit, a seconda della versione che intendiamo utilizzare.
Nel mio caso ho aggiunto:
C:\Users\matteo>PATH ( ......... )E:\Emgu\emgucv-windows-universal-cuda 2.9.0.1922\bin\x86
Siamo pronti ad aprire visual Studio, scegliamo un nuovo progetto “Console”.
Assicuriamoci di aggiungere gli assembly di openCV, nei riferimenti dovremo vedere:
Di seguito il listato del programma di esempio,
Per prima cosa creiamo una nuova istanza della ‘Capture’ di Emgucv.
Chiamata in questo modo, senza parametri, viene utilizzata la prima webcam disponibile sul computer, nel caso vi siano più cam installate, un parametro ‘index’ può essere passato. Dalla documentazione:
public Capture( int camIndex ) camIndex Type: System..::..Int32 The index of the camera to create capture from, starting from 0
Carico i filtri ‘HaarCascade’ pescandoli dalla directory originale di opencv. Se non sono presenti nelle cartelle di emgucv potete trovarli scaricando la versione C++ di opecv (come ho fatto).
// ... // Create a Camera capture instance Capture cap = new Capture(); // ... // Load cascade classifier configurations CascadeClassifier haar_face = new CascadeClassifier( "E:\\opencv\\data\\haarcascades\\haarcascade_frontalface_alt_tree.xml"); CascadeClassifier haar_nose = new CascadeClassifier( "E:\\opencv\\data\\haarcascades\\haarcascade_mcs_nose.xml"); CascadeClassifier haar_mouth = new CascadeClassifier( "E:\\opencv\\data\\haarcascades\\haarcascade_mcs_mouth.xml"); CascadeClassifier haar_left_eye = new CascadeClassifier( "E:\\opencv\\data\\haarcascades\\haarcascade_lefteye_2splits.xml"); CascadeClassifier haar_right_eye = new CascadeClassifier( "E:\\opencv\\data\\haarcascades\\haarcascade_righteye_2splits.xml");
La creazione degli oggetti necessari, frames, finestre di visualizzazione e oggetti utili viene fatta subito dopo:
// ... // Create a Image viewers to show the frames ImageViewer Original_view = new ImageViewer(); Original_view.Text = "Original"; ImageViewer feature_search_view = new ImageViewer(); feature_search_view.Text = "Features research areas"; ImageViewer features_view = new ImageViewer(); features_view.Text = "Features"; // Create the frames Image<Bgr, byte> features_frame; Image<Bgr, byte> orig_frame; Image<Gray, byte> grayframe; // Show windows Original_view.Show(); feature_search_view.Show(); features_view.Show(); form.Show(); Rectangle[] faces; Rectangle[] detection; Rectangle face = new Rectangle(-1, -1, -1, -1); Rectangle nose = new Rectangle(-1, -1, -1, -1); Rectangle left_eye = new Rectangle(-1, -1, -1, -1); Rectangle right_eye = new Rectangle(-1, -1, -1, -1); Rectangle mouth = new Rectangle(-1, -1, -1, -1); Rectangle eyes_roi = new Rectangle(-1, -1, -1, -1); Rectangle mouth_roi = new Rectangle(-1, -1, -1, -1); Rectangle nose_roi = new Rectangle(-1, -1, -1, -1); Bgr red = new Bgr(Color.Red); Bgr blue = new Bgr(Color.Blue); Bgr white = new Bgr(Color.White); Bgr black = new Bgr(Color.Black); Bgr green = new Bgr(Color.Green);
A questo punto possiamo iniziare il loop principale del programma (Nel caso di un progetto Windows Form, il loop deve essere messo in un thread separato per evitare il congelamento dell’interfaccia utente).
la chiamata API: ‘DetectMultiScale‘, rileva i visi presenti nel frame passato come argomento e ritorna uno o più oggetti ‘Rectangle’, per noi coordinate, ove sono stati rilevati visi.
public Rectangle[] DetectMultiScale( Image<Bgr, byte> image, double hitThreshold, Size winStride, Size padding, double scale, int finalThreshold, bool useMeanshiftGrouping ) Parameters image Type: Emgu.CV..::..Image<(Of <(<'Bgr, Byte>)>)> The image to search in hitThreshold Type: System..::..Double Threshold for the distance between features and SVM classifying plane. Usually it is 0 and should be specfied in the detector coefficients (as the last free coefficient). But if the free coefficient is omitted (which is allowed), you can specify it manually here. winStride Type: System.Drawing..::..Size Window stride. Must be a multiple of block stride. padding Type: System.Drawing..::..Size scale Type: System..::..Double Coefficient of the detection window increase. finalThreshold Type: System..::..Int32 After detection some objects could be covered by many rectangles. This coefficient regulates similarity threshold. 0 means don't perform grouping. Should be an integer if not using meanshift grouping. Use 2.0 for default useMeanshiftGrouping Type: System..::..Boolean If true, it will use meanshift grouping. Return Value The regions where positives are found
Allo stesso tempo, mi preoccupo di disegnare sul frame quest’area rilevata e ripeto l’operazione anche per l’occhio sinistro, destro, il naso e la bocca.
Sfruttando la proprietà ‘ROI’ dell’oggetto ‘Image<>’, possiamo risparmiare tempo processore e aumentare la precisione del sistema. ‘ROI’, è un oggetto ‘Rectangle’ relativo al frame, il filtro ‘HaarCascade’ cercherà solo all’interno di quest’area. Al termine mi devo anche ricordare di resettare questo parametro tramite ‘Rectangle.Empty’.
// For each frame captured from camera, detects face and his features. for (; ; ) { orig_frame = cap.QueryFrame(); grayframe = orig_frame.Convert<Gray, byte>(); features_frame = orig_frame.Convert<Bgr, byte>(); grayframe._EqualizeHist(); LineSegment2D J1 = new LineSegment2D(); LineSegment2D J2 = new LineSegment2D(); LineSegment2D J3 = new LineSegment2D(); LineSegment2D J4 = new LineSegment2D(); LineSegment2D J5 = new LineSegment2D(); LineSegment2D J6 = new LineSegment2D(); LineSegment2D J7 = new LineSegment2D(); LineSegment2D J8 = new LineSegment2D(); if (orig_frame != null) { faces = haar_face.DetectMultiScale( grayframe, 1.1, 1, new Size(120, 120), new Size(grayframe.Width - 10, grayframe.Height - 10) ); if (faces.Count() > 0) { // Features detection face = nose_roi = grayframe.ROI = faces[0]; detection = haar_nose.DetectMultiScale( grayframe, 1.1, 1, new Size(15, 15), new Size(grayframe.Width / 2, grayframe.Height / 2) ); nose = (detection.Count() > 0) ? detection[0] : new Rectangle(-1, -1, -1, -1); grayframe.ROI = Rectangle.Empty; eyes_roi = grayframe.ROI = new Rectangle(face.X, faces[0].Y + face.Height / 5, face.Width, face.Height / 3); detection = haar_left_eye.DetectMultiScale( grayframe, 1.1, 1, new Size(10, 10), new Size(face.Width / 3, face.Height / 3) ); left_eye = (detection.Count() > 0) ? detection[0] : new Rectangle(-1, -1, -1, -1); detection = haar_right_eye.DetectMultiScale( grayframe, 1.1, 1, new Size(10, 10), new Size(face.Width / 3, face.Height / 3) ); right_eye = (detection.Count() > 0) ? detection[0] : new Rectangle(-1, -1, -1, -1); grayframe.ROI = Rectangle.Empty; mouth_roi = grayframe.ROI = new Rectangle(face.X, face.Y + face.Height * 2 / 3, face.Width, face.Height / 3); detection = haar_mouth.DetectMultiScale( grayframe, 1.1, 1, new Size(5, 5), new Size(face.Width / 2, face.Height / 2) ); mouth = (detection.Count() > 0) ? detection[0] : new Rectangle(-1, -1, -1, -1); grayframe.ROI = Rectangle.Empty; // Draw ROI areas grayframe.Draw(face, new Gray(), 2); grayframe.Draw(new Rectangle(eyes_roi.X, eyes_roi.Y, eyes_roi.Width, eyes_roi.Height), new Gray(), 2); grayframe.Draw(new Rectangle(nose_roi.X, nose_roi.Y, nose_roi.Width, nose_roi.Height), new Gray(), 2); grayframe.Draw(new Rectangle(mouth_roi.X, mouth_roi.Y, mouth_roi.Width, mouth_roi.Height), new Gray(), 2); // If the features have not been found then, continue with the next frame if ((face.X != -1) & (left_eye.X != -1) & (right_eye.X != -1) & (mouth.X != -1) & (nose.X != -1)) { // Draw rectangles features_frame.Draw(face, red, 2); features_frame.Draw( new Rectangle(eyes_roi.X + left_eye.X, eyes_roi.Y + left_eye.Y, left_eye.Width, left_eye.Height), blue, 1); features_frame.Draw( new Rectangle(eyes_roi.X + right_eye.X, eyes_roi.Y + right_eye.Y, right_eye.Width, right_eye.Height), blue, 1); features_frame.Draw( new Rectangle(face.X + nose.X, face.Y + nose.Y, nose.Width, nose.Height), white, 1); features_frame.Draw( new Rectangle(mouth_roi.X + mouth.X, mouth_roi.Y + mouth.Y, mouth.Width, mouth.Height), green, 1); grayframe.Draw( new Rectangle(eyes_roi.X + left_eye.X, eyes_roi.Y + left_eye.Y, left_eye.Width, left_eye.Height), new Gray(), 1); grayframe.Draw( new Rectangle(eyes_roi.X + right_eye.X, eyes_roi.Y + right_eye.Y, right_eye.Width, right_eye.Height), new Gray(), 1); grayframe.Draw( new Rectangle(face.X + nose.X, face.Y + nose.Y, nose.Width, nose.Height), new Gray(), 1); grayframe.Draw( new Rectangle(mouth_roi.X + mouth.X, mouth_roi.Y + mouth.Y, mouth.Width, mouth.Height), new Gray(), 1); // Draw center points Point left_eye_center = new Point(eyes_roi.X + left_eye.X + left_eye.Width / 2, eyes_roi.Y + left_eye.Y + left_eye.Height / 2); Point right_eye_center = new Point(eyes_roi.X + right_eye.X + right_eye.Width / 2, eyes_roi.Y + right_eye.Y + right_eye.Height / 2); Point nose_center = new Point(nose_roi.X + nose.X + nose.Width / 2, nose_roi.Y + nose.Y + nose.Height / 2); Point nose_left_center = new Point(nose_roi.X + nose.X, nose_center.Y); Point nose_right_center = new Point(nose_roi.X + nose.X + nose.Width, nose_center.Y); Point mouth_center = new Point(mouth_roi.X + mouth.X + mouth.Width / 2, mouth_roi.Y + mouth.Y + mouth.Height / 2); features_frame.Draw(new CircleF(left_eye_center, 3.0f), blue, 1); features_frame.Draw(new CircleF(right_eye_center, 3.0f), blue, 1); features_frame.Draw(new CircleF(nose_center, 3.0f), white, 1); features_frame.Draw(new CircleF(mouth_center, 3.0f), green, 1); Point middle_eyes = new Point( left_eye_center.X + ((right_eye_center.X - left_eye_center.X) / 2), left_eye_center.Y + ((right_eye_center.Y - left_eye_center.Y) / 2) ); // 8 Characteristic parameters J1 = new LineSegment2D(left_eye_center, right_eye_center); J2 = new LineSegment2D(left_eye_center, mouth_center); J3 = new LineSegment2D(right_eye_center, mouth_center); J4 = new LineSegment2D(left_eye_center, nose_center); J5 = new LineSegment2D(right_eye_center, nose_center); J6 = new LineSegment2D(mouth_center, nose_center); J7 = new LineSegment2D(middle_eyes, nose_center); J8 = new LineSegment2D(nose_left_center, nose_right_center); features_frame.Draw(J1, black, 1); // Distance between middles of the eyes features_frame.Draw(J2, black, 1); // Distance between middle of the left eyes and middle point of mouth features_frame.Draw(J3, black, 1); // Distance between middle of the right eyes and middle point of mouth features_frame.Draw(J4, black, 1); // Distance between middle of the left eyes and middle point of nose features_frame.Draw(J5, black, 1); // Distance between middle of the rigth eyes and middle point of nose features_frame.Draw(J6, black, 1); // Distance between middle point of mouth and middle point of nose features_frame.Draw(J7, black, 1); // Distance of middle point of middle eyes and middle of nose features_frame.Draw(J8, black, 1); // Width of nose } } Original_view.Image = orig_frame; feature_search_view.Image = grayframe; features_view.Image = features_frame; } else break; Original_view.Refresh(); feature_search_view.Refresh(); features_view.Refresh(); Console.Clear(); Console.WriteLine("Face features:"); Console.WriteLine("---------------------------------------"); Console.WriteLine("J1 Distance between middles of the eyes: {0:N}", J1.Length); Console.WriteLine("J2 Distance between middle of the left eyes and middle point of mouth: {0:N}", J2.Length); Console.WriteLine("J3 Distance between middle of the right eyes and middle point of mouth: {0:N}", J3.Length); Console.WriteLine("J4 Distance between middle of the left eyes and middle point of nose: {0:N}", J4.Length); Console.WriteLine("J5 Distance between middle of the right eyes and middle point of nose: {0:N}", J5.Length); Console.WriteLine("J6 Distance between middle point of mouth and middle point of nose: {0:N}", J6.Length); Console.WriteLine("J7 Distance of middle point of middle eyes and middle of nose: {0:N}", J7.Length); Console.WriteLine("J8 Width of nose: {0:N}", J8.Length); }
Nell’esempio ho creato oggetti ‘LineSegment2D’ che rappresentano le distanza fra occhi, naso e bocca.
Questi elementi sono caratteristici per persona e possono costituire la base di una serie di parametri per la face recognition.
Pur rimanendo un esempio base, la capacità di rilevare le caratteristiche principali di un viso tramite una webcam anche su piattaforme mobile si presenta come una possibilità molto interessante utile allo sviluppo di tecnologie di interoperabilità uomo-device.
Esempio di funzionamento
Archiviato in:C#, Computer vision, Informatica, Programmazione Tagged: EmguCV, face detection, OpenCV