Share via


Rilevare i volti in immagini o video

Questo argomento illustra come usare FaceDetector per rilevare i visi in un'immagine. FaceTracker è ottimizzato per tracciare i volti nel tempo in una sequenza di fotogrammi video.

Per un metodo alternativo di rilevamento dei visi tramite FaceDetectionEffect, vedere Analisi della scena per l'acquisizione multimediale.

Il codice in questo articolo è stato adattato dagli esempi Basic Face Detection e Basic Face Tracking. È possibile scaricare questi esempi per visualizzare il codice usato nel contesto o per usare gli esempi come punto di partenza per la propria app.

Rilevare i volti in una singola immagine

La classe FaceDetector consente di rilevare uno o più visi in un'immagine ancora.

Questo esempio usa API dagli spazi dei nomi seguenti.

using Windows.Storage;
using Windows.Storage.Pickers;
using Windows.Storage.Streams;
using Windows.Graphics.Imaging;
using Windows.Media.FaceAnalysis;
using Windows.UI.Xaml.Media.Imaging;
using Windows.UI.Xaml.Shapes;

Dichiarare una variabile membro della classe per l'oggetto FaceDetector e per l'elenco di oggetti DetectedFace che verranno rilevati nell'immagine.

FaceDetector faceDetector;
IList<DetectedFace> detectedFaces;

Il rilevamento viso opera su un oggetto SoftwareBitmap che può essere creato in diversi modi. In questo esempio viene usato un oggetto FileOpenPicker per consentire all'utente di selezionare un file immagine in cui verranno rilevati i visi. Per altre informazioni sull'uso delle bitmap software, vedere Imaging.

FileOpenPicker photoPicker = new FileOpenPicker();
photoPicker.ViewMode = PickerViewMode.Thumbnail;
photoPicker.SuggestedStartLocation = PickerLocationId.PicturesLibrary;
photoPicker.FileTypeFilter.Add(".jpg");
photoPicker.FileTypeFilter.Add(".jpeg");
photoPicker.FileTypeFilter.Add(".png");
photoPicker.FileTypeFilter.Add(".bmp");

StorageFile photoFile = await photoPicker.PickSingleFileAsync();
if (photoFile == null)
{
    return;
}

Usare la classe BitmapDecoder per decodificare il file di immagine in un SoftwareBitmap. Il processo di rilevamento dei volti è più rapido con un'immagine più piccola e quindi è consigliabile ridimensionare l'immagine di origine a dimensioni inferiori. Questa operazione può essere eseguita durante la decodifica creando un oggetto BitmapTransform, impostando le proprietà ScaledWidth e ScaledHeightin e passandolo GetSoftwareBitmapAsync, che restituisce SoftwareBitmap codificato e in scala.

IRandomAccessStream fileStream = await photoFile.OpenAsync(FileAccessMode.Read);
BitmapDecoder decoder = await BitmapDecoder.CreateAsync(fileStream);

BitmapTransform transform = new BitmapTransform();
const float sourceImageHeightLimit = 1280;

if (decoder.PixelHeight > sourceImageHeightLimit)
{
    float scalingFactor = (float)sourceImageHeightLimit / (float)decoder.PixelHeight;
    transform.ScaledWidth = (uint)Math.Floor(decoder.PixelWidth * scalingFactor);
    transform.ScaledHeight = (uint)Math.Floor(decoder.PixelHeight * scalingFactor);
}

SoftwareBitmap sourceBitmap = await decoder.GetSoftwareBitmapAsync(decoder.BitmapPixelFormat, BitmapAlphaMode.Premultiplied, transform, ExifOrientationMode.IgnoreExifOrientation, ColorManagementMode.DoNotColorManage);

Nella versione corrente la classe FaceDetector supporta solo le immagini in Gray8 o Nv12. La classe SoftwareBitmap fornisce il metodo Convert, che converte una bitmap da un formato a un altro. Questo esempio converte l'immagine di origine nel formato pixel Gray8 se non è già in tale formato. Se lo si desidera, si possono usare i metodi GetSupportedBitmapPixelFormats e IsBitmapPixelFormatSupported per determinare in fase di esecuzione se è supportato un formato pixel, nel caso in cui il set di formati supportati venga espanso nelle versioni future.

// Use FaceDetector.GetSupportedBitmapPixelFormats and IsBitmapPixelFormatSupported to dynamically
// determine supported formats
const BitmapPixelFormat faceDetectionPixelFormat = BitmapPixelFormat.Gray8;

SoftwareBitmap convertedBitmap;

if (sourceBitmap.BitmapPixelFormat != faceDetectionPixelFormat)
{
    convertedBitmap = SoftwareBitmap.Convert(sourceBitmap, faceDetectionPixelFormat);
}
else
{
    convertedBitmap = sourceBitmap;
}

Creare un'istanza dell'oggetto FaceDetector chiamando CreateAsync e quindi chiamando DetectFacesAsync, passando la bitmap ridimensionata a una dimensione ragionevole e convertita in un formato pixel supportato. Questo metodo restituisce un elenco di oggetti DetectedFace. ShowDetectedFaces è un metodo helper, illustrato di seguito, che disegna quadrati intorno ai visi nell'immagine.

if (faceDetector == null)
{
    faceDetector = await FaceDetector.CreateAsync();
}

detectedFaces = await faceDetector.DetectFacesAsync(convertedBitmap);
ShowDetectedFaces(sourceBitmap, detectedFaces);

Assicurarsi di eliminare gli oggetti creati durante il processo di rilevamento dei volti.

sourceBitmap.Dispose();
fileStream.Dispose();
convertedBitmap.Dispose();

Per visualizzare l'immagine e disegnare caselle intorno ai visi rilevati, aggiungere un elemento Canvas alla pagina XAML.

<Canvas x:Name="VisualizationCanvas" Visibility="Visible" Grid.Row="0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>

Definire alcune variabili membro per applicare uno stile ai quadrati che verranno disegnati.

private readonly SolidColorBrush lineBrush = new SolidColorBrush(Windows.UI.Colors.Yellow);
private readonly double lineThickness = 2.0;
private readonly SolidColorBrush fillBrush = new SolidColorBrush(Windows.UI.Colors.Transparent);

Nel metodo helper ShowDetectedFaces, viene creato un nuovo ImageBrush e la sorgente è impostata a una SoftwareBitmapSource creata da SoftwareBitmap che rappresenta l'immagine di origine. Lo sfondo del controllo Canvas XAML è impostato sul pennello immagine.

Se l'elenco dei visi passati al metodo helper non è vuoto, scorrere ogni viso nell'elenco e usare la proprietà FaceBox di DetectedFace per determinare la posizione e le dimensioni del rettangolo all'interno dell'immagine che contiene il viso. Poiché è molto probabile che il controllo Canvas sia in una dimensione diversa rispetto all'immagine di origine, è necessario moltiplicare sia le coordinate X che Y e la larghezza e l'altezza di FaceBox ridimensionamento che corrisponde al rapporto tra le dimensioni dell'immagine di origine e le dimensioni effettive del controllo Canvas.

private async void ShowDetectedFaces(SoftwareBitmap sourceBitmap, IList<DetectedFace> faces)
{
    ImageBrush brush = new ImageBrush();
    SoftwareBitmapSource bitmapSource = new SoftwareBitmapSource();
    await bitmapSource.SetBitmapAsync(sourceBitmap);
    brush.ImageSource = bitmapSource;
    brush.Stretch = Stretch.Fill;
    this.VisualizationCanvas.Background = brush;

    if (detectedFaces != null)
    {
        double widthScale = sourceBitmap.PixelWidth / this.VisualizationCanvas.ActualWidth;
        double heightScale = sourceBitmap.PixelHeight / this.VisualizationCanvas.ActualHeight;

        foreach (DetectedFace face in detectedFaces)
        {
            // Create a rectangle element for displaying the face box but since we're using a Canvas
            // we must scale the rectangles according to the image’s actual size.
            // The original FaceBox values are saved in the Rectangle's Tag field so we can update the
            // boxes when the Canvas is resized.
            Rectangle box = new Rectangle();
            box.Tag = face.FaceBox;
            box.Width = (uint)(face.FaceBox.Width / widthScale);
            box.Height = (uint)(face.FaceBox.Height / heightScale);
            box.Fill = this.fillBrush;
            box.Stroke = this.lineBrush;
            box.StrokeThickness = this.lineThickness;
            box.Margin = new Thickness((uint)(face.FaceBox.X / widthScale), (uint)(face.FaceBox.Y / heightScale), 0, 0);

            this.VisualizationCanvas.Children.Add(box);
        }
    }
}

Tenere traccia dei visi in una sequenza di fotogrammi

Se si desidera rilevare i visi nel video, è più efficiente usare la classe FaceTracker anziché la classe FaceDetector, anche se i passaggi di implementazione sono molto simili. FaceTracker usa informazioni sui fotogrammi elaborati in precedenza per ottimizzare il processo di rilevamento.

using Windows.Media;
using System.Threading;
using Windows.System.Threading;

Dichiarare una variabile classe per l'oggetto FaceTracker. In questo esempio viene usato un ThreadPoolTimer per avviare il rilevamento dei volti in un intervallo definito. Viene usato un SemaphoreSlim per assicurarsi che sia in esecuzione una sola operazione di tracciamento del viso alla volta.

private FaceTracker faceTracker;
private ThreadPoolTimer frameProcessingTimer;
private SemaphoreSlim frameProcessingSemaphore = new SemaphoreSlim(1);

Per inizializzare l'operazione di rilevamento volti, creare un nuovo oggetto FaceTracker chiamando CreateAsync. Inizializzare l'intervallo timer desiderato e quindi creare il timer. Il metodo helper ProcessCurrentVideoFrame verrà chiamato ogni volta che è trascorso l'intervallo specificato.

this.faceTracker = await FaceTracker.CreateAsync();
TimeSpan timerInterval = TimeSpan.FromMilliseconds(66); // 15 fps
this.frameProcessingTimer = Windows.System.Threading.ThreadPoolTimer.CreatePeriodicTimer(new Windows.System.Threading.TimerElapsedHandler(ProcessCurrentVideoFrame), timerInterval);

L'helper ProcessCurrentVideoFrame viene chiamato in modo asincrono dal timer, quindi il metodo chiama prima il metodo Wait del semaforo per verificare se un'operazione di rilevamento è in corso e se il metodo restituisce senza tentare di rilevare i visi. Alla fine di questo metodo viene chiamato il metodo Release del semaforo, che consente la successiva chiamata a ProcessCurrentVideoFrame.

La classe FaceTracker opera sugli oggetti VideoFrame. Esistono diversi modi per ottenere un VideoFrame tra cui l'acquisizione di un fotogramma di anteprima da un oggetto MediaCapture in esecuzione o implementando il metodo ProcessFrame di IBasicVideoEffect. In questo esempio viene utilizzato un metodo helper non definito che restituisce un fotogramma video, GetLatestFrame, come segnaposto per questa operazione. Per informazioni sul recupero di fotogrammi video dal flusso di anteprima di un dispositivo di acquisizione multimediale in esecuzione, vedere Ottenere un frame di anteprima.

Come per FaceDetector, FaceTracker supporta un set limitato di formati di pixel. In questo esempio il rilevamento dei volti viene abbandonato se il frame fornito non è nel formato Nv12.

Chiamare ProcessNextFrameAsync per recuperare un elenco di oggetti DetectedFace che rappresentano i visi nel frame. Dopo aver ottenuto l'elenco dei visi, è possibile visualizzarli nello stesso modo descritto in precedenza per il rilevamento dei volti. Si noti che, poiché il metodo helper di rilevamento dei visi non viene chiamato nel thread dell'interfaccia utente, è necessario eseguire qualsiasi aggiornamento dell'interfaccia utente in una chiamata a CoreDispatcher.RunAsync.

public async void ProcessCurrentVideoFrame(ThreadPoolTimer timer)
{
    if (!frameProcessingSemaphore.Wait(0))
    {
        return;
    }

    VideoFrame currentFrame = await GetLatestFrame();

    // Use FaceDetector.GetSupportedBitmapPixelFormats and IsBitmapPixelFormatSupported to dynamically
    // determine supported formats
    const BitmapPixelFormat faceDetectionPixelFormat = BitmapPixelFormat.Nv12;

    if (currentFrame.SoftwareBitmap.BitmapPixelFormat != faceDetectionPixelFormat)
    {
        return;
    }

    try
    {
        IList<DetectedFace> detectedFaces = await faceTracker.ProcessNextFrameAsync(currentFrame);

        var previewFrameSize = new Windows.Foundation.Size(currentFrame.SoftwareBitmap.PixelWidth, currentFrame.SoftwareBitmap.PixelHeight);
        var ignored = this.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
        {
            this.SetupVisualization(previewFrameSize, detectedFaces);
        });
    }
    catch (Exception e)
    {
        // Face tracking failed
    }
    finally
    {
        frameProcessingSemaphore.Release();
    }

    currentFrame.Dispose();
}