In questo articolo ci occuperemo di come utilizzare le API messe a disposizione da Intel Perceptual Computing SDK per il recupero dei Face Landmark e Face Pose.
Cosa sono i landmark e le poseSi definiscono Face Landmark una serie di punti (detti marker) ben definiti sulla faccia umana. Ad esempio la punta del naso o l'angolo della bocca. Si definisce Face Pose, invece, la posizione di un viso nello spazio in termini di inclinazione dello stesso lungo i tre assi cartesiani.
Perceptual Computing SDK, Landmark e PoseIntel Computing SDK ci mette a disposizione una serie di API per poter agevolmente recuperare e, quindi, utilizzare sia i face landmark che le face pose.
Il meccanismo con cui si recuperano queste informazioni è molto simile a quanto visto nell'articolo relativo al face detection (http://software.intel.com/it-it/articles/introduzione-a-intel-perceptual-computing-sdk-face-detection).
In particolare, Intel Perceptual, espone le API per il recupero dei landmark e delle pose all'interno dello stesso modulo applicativo del face detection: la classe PXCMFaceAnalysis
Per poter ottenere landmark e pose, abbiamo la necessità di realizzare la nostra classe pipeline derivando, come già visto in altri articoli dalla UtilMPipeline:
Public Class FaceLandmarksPipeline
Inherits UtilMPipeline
Public Sub New()
MyBase.New()
EnableImage(PXCMImage.ColorFormat.COLOR_FORMAT_RGB24, 640, 480)
EnableFaceLocation()
EnableFaceLandmark()
End Sub
End Class
In questo caso abilitiamo la Pipeline all'utilizzo della parte video della web cam (con immagini RGB a 24 bit 640x480), il face location (senza il quale non possiamo ottenere i landmark) e i landmark stessi.
Il comando EnableFaceLandmark(), in maniera del tutto analoga a quanto fa il metodo EnableFaceLocation, fa si che, nel momento in cui andremo a richiedere le informazioni su landmark e le pose, queste siano disponibili (supponendo che il device che stimao utilizzando le supporti).
Recuperare i landmarkPrima di analizzare in dettaglio come procedere per recuperare i landmark di una faccia riconosciuta dall'SDK (per questo serve il face detection attivato), vediamo quali punti ci mette a disposizione l'SDK.
Nella seguente figura sono mostrati i 7 punti diponibili con le relative enumerazioni che li caratterizzano:
Una volta attivata la localizzazione facciale e attivato il recupero dei landmark, l'algoritmo da seguire per recuperare le informazioni sui landmark è il seguente:
- recuperare l'istanza della classe PXCMFaceAnalysis che si occupa dell'effettivo algoritmo di face detection e di face landmark;
- per ogni indice dell'eventuale Face di cui recuperare le informazioni, eseguire una query per capire quale e' il suo identificativo;
- se la faccia e' disponibile, recuperare l'istanza della classe PXCMFaceAnalysis.Landmark da utilizzare per il recupero delle informazioni;
- abilitare il profilo della PXCMFaceAnalysis.Landmark per il recupero dei 7 punti della faccia;
- tramite l'identificativo si recuperano, se disponibili, le informazioni relative ai landmark.
Possiamo recuperare l'istanza di PXCMFaceAnalisys tramite il metodo QueryFace() della class UitlMPipeline:
Dim faceAnalysis As PXCMFaceAnalysis = QueryFace()
Una volta che si ha l'istanza di PXCMFaceAnalysis (questa è Nothing se il Face Location non è stato abilitato con il metodo EnableFaceLocation()), si recupera l'identificativo della i-esima Face rilevata grazie al metodo QueryFace della stessa. Ad esempio l'identificativo della prima Face rilevata è:
Dim queryfaceresult As pxcmStatus = faceAnalysis.QueryFace(0, faceId, faceTimestamp)
Il valore di ritorno della funzione (come nella stragrande maggioranza delle funzioni esposte da Intel Perceptual Computing SDK), indica se il metodo ha avuto successo (pxcmStatus.PXCM_STATUS_NO_ERROR) oppure se non e' stata rilevata una face con tale indice (pxcmStatus.PXC_STATUS_ITEM_UNAVAILABLE).
Nel momento in cui è stata rilevata una Face, abbiamo a disposizione l'identificativo della stessa con il quale possiamo eseguire i restanti passi:
Dim faceLandmark As PXCMFaceAnalysis.Landmark = CType(faceAnalysis.DynamicCast(PXCMFaceAnalysis.Landmark.CUID), PXCMFaceAnalysis.Landmark)
Dim landmarkProfile As PXCMFaceAnalysis.Landmark.ProfileInfo
faceLandmark.QueryProfile(0, landmarkProfile)
landmarkProfile.labels = PXCMFaceAnalysis.Landmark.Label.LABEL_7POINTS
faceLandmark.SetProfile(landmarkProfile)
Dim faceLandmarkData() = New PXCMFaceAnalysis.Landmark.LandmarkData(6) {}
Dim landmarkResult As pxcmStatus = faceLandmark.QueryLandmarkData(faceId, PXCMFaceAnalysis.Landmark.Label.LABEL_7POINTS, faceLandmarkData)
Come possiamo vedere nel precedente pezzo di codice, il recupero dei dati dei landmark avviene tramite l'istanza della classe PXCMFaceAnalysis.Landmark.
Questa si ottiene, a partire dalla PXCMFaceAnalysis, utilizzando il metodo DynamicCast.
Dim faceLandmark As PXCMFaceAnalysis.Landmark = CType(faceAnalysis.DynamicCast(PXCMFaceAnalysis.Landmark.CUID), PXCMFaceAnalysis.Landmark)
DynamicCast è un metodo che troviamo (una sorta di factory) su parecchie classi dell'SDK e che, dato il CUID (Class Unique IDentifer) della classe da istanziare, restituisce l'istanza della stessa.
La creazione efettiva della classe è a carico del modulo corrispondente (in questo caso quello di Face Analysis).
Una volta ottenuta l'istanza di PXCMFaceAnalysis.Landmark, possimao impostare il profilo della stessa per recuperare i 7 punti dei landmark:
Dim landmarkProfile As PXCMFaceAnalysis.Landmark.ProfileInfo
faceLandmark.QueryProfile(0, landmarkProfile)
landmarkProfile.labels = PXCMFaceAnalysis.Landmark.Label.LABEL_7POINTS
faceLandmark.SetProfile(landmarkProfile)
e, infine, recuperare effettivamente i valori dei landmark:
Dim faceLandmarkData() = New PXCMFaceAnalysis.Landmark.LandmarkData(6) {}
Dim landmarkResult As pxcmStatus = faceLandmark.QueryLandmarkData(faceId, PXCMFaceAnalysis.Landmark.Label.LABEL_7POINTS, faceLandmarkData)
Il metodo QueryLandmarkData riempie l'array passato come argomento con le informazioni dei 7 punti landmark.
La struttura PXCMFaceAnalysis.Landmark.LandmarkData contiene i dati al singolo landmark:
- label: valore dell'enumerazione PXCMFaceAnalysis.Landmark.Label indicante il tipo di punto in esame. Ad esempio il valore LABEL_LEFT_EYE_OUTER_CORNER corrisponde all'angolo esterno dell'occhio sinistro;
- fid: l'identificativo della faccia rilevata;
- position: struttura PXCMPoint3DF32 in cui sono contenute le coordinate spaziali del punto rispetto alle coordinate dell'immagine recuperata dalla web cam. Le coordinate bidimensionali x e y sono disponibili anche utilizzando una normale web cam fornita di serie su un notebook, mentre la coordinata z è disponibile solo con la Creative Cam;
Il risultato del metodo QueryLandmarkData della classe PXCMFaceAnalysis.Landmark indica se i dati relativi ai landmark sono disponibili o meno. In particolare se il risultato è uguale a pxcmStatus.PXCM_STATUS_NO_ERROR, allora l'array dei dati è correttamente riempito e, quindi, possiamo riempire l'argomento dell'evento LandmarksCaptured.
Recuperare la face poseLa face pose indica la posizione della testa rilevata nello spazio in termini di rollio (roll), beccheggio (pitch) e imbardata (yaw).
Nella seguente figura è mostrato il significato dei tre termini:
Il recupero dei dati relativi alla posizione della testa avvengono utilizzando l'istanza della classe PXCMFaceAnalysis.Landmark già vista per i landmark ma utilizzando il metodo QueryPoseData:
Dim poseData As PXCMFaceAnalysis.Landmark.PoseData
Dim poseResult As pxcmStatus = faceLandmark.QueryPoseData(faceId, poseData)
La struttura dati PoseData contiene i valori:
- fid: identificativo della faccia rilevata;
- pitch: valore del beccheggio cioè della rotazione della faccia lungo il proprio asse trasversale (quello, per intenderci, che passa per le orecchie);
- roll: valore del rollio cioè della rotazione della faccia lungo il proprio asse longitudinale (quello, che esce dal naso);
- yaw: valore dell'imbardata cioè della rotazione della faccia lungo il suo asse verticale (quello, che esce dalla parte superiore della testa).
Realizzata la pipeline che ci permette di avere le immagini provenienti dalla web cam e gli eventuali dati relativi a landmark e pose, è il momento di pensare all'interfaccia grafica.
La seguente figura mostra la semplice interfaccia che realizzeremo:
Il code behind della form e' il seguente:
Public Class Form1
Private pipeline As FaceLandmarksPipeline
Private LastLandmarkData As FaceLandmarksEventArgs
Private Sub Form1_FormClosing(sender As Object, e As FormClosingEventArgs) Handles Me.FormClosing
RemoveHandler pipeline.ImageCaptured, AddressOf ImageCapturedHandler
RemoveHandler pipeline.LandmarksCaptured, AddressOf LandmarksCapturedHandler
pipeline.Dispose()
End Sub
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
pipeline = New FaceLandmarksPipeline
AddHandler pipeline.ImageCaptured, AddressOf ImageCapturedHandler
AddHandler pipeline.LandmarksCaptured, AddressOf LandmarksCapturedHandler
pipeline.LoopFrames()
End Sub
Private Sub ImageCapturedHandler(sender As Object, e As ImageCapturedEventArgs)
UpdateInterface(e)
End Sub
Private Delegate Sub UpdateImageDelegate(e As ImageCapturedEventArgs)
Private Sub UpdateInterface(e As ImageCapturedEventArgs)
If Me.InvokeRequired Then
Me.Invoke(New UpdateImageDelegate(AddressOf UpdateInterface), e)
Else
If LastLandmarkData IsNot Nothing AndAlso LastLandmarkData.Landmarks IsNot Nothing Then
Using drawer = Graphics.FromImage(e.Image)
For Each ldata In LastLandmarkData.Landmarks
drawer.FillEllipse(New SolidBrush(Color.FromArgb(128, 255, 255, 255)),
CInt(ldata.X - 5), CInt(ldata.Y - 5), 10, 10)
Next
End Using
If LastLandmarkData.Pose IsNot Nothing Then
txtPitch.Text = LastLandmarkData.Pose.Pitch.ToString("##0.0000")
txtRoll.Text = LastLandmarkData.Pose.Roll.ToString("##0.0000")
txtYaw.Text = LastLandmarkData.Pose.Yaw.ToString("##0.0000")
Else
txtPitch.Text = String.Empty
txtRoll.Text = String.Empty
txtYaw.Text = String.Empty
End If
End If
pctImage.Image = e.Image
End If
End Sub
Private Sub LandmarksCapturedHandler(sender As Object, e As FaceLandmarksEventArgs)
LastLandmarkData = e
End Sub
End Class
La form contiene un'istanza della pipeline che viene creata nell'evento di load della pagina.
Contestualmente alla creazione dell'istanza di FaceLandmarksPipeline, vengono agganciati i gestori di evento per il recupero dell'immagine e dei dati relativi a landmark e pose.
Il gestore dell'evento di recupero delle informazioni dei landmark si occupa di salvare in una variabile i dati stessi in modo che il gestore di recupero dell'immagine sia in grado di disegnare l'immagine proveniente dalla web cam, i punti individuati dai landmark e i dati di pose.
Il metodo di aggiornamento dell'interfaccia (in cui utilizziamo il pattern InvokeRequired/Invoke perchè l'evento di cattura dell'immagine potrebbe essere lanciato su un thread che non è quello principale) recupera il contesto grafico dell'immagine proveniente dalla web cam, disegna, per ogni landmark, un cerchio e visualizza i dati di rollio, beccheggio e imbardata all'interno degli appositi textbox:
If LastLandmarkData IsNot Nothing AndAlso LastLandmarkData.Landmarks IsNot Nothing Then
Using drawer = Graphics.FromImage(e.Image)
For Each ldata In LastLandmarkData.Landmarks
drawer.FillEllipse(New SolidBrush(Color.FromArgb(128, 255, 255, 255)),
CInt(ldata.X - 5), CInt(ldata.Y - 5), 10, 10)
Next
End Using
If LastLandmarkData.Pose IsNot Nothing Then
txtPitch.Text = LastLandmarkData.Pose.Pitch.ToString("##0.0000")
txtRoll.Text = LastLandmarkData.Pose.Roll.ToString("##0.0000")
txtYaw.Text = LastLandmarkData.Pose.Yaw.ToString("##0.0000")
Else
txtPitch.Text = String.Empty
txtRoll.Text = String.Empty
txtYaw.Text = String.Empty
End If
End If
pctImage.Image = e.Image