Introduzione a Intel Perceptual Computing SDK: Face Recognition - Parte 1

In questa prima parte dell'articolo ci occuperemo di capire come possiamo utilizzare le funzionalità di Face Recognition offerte da Perceptual Computing SDK. Nella seconda parte ci occuperemo di capire come riuscire a lavorare con i dati ottenuti dal riconoscimento facciale.

Face recognition e face model

prima di realizzare praticamente il codice che ci permetterà di riconoscere una eventuale faccia, parliamo un attimo di come funziona il meccanismo di face recognition.

La parola d'ordine del face recognition secondo Perceptual Computing è face model.

Una volta individuata la presenza di una faccia davanti allo schermo (con le tecniche già viste in precedenti articoli), possiamo utilizzare le funzionalità dell'SDK per generare un modello di tale faccia. Attraverso il modello possiamo verificare se tale faccia è o meno nota.
Tutte le classi utili per il face recognition si trovano allàinterno della classe PXCMFaceAnalysis.Recognition e, in particolare, il fulcro di tutto è la PXCMFaceAnalysis.Recognition.Model.

La seguente figura mostra le due classi:

Per poter "riconoscere" un volto dobbiamo individuare una faccia, generare il suo modello e confrontare tale modello con una serie di modelli noti.

Per verificare la presenza di un volto ci affidiamo alla classe PXCMFaceAnalysis ed in particolare al suo metodo QueryFace (che ci restituisce, se presente, l'identificativo che la piattaforma assegna alla faccia trovata).

Nel momento in cui abbiamo a disposizione l'identificativo della faccia, possiamo utilizzare il metodo CreateModel della classe PXCMFaceAnalysis.Recognition per generare il modello della faccia stessa (istanza della classe PXCMFaceAnalysis.Recognition.Model).

Il modello, infine, prevede il metodo Compare in grado di dirci se il modello su cui stiamo agendo è presente in un array di modelli passati per argomento.

Capito per sommi tratti il meccanismo di face recognition, andiamo a realizzare il codice per implementarlo. 

Costruiamo la pipeline

Cominciamo con il realizzare la classe, che chiameremo SimpleFaceReconitionPipeline, derivata dalla UtilMPipeline per la gestione del riconoscimento facciale.

Come già visto in altri articoli relativi alle funzionalità di face location e face detection, definiamo una serie di eventi che la nostra classe solleverà nel momento in cui dovrà comunicare con il mondo esterno.

Nel caso specifico definiremo un evento FaceModelCaptured per comunicare al mondo esterno che abbiamo riconosciuto una faccia, i dati relativi alla sua location e l'eventuale modello associato.

La struttura della classe SimpleFaceReconitionPipeline non si discosta molto dalla struttura delle pipeline viste nei precedenti articoli della serie ed in particolare:

Public Class SimpleFaceRecognitionPipeline
    Inherits UtilMPipeline
    Public Event ImageCaptured(sender As Object, e As ImageCapturedEventArgs)
    Public Event FaceModelCaptured(sender As Object, e As FaceModelRecognitionEventArgs)
    Public Event PipelineStarted(sender As Object, e As EventArgs)
    Public Event PipelineStopped(sender As Object, e As EventArgs)
    Public Event PipelineError(sender As Object, e As EventArgs)
 
    Public Sub New()
        MyBase.New()
        EnableImage(PXCMImage.ColorFormat.COLOR_FORMAT_RGB24)
        EnableFaceLocation()
        EnableFaceLandmark()
    End Sub
 
    Private LoopFramesThread As Thread
    Public Overrides Function LoopFrames() As Boolean
        If LoopFramesThread Is Nothing Then
            LoopFramesThread = New Thread(New ThreadStart(AddressOf LoopFramesCode))
            LoopFramesThread.Start()
            Return True
        Else
            Return False
        End If
    End Function
 
    Private Sub LoopFramesCode()
        Try
            RaiseEvent PipelineStarted(Me, EventArgs.Empty)
            Dim result = MyBase.LoopFrames()
            If Not result Then RaiseEvent PipelineError(Me, EventArgs.Empty)
        Catch ex As Exception
            RaiseEvent PipelineStopped(Me, EventArgs.Empty)
        End Try
    End Sub
 
    Public Overrides Sub OnImage(image As PXCMImage)
        Dim data As PXCMImage.ImageData
        Dim status = image.AcquireAccess(PXCMImage.Access.ACCESS_READ,
                                         PXCMImage.ColorFormat.COLOR_FORMAT_RGB32, data)
        If status = pxcmStatus.PXCM_STATUS_NO_ERROR Then
            Dim width As UInt32
            Dim height As UInt32
            Dim colorFormat As PXCMImage.ColorFormat
            If image.GetImageInfo(width, height, colorFormat) Then
                Dim bitmap As Bitmap = data.ToBitmap(width, height)
                RaiseEvent ImageCaptured(Me, New ImageCapturedEventArgs() With {.Image = bitmap, .TimeStamp = DateTime.Now})
                image.ReleaseAccess(data)
            End If
        End If
    End Sub
 
    Public Overrides Function OnNewFrame() As Boolean
        .
 .
 .
    End Function
 
End Class

La classe che definisce l'argomento dell'evento FaceModelCaptured è mostrata nella seguente figura:

Il cuore del riconoscimento facciale si trova nel metodo OnNewFrame in cui vengono eseguiti i seguenti passi:

  1. Recupero dell'istanza della classe PXCMFaceAnalysis

    faceAnalysis = QueryFace()
    

     
  2. recupero, se esiste, dell'identificativo della prima faccia rilevata

    Dim faceId As Integer
    Dim faceTimestamp As ULong
    Dim queryFaceResult As pxcmStatus = faceAnalysis.QueryFace(0, faceId, faceTimestamp)
    Dim e = New FaceModelRecognitionEventArgs With {.FaceID = faceId,
                                                    .TimeStamp = faceTimestamp,
                                                    .FaceDetected = (queryFaceResult = pxcmStatus.PXCM_STATUS_NO_ERROR)}
    If queryFaceResult = pxcmStatus.PXCM_STATUS_NO_ERROR Then
          .
          .
    end if
    

     
  3. Se la faccia esiste (queryFaceResult = pxcmStatus.PXCM_STATUS_NO_ERROR) creiamo l'istanza della classe PXCMFaceAnalysis.Recognition ed impostiamo il profilo della stessa:

    recognizer = faceAnalysis.DynamicCast(Of PXCMFaceAnalysis.Recognition)(PXCMFaceAnalysis.Recognition.CUID)
    Dim pInfo As PXCMFaceAnalysis.Recognition.ProfileInfo
    recognizer.QueryProfile(pInfo)
    recognizer.SetProfile(pInfo)
    


  4. Infine recuperiamo il modello:
    Dim model As PXCMFaceAnalysis.Recognition.Model = Nothing
    Dim status = recognizer.CreateModel(faceId, model)
    If status = pxcmStatus.PXCM_STATUS_NO_ERROR Then
        .
     .
    End If
    

L'interfaccia grafica ed il riconoscimento facciale

La nostra interfaccia grafica utilizzerà la pipeline creata e, in particolare si occuperà di gestire i due eventi fondamentali sollevati dalla pipeline: quello di recupero dell'immagine e quello di recupero del modello.

Lo scopo dell'interfaccia sarà di permettere all'utente di memorizzare modelli dandigli dei nomi e di visualizzare tali nomi nel momento in cui perceptual li riconoscerà.

Nella seguente figura è mostrata l'interfaccia del nostro client:

Il pulsante può essere utilizzato dall'utente per memorizzare il modello dellàeventuale faccia ripresa dalla webcam.

Il code-behind della nostra interfaccia è:

Public Class Form1
    Private ReadOnly ModelsDB As Dictionary(Of String, PXCMFaceAnalysis.Recognition.Model) = _
        New Dictionary(Of String, PXCMFaceAnalysis.Recognition.Model)
 
    Private pipeline As SimpleFaceRecognitionPipeline
 
    Private LastFaceModelData As FaceModelRecognitionEventArgs
 
    Private Sub Form1_FormClosing(sender As Object, e As FormClosingEventArgs) Handles Me.FormClosing
        RemoveHandler pipeline.ImageCaptured, AddressOf ImageCapturedHandler
        RemoveHandler pipeline.FaceModelCaptured, AddressOf FaceModelCapturedHandler
        pipeline.Dispose()
    End Sub
 
    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
        pipeline = New SimpleFaceRecognitionPipeline
        AddHandler pipeline.ImageCaptured, AddressOf ImageCapturedHandler
        AddHandler pipeline.FaceModelCaptured, AddressOf FaceModelCapturedHandler
        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 FaceModelCapturedHandler(sender As Object, e As FaceModelRecognitionEventArgs)
        LastFaceModelData = e
    End Sub
 
    Private Sub UpdateInterface(e As ImageCapturedEventArgs)
        .
 .
    End Sub
 
    Private Sub btnCreateFaceModel_Click(sender As Object, e As EventArgs) Handles btnCreateFaceModel.Click
        .
 .
    End Sub 
End Class

Andiamo ad esaminare i soli metodi importanti per il riconoscimento facciale.

Il gestore dell'evento FaceModelCaptured della nostra pipeline si occupa di memorizzare in una variabile privata della form (LastFaceModelData) il valore dell'argomento passato dalla pipeline stessa:

Private Sub FaceModelCapturedHandler(sender As Object, e As FaceModelRecognitionEventArgs
    LastFaceModelData = e 
End Sub

Tale valore verrà utilizzato dal gestore dell'evento ImageCaptured della pipeline.

Private Sub ImageCapturedHandler(sender As Object, e As ImageCapturedEventArgs)
    UpdateInterface(e)
End Sub
Private Sub UpdateInterface(e As ImageCapturedEventArgs)
    If Me.InvokeRequired Then
        Me.Invoke(New UpdateImageDelegate(AddressOf UpdateInterface), e)
    Else
        Dim bitmap As Bitmap = New Bitmap(e.Image)
        Dim modelname As String = Nothing
        If LastFaceModelData IsNot Nothing Then
            If LastFaceModelData.FaceDetected Then
                If LastFaceModelData.Model IsNot Nothing Then
                    btnCreateFaceModel.Enabled = True
                    If ModelsDB.Any Then
                        Dim models = ModelsDB.Values.ToArray
                        Dim index As UInteger
                        Dim status = LastFaceModelData.Model.Compare(models, index)
                        If status = pxcmStatus.PXCM_STATUS_NO_ERROR Then
                            modelname = ModelsDB.Keys(CInt(index))
                        End If
                    End If
                End If
 
                Using drawer = Graphics.FromImage(bitmap)
                    drawer.FillRectangle(New SolidBrush(Color.FromArgb(128, 255, 255, 255)),
                                         New Rectangle(New Point(LastFaceModelData.FaceRectangle.X, LastFaceModelData.FaceRectangle.Y),
                                                       New Size(LastFaceModelData.FaceRectangle.Width, LastFaceModelData.FaceRectangle.Height)))
                    If modelname IsNot Nothing Then
                        drawer.DrawString(String.Format("{0}", modelname), New Font("Times Roman", 12), Brushes.Red,
                                                        New Point(LastFaceModelData.FaceRectangle.X + 5, LastFaceModelData.FaceRectangle.Y + 5))
                    Else
                        drawer.DrawString(String.Format("FaceID={0}", LastFaceModelData.FaceID), New Font("Times Roman", 12), Brushes.Black,
                                                        New Point(LastFaceModelData.FaceRectangle.X + 5, LastFaceModelData.FaceRectangle.Y + 5))
                    End If
 
                End Using
            End If
 
        End If
        lblRecognizedModelName.Text = modelname
        pctImage.Image = bitmap
    End If
End Sub

Il metodo UpdateInterface si occupa di:

  1. costruire una bitmap a partire dalla bitmap proveniente dalla webcam;
  2. Verificare se l'oggetto contenuto nella variabile LastFaceModelData indica di aver rilevato una faccia e se è presente un modello;
  3. se è presente un modello (LastFaceModelData.Model non nothing) e se il dictionary che contiene l'elenco dei modelli validi non è vuoto, allora si verifica, grazie al metodo Compare della classe PXCMFaceAnalysis.Recognition.Model, se il modello corrente è uno di quelli memorizzati (cioè, in definitiva, se la faccia è nota):

    If LastFaceModelData.Model IsNot Nothing Then
        btnCreateFaceModel.Enabled = True
        If ModelsDB.Any Then
            Dim models = ModelsDB.Values.ToArray
            Dim index As UInteger
            Dim status = LastFaceModelData.Model.Compare(models, index)
            If status = pxcmStatus.PXCM_STATUS_NO_ERROR Then
                modelname = ModelsDB.Keys(CInt(index))
            End If
        End If
    Else
        btnCreateFaceModel.Enabled = False
    End If
    

     
  4. in caso di faccia nota si disegna la posizione della stessa riportando il nome del modello, altrimenti si disegna la posizione della accia con il proprio identificativo.

Il risultato che otteniamo è mostrato nella seguente figura:

La memorizzazione del modello tra l'elenco di quelli validi avviene nel momento in cui l'utente preme il tasto:

Private Sub btnCreateFaceModel_Click(sender As Object, e As EventArgs) Handles btnCreateFaceModel.Click
    If LastFaceModelData IsNot Nothing AndAlso LastFaceModelData.Model IsNot Nothing Then
        Dim model = LastFaceModelData.Model
        Dim modelName = InputBox("Nome del modello")
        If Not String.IsNullOrWhiteSpace(modelName) Then
            ModelsDB.Add(modelName, model)
        End If
    End If
End Sub

Viene richiesto il nome da dare al modello (che verrà visualizzato in caso di riconoscimento) e salvato lo stesso in una classe di tipo dictionary utilizzando come chiave il nome del modello stesso.

Per come è strutturata l'applicazione, l'elenco dei modelli ritenuti validi si resetta ogni volta che chiudiamo la nostra applicazione. Nella prossima parte dell'articolo vedremo come sfruttare le funzionalità di serializzazione dei modelli forniteci dalla piattaforma per realizzare un repository permanente di modelli.

Tutte le funzionalità viste in questo articolo possono essere testate e sono funzionanti con la web cam integrata del vostro notebook senza bisogno di avere la Creative Gesture Cam.

Per informazioni più dettagliate sulle ottimizzazioni basate su compilatore, vedere il nostro Avviso sull'ottimizzazione.