Analizuj wideo niemal w czasie rzeczywistymAnalyze videos in near real time

W tym artykule pokazano, jak przeprowadzić analizę niemal w czasie rzeczywistym na klatkach, które są pobierane z strumienia wideo na żywo przy użyciu interfejs API przetwarzania obrazów.This article demonstrates how to perform near real-time analysis on frames that are taken from a live video stream by using the Computer Vision API. Podstawowe elementy takiej analizy są następujące:The basic elements of such an analysis are:

  • Pobieranie ramek ze źródła wideo.Acquiring frames from a video source.
  • Wybieranie ramek do przeanalizowania.Selecting which frames to analyze.
  • Przesyłanie tych ramek do interfejsu API.Submitting these frames to the API.
  • Zużywanie każdego wyniku analizy zwracanego z wywołania interfejsu API.Consuming each analysis result that's returned from the API call.

Przykłady w tym artykule są zapisywane w C#temacie.The samples in this article are written in C#. Aby uzyskać dostęp do kodu, przejdź do strony Przykładowa analiza klatek wideo w witrynie GitHub.To access the code, go to the Video frame analysis sample page on GitHub.

Podejścia do uruchamiania analizy niemal w czasie rzeczywistymApproaches to running near real-time analysis

Można rozwiązać problem związany z analizą w czasie rzeczywistym strumieni wideo przy użyciu różnych metod.You can solve the problem of running near real-time analysis on video streams by using a variety of approaches. W tym artykule opisano trzy z nich, zwiększając poziomy złożoności.This article outlines three of them, in increasing levels of sophistication.

Zaprojektuj nieskończoną pętlęDesign an infinite loop

Najprostszym projektem dla analizy niemal w czasie rzeczywistym jest nieskończona pętla.The simplest design for near real-time analysis is an infinite loop. W każdej iteracji tej pętli można pobrać ramkę, przeanalizować ją, a następnie użyć wyniku:In each iteration of this loop, you grab a frame, analyze it, and then consume the result:

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

Jeśli analiza była taka sama, jak w przypadku lekkich algorytmów po stronie klienta, to podejście byłoby odpowiednie.If your analysis were to consist of a lightweight, client-side algorithm, this approach would be suitable. Jeśli jednak Analiza odbywa się w chmurze, wynikłe opóźnienie oznacza, że wywołanie interfejsu API może trwać kilka sekund.However, when the analysis occurs in the cloud, the resulting latency means that an API call might take several seconds. W tym czasie nie są przechwytywane obrazy, a wątek nie wykonuje żadnych operacji.During this time, you're not capturing images, and your thread is essentially doing nothing. Maksymalna szybkość klatek jest ograniczona przez opóźnienie wywołań interfejsu API.Your maximum frame rate is limited by the latency of the API calls.

Zezwalaj na równoległe uruchamianie wywołań interfejsu APIAllow the API calls to run in parallel

Chociaż prosta pętla jednowątkowa ma sens dla lekkiego algorytmu po stronie klienta, nie pasuje do opóźnienia wywołania interfejsu API chmury.Although a simple, single-threaded loop makes sense for a lightweight, client-side algorithm, it doesn't fit well with the latency of a cloud API call. Rozwiązaniem tego problemu jest umożliwienie wykonywania długotrwałego wywołania interfejsu API równolegle z obsługą ramek.The solution to this problem is to allow the long-running API call to run in parallel with the frame-grabbing. W C#programie można to zrobić za pomocą równoległości opartej na zadaniach.In C#, you could do this by using task-based parallelism. Na przykład można uruchomić następujący kod:For example, you can run the following code:

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

W tym podejściu każda analiza zostanie uruchomiona w osobnym zadaniu.With this approach, you launch each analysis in a separate task. Zadanie można uruchomić w tle, podczas gdy będziesz kontynuować przeprowadzanie nowej ramki.The task can run in the background while you continue grabbing new frames. Podejście to pozwala uniknąć blokowania wątku głównego podczas oczekiwania na zwrócenie wywołania interfejsu API.The approach avoids blocking the main thread as you wait for an API call to return. Jednak takie podejście może zaprezentować pewne wady:However, the approach can present certain disadvantages:

  • Koszt ten gwarantuje pewne gwarancje, które zapewnia prosta wersja.It costs you some of the guarantees that the simple version provided. Oznacza to, że wiele wywołań interfejsu API może być wykonywanych równolegle, a wyniki mogą zostać zwrócone w niewłaściwej kolejności.That is, multiple API calls might occur in parallel, and the results might get returned in the wrong order.
  • Może to również spowodować, że wiele wątków wprowadzi jednocześnie funkcję ConsumeResult (), która może być niebezpieczna, jeśli funkcja nie jest bezpieczna wątkowo.It could also cause multiple threads to enter the ConsumeResult() function simultaneously, which might be dangerous if the function isn't thread-safe.
  • Na koniec ten prosty kod nie śledzi zadań, które zostały utworzone, dlatego wyjątki dyskretnie znikają.Finally, this simple code doesn't keep track of the tasks that get created, so exceptions silently disappear. W tym celu należy dodać wątek "konsument", który śledzi zadania analizy, podnosi wyjątki, kasuje zadania długotrwałe i gwarantuje, że wyniki są używane w odpowiedniej kolejności, po jednym naraz.Thus, you need to add a "consumer" thread that tracks the analysis tasks, raises exceptions, kills long-running tasks, and ensures that the results get consumed in the correct order, one at a time.

Projektuj system dla konsumentówDesign a producer-consumer system

W przypadku ostatecznego podejścia Projektowanie systemu "producent-odbiorca" tworzy wątek producenta, który wygląda podobnie do wcześniej wymienionej pętli nieskończonej.For your final approach, designing a "producer-consumer" system, you build a producer thread that looks similar to your previously mentioned infinite loop. Jednak zamiast zużywać wyniki analizy natychmiast po ich udostępnieniu, producent po prostu umieszcza zadania w kolejce, aby śledzić je.However, instead of consuming the analysis results as soon as they're available, the producer simply places the tasks in a queue to keep track of them.

// 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);
    }
}

Tworzony jest również wątek odbiorcy, który przyjmuje zadania z kolejki, czeka na zakończenie, a następnie wyświetla wynik lub zgłasza wyjątek, który został zgłoszony.You also create a consumer thread, which takes tasks off the queue, waits for them to finish, and either displays the result or raises the exception that was thrown. Korzystając z kolejki, można zagwarantować, że wyniki są używane pojedynczo w prawidłowej kolejności, bez ograniczania maksymalnej liczby klatek w systemie.By using the queue, you can guarantee that the results get consumed one at a time, in the correct order, without limiting the maximum frame rate of the system.

// 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);
    }
}

Implementowanie rozwiązaniaImplement the solution

Szybkie rozpoczęcie efektywnej pracyGet started quickly

Aby pomóc w rozpoczęciu i uruchomieniu aplikacji tak szybko, jak to możliwe, zaimplementowano system opisany w poprzedniej sekcji.To help get your app up and running as quickly as possible, we've implemented the system that's described in the preceding section. Jest on wystarczająco elastyczny, aby pomieścić wiele scenariuszy, podczas gdy jest to łatwe w użyciu.It's intended to be flexible enough to accommodate many scenarios, while being easy to use. Aby uzyskać dostęp do kodu, przejdź do strony Przykładowa analiza klatek wideo w witrynie GitHub.To access the code, go to the Video frame analysis sample page on GitHub.

Biblioteka zawiera FrameGrabber klasę, która implementuje wcześniej omawiany system konsumentów, aby przetwarzać ramki wideo z kamery internetowej.The library contains the FrameGrabber class, which implements the previously discussed producer-consumer system to process video frames from a webcam. Użytkownicy mogą określić dokładną postać wywołania interfejsu API, a Klasa używa zdarzeń, aby pozwolić kod wywołujący, gdy nowa ramka zostanie pobrana lub gdy jest dostępny nowy wynik analizy.Users can specify the exact form of the API call, and the class uses events to let the calling code know when a new frame is acquired, or when a new analysis result is available.

Aby zilustrować niektóre z tych możliwości, oferujemy dwie przykładowe aplikacje, które korzystają z biblioteki.To illustrate some of the possibilities, we've provided two sample apps that use the library.

Pierwsza Przykładowa aplikacja to prosta aplikacja konsolowa, która pobiera ramki z domyślnej kamery internetowej, a następnie przesyła je do interfejs API rozpoznawania twarzy na potrzeby wykrywania kroju.The first sample app is a simple console app that grabs frames from the default webcam and then submits them to the Face API for face detection. Uproszczona wersja aplikacji jest odtwarzana w następującym kodzie:A simplified version of the app is reproduced in the following code:

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 void Main(string[] args)
        {
            // Create grabber.
            FrameGrabber<DetectedFace[]> grabber = new FrameGrabber<DetectedFace[]>();

            // Create Face API 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 API.
                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.
            grabber.StartProcessingCameraAsync().Wait();

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

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

Druga Przykładowa aplikacja jest nieco bardziej interesująca.The second sample app is a bit more interesting. Umożliwia wybranie interfejsu API, który ma być wywoływany w klatkach wideo.It allows you to choose which API to call on the video frames. Po lewej stronie Aplikacja pokazuje podgląd wideo na żywo.On the left side, the app shows a preview of the live video. Po prawej stronie jest nakładany wynik ostatniego interfejsu API w odpowiedniej ramce.On the right, it overlays the most recent API result on the corresponding frame.

W większości trybów istnieje widoczne opóźnienie między wideo na żywo po lewej stronie i analizą wizualizacji po prawej stronie.In most modes, there's a visible delay between the live video on the left and the visualized analysis on the right. To opóźnienie to czas potrzebny do wywołania interfejsu API.This delay is the time that it takes to make the API call. Wyjątek znajduje się w trybie "EmotionsWithClientFaceDetect", który służy do automatycznego wykrywania czołowego na komputerze klienckim przy użyciu OpenCV przed przesłaniem jakichkolwiek obrazów do usługi Azure Cognitive Services.An exception is in the "EmotionsWithClientFaceDetect" mode, which performs face detection locally on the client computer by using OpenCV before it submits any images to Azure Cognitive Services.

Korzystając z tej metody, można natychmiast wizualizować wykryte działania.By using this approach, you can visualize the detected face immediately. Po powrocie wywołania interfejsu API można później zaktualizować emocji.You can then update the emotions later, after the API call returns. Pokazuje to, że podejście "hybrydowe".This demonstrates the possibility of a "hybrid" approach. Oznacza to, że niektóre proste przetwarzanie można wykonać na kliencie, a następnie interfejsy API usług Cognitive Services może służyć do rozszerzania tego przetwarzania o bardziej zaawansowaną analizę, gdy jest to konieczne.That is, some simple processing can be performed on the client, and then Cognitive Services APIs can be used to augment this processing with more advanced analysis when necessary.

Aplikacja LiveCameraSample wyświetlająca obraz ze znacznikami

Integrowanie przykładów z bazą koduIntegrate the samples into your codebase

Aby rozpocząć pracę z tym przykładem, wykonaj następujące czynności:To get started with this sample, do the following:

  1. Pobierz z Subskrypcji klucze interfejsu API dla interfejsów API wizualizacji.Get API keys for the Vision APIs from Subscriptions. Odpowiednimi interfejsami API do analizy ramek wideo są:For video frame analysis, the applicable APIs are:

  2. Klonuj repozytorium poznawcze-przykłady-VideoFrameAnalysis GitHub.Clone the Cognitive-Samples-VideoFrameAnalysis GitHub repo.

  3. Otwórz przykład w programie Visual Studio 2015 lub nowszym, a następnie Skompiluj i uruchom przykładowe aplikacje:Open the sample in Visual Studio 2015 or later, and then build and run the sample applications:

    • W przypadku aplikacji BasicConsoleSample klucz interfejsu API rozpoznawania twarzy jest zapisany bezpośrednio w kodzie w pliku BasicConsoleSample/Program.cs.For BasicConsoleSample, the Face API key is hard-coded directly in BasicConsoleSample/Program.cs.
    • W polu LiveCameraSample wprowadź klucze w okienku Ustawienia aplikacji.For LiveCameraSample, enter the keys in the Settings pane of the app. Klucze są utrwalane w wielu sesjach jako dane użytkownika.The keys are persisted across sessions as user data.

Gdy wszystko jest gotowe do integracji przykładów, odwołuje się do biblioteki VideoFrameAnalyzer z własnych projektów.When you're ready to integrate the samples, reference the VideoFrameAnalyzer library from your own projects.

Funkcje obrazu, głosu, wideo i tekstu — VideoFrameAnalyzer używają platformy Cognitive Services Azure.The image-, voice-, video-, and text-understanding capabilities of VideoFrameAnalyzer use Azure Cognitive Services. Firma Microsoft odbiera obrazy, dźwięk, wideo i inne przekazywane dane (za pośrednictwem tej aplikacji) i mogą ich używać do celów poprawy jakości usług.Microsoft receives the images, audio, video, and other data that you upload (via this app) and might use them for service-improvement purposes. Prosimy o pomoc w ochronie prywatności osób, których dane są wysyłane do usług Azure Cognitive Services przy użyciu aplikacji.We ask for your help in protecting the people whose data your app sends to Azure Cognitive Services.

PodsumowanieSummary

W tym artykule przedstawiono sposób uruchamiania analizy niemal w czasie rzeczywistym na żywo strumieni wideo przy użyciu interfejs API rozpoznawania twarzy i interfejs API przetwarzania obrazów.In this article, you learned how to run near real-time analysis on live video streams by using the Face API and the Computer Vision API. Wiesz również, jak można użyć naszego przykładowego kodu, aby rozpocząć pracę.You also learned how you can use our sample code to get started. Aby rozpocząć tworzenie aplikacji przy użyciu bezpłatnych kluczy interfejsu API, przejdź do strony rejestracji w usłudze Azure Cognitive Services.To get started building your app by using free API keys, go to the Azure Cognitive Services sign-up page.

Możesz bezpłatnie przekazać Opinie i sugestie w repozytorium GitHub.Feel free to provide feedback and suggestions in the GitHub repository. Aby zapewnić szersze informacje zwrotne interfejsu API, przejdź do naszej witryny UserVoice.To provide broader API feedback, go to our UserVoice site.