Анализ видео в режиме почти реального времени

В этой статье показано, как использовать API визуального распознавания Azure для выполнения практически в реальном времени анализа кадров, взятых из потоков видеотрансляции. Основные элементы такого анализа:

  • Получение кадров из источника видео
  • Выбор кадров для анализа
  • Отправка этих кадров в API
  • Использование каждого результата анализа, возвращаемого из вызова API

Совет

Примеры в этой статье написаны на языке C#. Чтобы получить доступ к коду, на сайте GitHub перейдите на страницу Пример анализа видеокадров.

Подходы к выполнению анализа в режиме почти реального времени

Вы можете решить проблему выполнения анализа практически в режиме реального времени в видеопотоках с помощью различных подходов. В этой статье описаны три таких подхода (по уровню сложности).

Метод 1. Проектирование бесконечного цикла

Простейшая система анализа в режиме почти реального времени представляет собой бесконечный цикл. В каждой итерации этого цикла приложение извлекает кадр, анализирует его, а затем обрабатывает результат:

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

Если при анализе должен применяться упрощенный алгоритм на стороне клиента, такого подхода будет достаточно. Тем не менее если анализ происходит в облаке, полученная задержка означает, что вызов API может занять несколько секунд. В это время вы не записываете образы, и поток является, по сути, пассивным. Максимальная частота кадров ограничивается задержкой вызовов API.

Метод 2. Разрешение параллельного выполнения вызовов API

Хотя простой однопоточный цикл имеет смысл использовать с упрощенным алгоритмом на стороне клиента, он плохо сочетается с задержкой вызовов API в облаке. Решение этой проблемы состоит в том, чтобы разрешить выполнять длительный вызов API одновременно с перехватом кадров. Используя язык C#, вы можете сделать это с помощью параллелизма на основе задач. Например, вы можете запустить следующий код:

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

При таком подходе каждый анализ запускается в рамках отдельной задачи. Эта задача может выполняться в фоновом режиме, пока продолжается захват новых кадров. Этот подход позволяет избежать блокировки основного потока во время ожидания возврата вызова API. Однако такой подход может иметь некоторые недостатки.

  • При этом подходе теряются некоторые гарантированные функции простой версии. Следовательно, несколько вызовов API могут выполняться параллельно, а результаты могут возвращаться в неправильном порядке.
  • Это также может привести к тому, что несколько потоков одновременно введут функцию ConsumeResult(), что может представлять опасность, если функция не является потокобезопасной.
  • Наконец, этот простой код не позволяет отслеживать создаваемые задачи, поэтому исключения исчезают без уведомления. Таким образом, вам необходимо добавить "поток-получатель", который будет отслеживать задачи анализа, вызывать исключения, завершать долго выполняющиеся задачи и обеспечивать использование результатов в правильном порядке, по очереди.

Метод 3. Проектирование системы производителя-потребителя

Для разработки системы "производитель-потребитель" вы создадите поток производителя, похожий на бесконечный цикл предыдущего раздела. Затем вместо того, чтобы использовать результаты анализа, как только они доступны, производитель просто помещает задачи в очередь, чтобы отслеживать их.

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

Вы также создаете поток-получатель, который принимает задачи из очереди, дожидается их завершения и либо отображает результат, либо вызывает созданное исключение. Используя эту очередь, вы можете гарантировать, что результаты потребляются по одному за раз в правильном порядке без ограничения максимальной частоты кадров системы.

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

Реализация решения

Получение примера кода

Чтобы помочь вам быстро и быстро запустить приложение, мы реализовали систему, описанную в предыдущем разделе. Она должна быть достаточно гибкой для реализации множества сценариев и одновременно для простоты использования. Чтобы получить доступ к коду, перейдите к примеру репозитория видеокадров на GitHub.

Библиотека содержит FrameGrabber класс, который реализует систему производителя-потребителя для обработки видеокадров с веб-камеры. Пользователи могут указать точную форму вызова API, а класс с помощью событий позволяет вызывающему коду определять, когда поступает новый кадр или становится доступным новый результат анализа.

Просмотр примеров реализаций

Для иллюстрации некоторых возможностей мы предоставили два примера приложений, использующих библиотеку.

Первый пример приложения представляет собой простое консольное приложение, которое захватывает кадры с веб-камеры по умолчанию, а затем передает их службе "Распознавание лиц" для определения лиц. Упрощенная версия приложения представлена в следующем коде:

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

Второй пример приложения предлагает дополнительные функциональные возможности. Он позволяет выбрать, какой API вызывать для видеокадров. Слева в приложении отображается видео в реальном времени для предварительного просмотра. Справа отображается последний результат API, наложенный на соответствующий кадр.

В большинстве режимов наблюдается видимая задержка между текущим видео с левой стороны и визуализированным анализом с правой. Эта задержка равна времени, которое занимает выполнение вызова API. Исключение находится в EmotionsWithClientFaceDetect режиме, который выполняет обнаружение лиц локально на клиентском компьютере с помощью OpenCV перед отправкой изображений в службы ИИ Azure.

С помощью такого подхода вы можете сразу же визуализировать обнаруженное лицо. После возврата вызова API можно обновить атрибуты позже. Этот пример демонстрирует возможность гибридного подхода. То есть некоторые простые обработки можно выполнять на клиенте, а затем api-интерфейсы служб ИИ Azure можно использовать для расширения этой обработки с более сложным анализом при необходимости.

The LiveCameraSample app displaying an image with tags

Интеграция примеров в базу кода

Чтобы приступить к работе с этим примером, сделайте следующее:

  1. Создайте учетную запись Azure. Если у вас есть такая учетная запись, перейдите к следующему шагу.
  2. Создайте ресурсы для визуального распознавания и распознавания лиц Azure в портал Azure, чтобы получить ключ и конечную точку. При настройке обязательно выберите уровень "Бесплатный" (F0).
  3. Клонируйте репозиторий GitHub Cognitive-Samples-VideoFrameAnalysis.
  4. Откройте пример в Visual Studio 2015 или более поздней версии, а затем создайте и запустите примеры приложений:
    • Для примера BasicConsoleSample ключ распознавания лиц содержится непосредственно в коде BasicConsoleSample/Program.cs.
    • Для LiveCameraSample введите ключи в область Параметры приложения. Ключи будут сохраняться во всех сеансах как пользовательские данные.

Когда вы будете готовы интегрировать примеры, обратитесь к библиотеке VideoFrameAnalyzer из собственных проектов.

Возможности распознавания изображений, голоса, видео и текста в VideoFrameAnalyzer используют службы ИИ Azure. Корпорация Майкрософт получает изображения, аудио, видео и другие данные, которые вы отправляете (через это приложение) и может использовать их для улучшения обслуживания. Мы просим вас помочь защитить людей, данные которых ваше приложение отправляет в службы ИИ Azure.

Следующие шаги

Из этой статьи вы узнали, как выполнять анализ практически в режиме реального времени в потоковой трансляции видео с помощью служб распознавания лиц и Azure AI Vision. Вы также узнали, как можно использовать пример кода, чтобы приступить к работе.

Отправляйте отзывы и предложения в репозиторий GitHub. Чтобы предоставить более подробные отзывы об API, перейдите на наш сайт UserVoice.