Introduzione a Intel Perceptual Computing SDK: HelloCam!

Quando si approcia ad un nuovo linguaggio di sviluppo o ad un nuovo SDK, di solito, il primo programma che si scrive viene chiamato "Hello World!". In questo caso si tratta di un primo esempio di come utilizzare le funzionalità dell'SDK Perceptual relativamente alle feture della WebCam e, quindi, il nome non può che essere "Hello Cam!".

Un pò di architettura

Nella seguente figura è rappresentato il modello dell'architettura su cui si fonda Perceptual SDK.

In particolare, in questo articolo costruiremo un esempio utilizzando le funzionalita' messe a disposizione dal porting di Perceptual per .NET (mostrate in rosso nella Figura 1).
In particolare utilizzeremo le classi presenti all'interno della dll libpxcclr.dll che si trova nelle cartelle:

$(PCSDK_DIR)binwin32
$(PCSDK_DIR)binx64
 

Questa libreria fa da wrapper per le classi e le funzionalita' C++ esponendole al mondo .NET (C# e VB.NET).

Il progetto Windows Form

Per cominciare apriamo il buon Visual Studio 2012 e creaiamo un nuovo progetto Windows form come mostrato nella seguente figura

Una volta creato il progetto dobbiamo aggiungere la reference alla dll utilizzando il comando "Add Reference..."

 

In questo progetto utilizzeremo le funzonalità messe a disposizione della classe UtilMPipeline.
Questa, di fatto, fa da ponte tra il mondo .NET e la classe C++ UtilPipeline (http://software.intel.com/sites/landingpage/perceptual_computing/documentation/html/utilpipeline.html) e rappresenta il modo piu' semplice per accedere alle funzionalità offerte dall'SDK.
In questo semplice esempio ci limiteremo a gestire le immagini provenienti dalla cam per visualizzarle nella nostra interfaccia.
In particolare, implementiamo una nostra classe che estende la classe wrapper UtilMPipeline.

Public Class ImageCamPipeline
    Inherits UtilMPipeline
    Public Sub New()
        MyBase.New()
        EnableImage(PXCMImage.ColorFormat.COLOR_FORMAT_RGB24, 640, 480)
    End Sub
 
End Class
 

Il costruttore della classe abilita la stessa all'utilizzo della parte relativa alle immagini provenienti dalla telecamera (nel nostro esempio il formato dell'immagine e' RGB a 24 bit con una risoluzione di 640 per 480 pixel). Vedremo in seguito quali sono le altre possibilità offerte dal metodo EnableImage e dall'enumerazione PXCMImage.ColorFormat.
Per cominciare possiamo notare che tutte le classi "managed" (cioe' afferenti al mondo .NET) hanno la lettera M tra tra il prefisso che ne indica l'area di utilizzo e il suffisso che ne indica la funzionalita': ad esempio la PXCMImage è la classe .NET che implementa l'interfaccia PXCImage del framework Perceptual.

A questo punto dobbiamo gestire il flusso di immagini provenienti dall Web Cam ed inviarle alla nostra interfaccia. Per fare questo è sufficiente eseguire l'override del metoto OnImage della UtilMPipeline.

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,
                                                                            .FPS = CalculateFPS(DateTime.Now)})
            image.ReleaseAccess(data)
        End If
    End If
End Sub
 

Il metodo di estensione GetImageInfo non fa parte dell'SDK di Perceptual ma è stato realizzato per sopperire al fatto che la struttura dati contenente i dati dell'immagine ha lo stesso nome (a meno della minuscola iniziale) della proprietà dell'oggetto PXCMImage che effettivamente li contiene e, quindi, entrambe non sono visibili in un linguaggio case insensitive come VB.NET..

La classe ImageCamPipeline comunica all'esterno tramite eventi, ed è per questo motivo che, una volta recuperata l'immagine dalla cam, solleva un evento per comunicare al mondo esterno l'immagine stessa.

Public Event ImageCaptured(sender As Object, e As ImageCapturedEventArgs)
 

In particolare, oltre a comunicare la sola immagine, si è deciso di comunicare anche il timestamp (data e ora) di acquisizione dell'immagine e in frame rate istantaneo (ottenuto dalla differenza tra i timestamp di due immagini consecutive). Il tutto è "impacchettato" nell'argomento dell'evento:

Public Class ImageCapturedEventArgs
    Inherits EventArgs
    Public Property Image As Bitmap
    Public Property TimeStamp As DateTime
    Public Property FPS As Integer?
End Class
 

Per completezza, l'FPS istantaneo è calcolato nel seguente modo:

Private Function CalculateFPS(currentTimeStamp As Date) As Integer?
        Dim fps As Integer? = Nothing
        If LastImageTimestamp.HasValue Then
            fps = CInt(Math.Floor(1000D / (currentTimeStamp - LastImageTimestamp.Value).TotalMilliseconds))
        End If
        Me.LastImageTimestamp = currentTimeStamp
        Return fps
End Function
 

Creata la nostra classe di interazione con la web cam, possiamo cominciare a creare l'interfaccia che mostrerà l'immagine stessa e il frame rate istantaneo.
Nella seguente figura è mostrata la finestra di disegno di Visual Studio che riporta la parte di design dell'interfaccia stessa:

Il code behind della stessa è relativamente semplice :

Public Class Form1
    Private pipeline As ImageCamPipeline
   Private Sub Form1_FormClosing(sender As Object, e As FormClosingEventArgs) Handles Me.FormClosing
        pipeline.Dispose()
    End Sub
    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
        pipeline = New ImageCamPipeline
        AddHandler pipeline.ImageCaptured, AddressOf ImageCapturedHandler
        pipeline.LoopFrames()
    End Sub
    Private Sub ImageCapturedHandler(sender As Object, e As ImageCapturedEventArgs)
        UpdateInterface(e)
    End Sub
    Private Delegate Sub UpdateInterfaceDelegate(e As ImageCapturedEventArgs)
    Public Sub UpdateInterface(e As ImageCapturedEventArgs)
        If Me.InvokeRequired Then
            Me.Invoke(New UpdateInterfaceDelegate(AddressOf UpdateInterface), e)
        Else
            pctImage.Image = New Bitmap(e.Image)
            If e.FPS.HasValue Then
                lblFPS.Text = String.Format("{0} fps", e.FPS.Value)
            Else
                lblFPS.Text = String.Empty
            End If
        End If
    End Sub
End Class
 

Tralasciamo per un attimo la gestione dell'aggiornamento dell'interfaccia (metodo UpdateInterface) di cui ci occuperemo in seguito e che non è strettamente legato a Perceptual SDK.
La nostra interfaccia ha a disposizione un'istanza della classe ImageCamPipeline che viene creata, a cui viene agganciato il gestore di evento della ricezione dell'immagine e che viene avviata all'interno del gestore dell'evento load della form.
Il metodo LoopFrames della classe base UtilMPipeline richiama il metodo LoopFrames (http://software.intel.com/sites/landingpage/perceptual_computing/documentation/html/loopframes_utilpipeline.html) della UtilPipeline il cui scopo è quello di dare il via ad una sorta di loop. Dalla documentazione ufficiale otteniamo che lo pseudo-codice eseguito è il seguente:

if (!Init()) return false;
for (;;) {
         // some device hot-plug code omitted.
         if (!AcquireFrame(true)) break;
         if (!ReleaseFrame()) break;
 }
 Close();
 return true
 

Il metodo consta dei seguenti passi:

  1. viene inizializzata la classe e il suo "aggancio" con il device;
  2. viene eseguito un ciclo infinito all'interno del quale viene recuperato il frame dalla cam (che tipo di frame dipende da cosa è abilitato) e, di seguito, rilasciato;
  3. viene eseguita la chiusura della connessione con il device.

Si evince che il LoopFrames è bloccante rispetto al thread che lo richiama e, nella nostra interfaccia, questo comporta che la stessa non sia in grado di partire poichè il metodo LoopFrames è richiamato nel gestore dell'evento Load che si svolge nel thread principale della UI.
Per risolvere questo problema, possiamo pensare di riscrivere il metodo LoopFrames per fare in modo che lo stesso avvenga in un thread separato sbloccando il chiamante:

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
 

L'attributo di classe LoopFramesThread e' di tipo Thread mentre LoopFramesCode contiene il codice che, effettivamente esegue il LoopFrames della classe padre:

Private LoopFramesThread As Thread
     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
 

Il Try....Catch all'interno del quale viene eseguito il FramesLoop serve per evitare che una chiusura del thread con il metodo Abort() generi un'eccezione non gestita.
Gli eventi PipelineStarted, PipelineError e PipelineStopped servono per fare in modo che colui che utilizza la classe Pipeline, sia in grado di sapere se qualcosa è andato storto nell'esecuzione del LoopFrames della classe base (può accadere se, ad esempio, si vuole recuperare l'immagine di profondità - vedremo in seguito come - e non si ha attaccata la Creative Cam).

In questo modo, ridefinendo in ottica multi-thread, il FrameLoops, possiamo essere certi che l'interfaccia non si blocchi.
Di contro, però, è altamente probabile che l'evento ImageCaptured della nostra pipeline, avvenga in un thread che non è quello principale della nostra UI.
Poichè le applicazioni Windows Form sono STA (Single Thread Apartment), siamo costretti ad aggiornare la nostra interfaccia utilizzando la tecnica dell'InvokeRequired (metodo UpdateInterface).

L'esempio riportato è molto semplice ma permette di cominciare a capire il funzionamento dell'SDK Perceptual. In particolare, se ci si limita a recuperare l'immagine RGB a 24 bit, possiamo utilizzarlo anche con una normale web cam.
Utilizzare una Creative Cam permette di attivare altre tipologie di visualizzazione e, in generale, da delle performance migliori. Ad esempio la seguente figura mostra il confronto tra la web cam integrata del mio ultrabook e la Creative Cam in termini di definizione e, soprattutto, di frame rate istantaneo.

Infine, possiamo sfruttare le caratteristiche della Creative Cam per recuperare, ad esempio, l'immagine di profondità.
Per fare questo è sufficiante cambiare la modalità con cui è richiesta l'abilitazione nel costruttore della nostra Pipeline:

Public Sub New()
        MyBase.New()
        EnableImage(PXCMImage.ColorFormat.COLOR_FORMAT_DEPTH, 320, 240)
End Sub
 

La seguente figura mostra il risultato:

L'insieme dei valori ammessi per l'enumerazione PCXMImage.ColorFormat è mostrato all'indirizzo http://software.intel.com/sites/landingpage/perceptual_computing/documentation/html/index.html?pxcimagecolorformat.html.

 

 

Para obtener información más completa sobre las optimizaciones del compilador, consulte nuestro Aviso de optimización.