Пример: Анализ видео в реальном времениExample: How to Analyze Videos in Real-time

В этом руководстве показано, как выполнить анализ практически в реальном времени по кадрам, взятым из видеотрансляции.This guide will demonstrate how to perform near-real-time analysis on frames taken from a live video stream. Базовые операции этой системы:The basic components in such a system are:

  • получение кадров из источника видео;Acquire frames from a video source
  • выбор кадров для анализа;Select which frames to analyze
  • отправка этих кадров в API;Submit these frames to the API
  • обработка каждого результата анализа, возвращенного в результате вызова API.Consume each analysis result that is returned from the API call

Эти примеры написаны на C#. Код можно найти на GitHub: https://github.com/Microsoft/Cognitive-Samples-VideoFrameAnalysis.These samples are written in C# and the code can be found on GitHub here: https://github.com/Microsoft/Cognitive-Samples-VideoFrameAnalysis.

ПодходThe Approach

Есть несколько способов выполнить анализ видеопотоков практически в реальном времени.There are multiple ways to solve the problem of running near-real-time analysis on video streams. Начнем с описания трех подходов (по уровню сложности).We will start by outlining three approaches in increasing levels of sophistication.

Простой подходA Simple Approach

Простейший конструктор для системы анализа практически в реальном времени представляет собой бесконечный цикл, где в каждой итерации мы захватываем кадр, анализируем его и затем используем результат.The simplest design for a near-real-time analysis system is an infinite loop, where each iteration grabs a frame, analyzes it, and then consumes the result:

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

Если при анализе применялся упрощенный алгоритм на стороне клиента, такого подхода будет достаточно.If our analysis consisted of a lightweight client-side algorithm, this approach would be suitable. Тем не менее если анализ происходит в облаке, возникновение задержки означает, что вызов API может занять несколько секунд.However, when analysis happens in the cloud, the latency involved means that an API call might take several seconds. В это время мы не записываем образы, и наш поток является, по сути, пассивным.During this time, we are not capturing images, and our thread is essentially doing nothing. Максимальная частота кадров ограничивается задержкой вызовов API.Our maximum frame-rate is limited by the latency of the API calls.

Организация параллельных вызовов APIParallelizing API Calls

Хотя простой однопоточный цикл имеет смысл использовать с упрощенным алгоритмом на стороне клиента, он плохо сочетается с задержкой при вызовах API в облаке.While a simple single-threaded loop makes sense for a lightweight client-side algorithm, it doesn't fit well with the latency involved in cloud API calls. Решение этой проблемы состоит в том, чтобы разрешить выполнять длительные вызовы API одновременно с перехватом кадров.The solution to this problem is to allow the long-running API calls to execute in parallel with the frame-grabbing. В C# это достигается с помощью параллелизма на основе задач, например:In C#, we could achieve this using Task-based parallelism, for example:

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

С помощью этого кода каждый анализ запускается в рамках отдельной задачи, которая может выполняться в фоновом режиме, пока продолжается захват новых кадров.This code launches each analysis in a separate Task, which can run in the background while we continue grabbing new frames. С помощью этого метода мы избегаем блокирования основного потока при ожидании возврата вызова API, но теряем некоторые гарантии, предоставляемые простой версией.With this method we avoid blocking the main thread while waiting for an API call to return, but we have lost some of the guarantees that the simple version provided. Несколько вызовов API могут выполняться параллельно, а полученные результаты могут быть в неверном порядке.Multiple API calls might occur in parallel, and the results might get returned in the wrong order. Это также может привести к тому, что несколько потоков одновременно введут функцию ConsumeResult(), что может представлять опасность, если функция не является потокобезопасной.This could also cause multiple threads to enter the ConsumeResult() function simultaneously, which could be dangerous, if the function is not thread-safe. Наконец, этот простой код не позволяет отслеживать создаваемые задачи, поэтому исключения будут исчезать без уведомления.Finally, this simple code does not keep track of the Tasks that get created, so exceptions will silently disappear. Таким образом, последний шаг — добавить "поток-получатель", который будет отслеживать задачи анализа, вызывать исключения, завершать долго выполняющиеся задачи и обеспечивать использование результатов в правильном порядке.Therefore, the final step is to add a "consumer" thread that will track the analysis tasks, raise exceptions, kill long-running tasks, and ensure that the results get consumed in the correct order.

Конструкция "производитель-получатель"A Producer-Consumer Design

В нашей итоговой системе "производитель-получатель" есть поток-производитель, который похож на описанный ранее бесконечный цикл.In our final "producer-consumer" system, we have a producer thread that looks similar to our previous infinite loop. Но вместо того, чтобы использовать результаты анализа сразу же по мере их доступности, производитель просто помещает их в очередь для отслеживания.However, instead of consuming analysis results as soon as they are available, the producer simply puts the tasks into 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);
    }
}

У нас также есть поток-получатель, который принимает задачи из очереди, дожидается их завершения и либо отображает результат, либо вызывает созданное исключение.We also have a consumer thread that takes tasks off the queue, waits for them to finish, and either displays the result or raises the exception that was thrown. С помощью очереди мы можем гарантировать, что результаты будут использоваться последовательно в правильном порядке и без ограничения максимальной частоты кадров системы.By using the queue, we can guarantee that 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();
 
    // Await 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);
    }
}

Реализация решенияImplementing the Solution

Начало работыGetting Started

Чтобы запустить приложение, работающее как можно быстрее, используйте гибкую реализацию описанной выше системы.To get your app up and running as quickly as possible, you will use a flexible implementation of the system described above. Получить код можно отсюда: https://github.com/Microsoft/Cognitive-Samples-VideoFrameAnalysis.To access the code, go to https://github.com/Microsoft/Cognitive-Samples-VideoFrameAnalysis.

Библиотека содержит класс FrameGrabber, который реализует описанную выше систему "производитель-получатель" для обработки видеокадров с веб-камеры.The library contains the class FrameGrabber, which implements the producer-consumer system discussed above to process video frames from a webcam. Пользователь может указать точную форму вызова API, а класс с помощью событий позволяет вызывающему коду определять, когда поступает новый кадр или становится доступным новый результат анализа.The user 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 a new analysis result is available.

Для иллюстрации некоторых возможностей есть два примера приложений, использующих библиотеку.To illustrate some of the possibilities, there are two sample apps that use the library. Первый — это простое консольное приложение, упрощенная версия которого воспроизведена ниже.The first is a simple console app, and a simplified version of it is reproduced below. Приложение захватывает кадры с установленной по умолчанию веб-камеры и отправляет их в API распознавания лиц для обработки.It grabs frames from the default webcam, and submits them to the Face API for face detection.

using System;
using VideoFrameAnalyzer;
using Microsoft.ProjectOxford.Face;
using Microsoft.ProjectOxford.Face.Contract;
     
namespace VideoFrameConsoleApplication
{
    class Program
    {
        static void Main(string[] args)
        {
            // Create grabber, with analysis type Face[]. 
            FrameGrabber<Face[]> grabber = new FrameGrabber<Face[]>();
            
            // Create Face API Client. Insert your Face API key here.
            private readonly IFaceClient faceClient = new FaceClient(
            new ApiKeyServiceClientCredentials("<subscription key>"),
            new System.Net.Http.DelegatingHandler[] { });

            // Set up our Face API call.
            grabber.AnalysisFunction = async frame => return await faceClient.DetectAsync(frame.Image.ToMemoryStream(".jpg"));

            // Set up a listener for when we receive a new result from an API call. 
            grabber.NewResultAvailable += (s, e) =>
            {
                if (e.Analysis != null)
                    Console.WriteLine("New result received for frame acquired at {0}. {1} faces detected", e.Frame.Metadata.Timestamp, e.Analysis.Length);
            };
            
            // Tell grabber to call the Face API every 3 seconds.
            grabber.TriggerAnalysisOnInterval(TimeSpan.FromMilliseconds(3000));

            // Start running.
            grabber.StartProcessingCameraAsync().Wait();

            // Wait for keypress to stop
            Console.WriteLine("Press any key to stop...");
            Console.ReadKey();
            
            // Stop, blocking until done.
            grabber.StopProcessingAsync().Wait();
        }
    }
}

Второй пример приложения более интересный. Он позволяет выбрать, какой API вызывать для видеокадров.The second sample app is a bit more interesting, and allows you to choose which API to call on the video frames. Слева в приложении отображается видео в реальном времени для предварительного просмотра, справа — последний результат API, наложенный на соответствующий кадр.On the left-hand side, the app shows a preview of the live video, on the right-hand side it shows the most recent API result overlaid on the corresponding frame.

В большинстве режимов будет наблюдаться видимая задержка между текущим видео с левой стороны и визуализированным анализом с правой.In most modes, there will be a visible delay between the live video on the left, and the visualized analysis on the right. Эта задержка равна времени, которое занимает выполнение вызова API.This delay is the time taken to make the API call. Исключением является режим "EmotionsWithClientFaceDetect", который позволяет выполнять обнаружение лиц локально на клиентском компьютере с помощью OpenCV перед отправкой изображений в Cognitive Services.One exception is the "EmotionsWithClientFaceDetect" mode, which performs face detection locally on the client computer using OpenCV, before submitting any images to Cognitive Services. Таким образом, мы можем сразу же визуализировать обнаруженное лицо, а затем, после возвращения вызова API, обновить эмоции.This way, we can visualize the detected face immediately and then update the emotions once the API call returns. Этот пример демонстрирует возможность гибридного подхода, когда клиент может выполнять простую обработку, а API-интерфейсы Cognitive Services могут при необходимости дополнять ее более сложным анализом.This is an example of a "hybrid" approach, where the client can perform some simple processing, and Cognitive Services APIs can augment this with more advanced analysis when necessary.

HowToAnalyzeVideo

Интеграция в базу кодаIntegrating into your codebase

Чтобы приступить к работе с этим примером, сделайте следующее:To get started with this sample, follow these steps:

  1. Получите ключи API-интерфейсов зрения в разделе Подписки.Get API keys for the Vision APIs from Subscriptions. Для анализа кадров видео можно использовать следующие API-интерфейсы:For video frame analysis, the applicable APIs are:

  2. Клонируйте репозиторий GitHub Cognitive-Samples-VideoFrameAnalysis.Clone the Cognitive-Samples-VideoFrameAnalysis GitHub repo

  3. Откройте пример в Visual Studio 2015, создайте и запустите примеры приложений.Open the sample in Visual Studio 2015, and build and run the sample applications:

    • Для примера BasicConsoleSample ключ API распознавания лиц содержится непосредственно в коде BasicConsoleSample/Program.cs.For BasicConsoleSample, the Face API key is hard-coded directly in BasicConsoleSample/Program.cs.
    • Для LiveCameraSample ключи следует ввести в области "Параметры" приложения.For LiveCameraSample, the keys should be entered into the Settings pane of the app. Они будут сохраняться во всех сеансах как пользовательские данные.They will be persisted across sessions as user data.

Когда все будет готово к интеграции, просто создайте ссылку на библиотеку VideoFrameAnalyzer из собственных проектов.When you're ready to integrate, reference the VideoFrameAnalyzer library from your own projects.

СводкаSummary

С помощью этого руководства вы научились запускать анализ потоков видео в реальном времени с использованием API распознавания лиц, API компьютерного зрения и API распознавания эмоций, а также научились приступить к работе с использованием наших примеров кода.In this guide, you learned how to run near-real-time analysis on live video streams using the Face, Computer Vision, and Emotion APIs, and how to use our sample code to get started. Вы можете создать свое приложения, используя бесплатные ключи API на странице регистрации Azure Cognitive Services здесь.You can start building your app with free API keys at the Azure Cognitive Services sign-up page.

Отправляйте отзывы и предложения в репозиторий GitHub или более подробные отзывы об API — на наш сайт UserVoice.Feel free to provide feedback and suggestions in the GitHub repository or, for broader API feedback, on our UserVoice site.