Analysera videor i nära realtid

Den här artikeln visar hur du använder Azure AI Vision-API:et för att utföra analyser i nära realtid på bildrutor som hämtas från en livevideoström. De grundläggande elementen i en sådan analys är:

  • Hämta bildrutor från en videokälla
  • Välja vilka ramar som ska analyseras
  • Skicka dessa ramar till API:et
  • Använda varje analysresultat som returneras från API-anropet

Dricks

Exemplen i den här artikeln är skrivna i C#. Om du vill komma åt koden går du till exempelsidan videoramanalys på GitHub.

Metoder för att köra analys i nära realtid

Du kan lösa problemet med att köra analys i nära realtid på videoströmmar med hjälp av en mängd olika metoder. Den här artikeln beskriver tre av dem, i ökande nivåer av sofistikering.

Metod 1: Utforma en oändlig loop

Den enklaste designen för analys i nära realtid är en oändlig loop. I varje iteration av den här loopen hämtar programmet en ram, analyserar den och bearbetar sedan resultatet:

while (true)
{
    Frame f = GrabFrame();
    if (ShouldAnalyze(f))
    {
        AnalysisResult r = await Analyze(f);
        ConsumeResult(r);
    }
}

Om din analys skulle bestå av en enkel algoritm på klientsidan skulle den här metoden vara lämplig. Men när analysen sker i molnet innebär den resulterande svarstiden att ett API-anrop kan ta flera sekunder. Under den här tiden tar du inte bilder och tråden gör i princip ingenting. Din maximala bildfrekvens begränsas av svarstiden för API-anropen.

Metod 2: Tillåt att API-anropen körs parallellt

Även om en enkel, enkel trådad loop passar bra för en enkel algoritm på klientsidan passar den inte bra med svarstiden för ett API-molnanrop. Lösningen på det här problemet är att tillåta att det långvariga API-anropet körs parallellt med ramgreppen. I C# kan du göra detta med hjälp av uppgiftsbaserad parallellitet. Du kan till exempel köra följande kod:

while (true)
{
    Frame f = GrabFrame();
    if (ShouldAnalyze(f))
    {
        var t = Task.Run(async () =>
        {
            AnalysisResult r = await Analyze(f);
            ConsumeResult(r);
        }
    }
}

Med den här metoden startar du varje analys i en separat uppgift. Uppgiften kan köras i bakgrunden medan du fortsätter att ta tag i nya bildrutor. Metoden undviker att blockera huvudtråden medan du väntar på att ett API-anrop ska returneras. Metoden kan dock medföra vissa nackdelar:

  • Det kostar dig några av de garantier som den enkla versionen gav. Det innebär att flera API-anrop kan ske parallellt och resultaten kan returneras i fel ordning.
  • Det kan också orsaka att flera trådar anger funktionen ConsumeResult() samtidigt, vilket kan vara farligt om funktionen inte är trådsäker.
  • Slutligen håller den här enkla koden inte reda på de uppgifter som skapas, så undantag försvinner tyst. Därför måste du lägga till en "konsumenttråd" som spårar analysuppgifterna, genererar undantag, dödar långvariga uppgifter och ser till att resultaten förbrukas i rätt ordning, en i taget.

Metod 3: Utforma ett producent-konsumentsystem

Om du vill utforma ett "producent-konsument-system" skapar du en producenttråd som liknar föregående avsnitts oändliga loop. I stället för att använda analysresultaten så snart de är tillgängliga placerar producenten bara uppgifterna i en kö för att hålla reda på dem.

// Queue that will contain the API call tasks.
var taskQueue = new BlockingCollection<Task<ResultWrapper>>();

// Producer thread.
while (true)
{
    // Grab a frame.
    Frame f = GrabFrame();

    // Decide whether to analyze the frame.
    if (ShouldAnalyze(f))
    {
        // Start a task that will run in parallel with this thread.
        var analysisTask = Task.Run(async () =>
        {
            // Put the frame, and the result/exception into a wrapper object.
            var output = new ResultWrapper(f);
            try
            {
                output.Analysis = await Analyze(f);
            }
            catch (Exception e)
            {
                output.Exception = e;
            }
            return output;
        }

        // Push the task onto the queue.
        taskQueue.Add(analysisTask);
    }
}

Du skapar också en konsumenttråd som tar bort uppgifter från kön, väntar på att de ska slutföras och visar antingen resultatet eller genererar undantaget som utlöstes. Genom att använda den här kön kan du garantera att resultatet förbrukas en i taget, i rätt ordning, utan att begränsa systemets maximala bildfrekvens.

// Consumer thread.
while (true)
{
    // Get the oldest task.
    Task<ResultWrapper> analysisTask = taskQueue.Take();
 
    // Wait until the task is completed.
    var output = await analysisTask;

    // Consume the exception or result.
    if (output.Exception != null)
    {
        throw output.Exception;
    }
    else
    {
        ConsumeResult(output.Analysis);
    }
}

Implementera lösningen

Hämta exempelkod

För att få igång appen så snabbt som möjligt har vi implementerat systemet som beskrivs i föregående avsnitt. Det är avsett att vara tillräckligt flexibelt för att hantera många scenarier, samtidigt som det är enkelt att använda. Om du vill komma åt koden går du till exempellagringsplatsen videoramanalys på GitHub.

Biblioteket innehåller FrameGrabber klassen som implementerar producent-konsumentsystemet för att bearbeta videorutor från en webbkamera. Användare kan ange den exakta formen av API-anropet och klassen använder händelser för att meddela den anropande koden när en ny ram hämtas eller när ett nytt analysresultat är tillgängligt.

Visa exempelimplementeringar

För att illustrera några av möjligheterna har vi tillhandahållit två exempelappar som använder biblioteket.

Den första exempelappen är en enkel konsolapp som hämtar ramar från standardkameran och sedan skickar dem till ansiktstjänsten för ansiktsigenkänning. En förenklad version av appen representeras i följande kod:

using System;
using System.Linq;
using Microsoft.Azure.CognitiveServices.Vision.Face;
using Microsoft.Azure.CognitiveServices.Vision.Face.Models;
using VideoFrameAnalyzer;

namespace BasicConsoleSample
{
    internal class Program
    {
        const string ApiKey = "<your API key>";
        const string Endpoint = "https://<your API region>.api.cognitive.microsoft.com";

        private static async Task Main(string[] args)
        {
            // Create grabber.
            FrameGrabber<DetectedFace[]> grabber = new FrameGrabber<DetectedFace[]>();

            // Create Face Client.
            FaceClient faceClient = new FaceClient(new ApiKeyServiceClientCredentials(ApiKey))
            {
                Endpoint = Endpoint
            };

            // Set up a listener for when we acquire a new frame.
            grabber.NewFrameProvided += (s, e) =>
            {
                Console.WriteLine($"New frame acquired at {e.Frame.Metadata.Timestamp}");
            };

            // Set up a Face API call.
            grabber.AnalysisFunction = async frame =>
            {
                Console.WriteLine($"Submitting frame acquired at {frame.Metadata.Timestamp}");
                // Encode image and submit to Face service.
                return (await faceClient.Face.DetectWithStreamAsync(frame.Image.ToMemoryStream(".jpg"))).ToArray();
            };

            // Set up a listener for when we receive a new result from an API call.
            grabber.NewResultAvailable += (s, e) =>
            {
                if (e.TimedOut)
                    Console.WriteLine("API call timed out.");
                else if (e.Exception != null)
                    Console.WriteLine("API call threw an exception.");
                else
                    Console.WriteLine($"New result received for frame acquired at {e.Frame.Metadata.Timestamp}. {e.Analysis.Length} faces detected");
            };

            // Tell grabber when to call the API.
            // See also TriggerAnalysisOnPredicate
            grabber.TriggerAnalysisOnInterval(TimeSpan.FromMilliseconds(3000));

            // Start running in the background.
            await grabber.StartProcessingCameraAsync();

            // Wait for key press to stop.
            Console.WriteLine("Press any key to stop...");
            Console.ReadKey();

            // Stop, blocking until done.
            await grabber.StopProcessingAsync();
        }
    }
}

Den andra exempelappen erbjuder fler funktioner. Det gör att du kan välja vilket API som ska anropas på videoramarna. Till vänster visar appen en förhandsgranskning av livevideon. Till höger överläggs det senaste API-resultatet i motsvarande ram.

I de flesta lägen finns det en synlig fördröjning mellan livevideon till vänster och den visualiserade analysen till höger. Den här fördröjningen är den tid det tar att göra API-anropet. Ett undantag är i EmotionsWithClientFaceDetect läget som utför ansiktsidentifiering lokalt på klientdatorn med hjälp av OpenCV innan avbildningar skickas till Azure AI-tjänster.

Med den här metoden kan du visualisera det identifierade ansiktet omedelbart. Du kan sedan uppdatera attributen senare när API-anropet har returnerats. Detta visar möjligheten till en "hybrid"-metod. En del enkel bearbetning kan alltså utföras på klienten, och sedan kan API:er för Azure AI-tjänster användas för att utöka bearbetningen med mer avancerad analys vid behov.

The LiveCameraSample app displaying an image with tags

Integrera exempel i din kodbas

Kom igång med det här exemplet genom att göra följande:

  1. Skapa ett Azure-konto. Om du redan har en kan du gå vidare till nästa steg.
  2. Skapa resurser för Azure AI Vision and Face i Azure-portalen för att hämta din nyckel och slutpunkt. Se till att välja den kostnadsfria nivån (F0) under installationen.
    • Azure AI Vision
    • Ansikte När resurserna har distribuerats väljer du Gå till resurs för att samla in din nyckel och slutpunkt för varje resurs.
  3. Klona GitHub-lagringsplatsen Cognitive-Samples-VideoFrameAnalysis.
  4. Öppna exemplet i Visual Studio 2015 eller senare och skapa och kör sedan exempelprogrammen:
    • För BasicConsoleSample hårdkodas ansiktsnyckeln direkt i BasicConsoleSample/Program.cs.
    • För Live Kamera Sample anger du nycklarna i fönstret Inställningar i appen. Nycklarna sparas mellan sessioner som användardata.

När du är redo att integrera exemplen refererar du till VideoFrameAnalyzer-biblioteket från dina egna projekt.

Funktionerna för bild-, röst-, video- och texttolkning i VideoFrameAnalyzer använder Azure AI-tjänster. Microsoft tar emot de bilder, ljud, video och andra data som du laddar upp (via den här appen) och kan använda dem i tjänstförbättringssyfte. Vi ber om din hjälp med att skydda de personer vars data din app skickar till Azure AI-tjänster.

Nästa steg

I den här artikeln har du lärt dig hur du kör analys i nära realtid på livevideoströmmar med hjälp av tjänsterna Ansiktsigenkänning och Azure AI Vision. Du har också lärt dig hur du kan använda vår exempelkod för att komma igång.

Ge gärna feedback och förslag på GitHub-lagringsplatsen. Om du vill ge bredare API-feedback går du till vår UserVoice-webbplats .