Como analisar vídeos em tempo realHow to analyze videos in real time

Este guia demonstrará como executar uma análise quase em tempo real em quadros obtidos de um fluxo de vídeo ao vivo.This guide will demonstrate how to perform near-real-time analysis on frames taken from a live video stream. Os componentes básicos de um sistema desse tipo são:The basic components in such a system are:

  • Adquirir quadros de uma fonte de vídeoAcquire frames from a video source
  • Selecionar os quadros a serem analisadosSelect which frames to analyze
  • Enviar esses quadros para a APISubmit these frames to the API
  • Consumir cada resultado da análise retornado da chamada à APIConsume each analysis result that is returned from the API call

Esses exemplos são gravados em C# e o código pode ser encontrado no GitHub aqui: 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.

A abordagemThe Approach

Há várias maneiras de resolver o problema de execução da análise quase em tempo real em fluxos de vídeo.There are multiple ways to solve the problem of running near-real-time analysis on video streams. Começaremos descrevendo três abordagens em níveis crescentes de sofisticação.We will start by outlining three approaches in increasing levels of sophistication.

Uma abordagem simplesA Simple Approach

O design mais simples para um sistema de análise quase em tempo real é um loop infinito, no qual em cada iteração capturamos um quadro, analisamos esse quadro e, em seguida, consumimos o resultado:The simplest design for a near-real-time analysis system is an infinite loop, where in each iteration we 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);
    }
}

Se nossa análise consistiu em um algoritmo leve do lado do cliente, essa abordagem pode ser adequada.If our analysis consisted of a lightweight client-side algorithm, this approach would be suitable. No entanto, quando nossa análise ocorre na nuvem, a latência envolvida significa que uma chamada à API pode levar alguns segundos e, durante esse período, não estamos capturando imagens e, basicamente, nosso thread não faz nada.However, when our analysis is happening in the cloud, the latency involved means that an API call might take several seconds, during which time we are not capturing images, and our thread is essentially doing nothing. Nossa taxa máxima de quadros é limitada pela latência das chamadas à API.Our maximum frame-rate is limited by the latency of the API calls.

Paralelizando chamadas à APIParallelizing API Calls

Enquanto um loop single-threaded simples faz sentido para um algoritmo leve do lado do cliente, ele não se ajusta bem à latência envolvida em chamadas à API na nuvem.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. A solução para esse problema é permitir que as chamadas à API de execução longa sejam executadas em paralelo com a captura de quadros.The solution to this problem is to allow the long-running API calls to execute in parallel with the frame-grabbing. No C#, podemos conseguir isso usando o paralelismo baseado em Tarefa, por exemplo: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);
        }
    }
}

Essa abordagem inicia cada análise em uma Tarefa separada, que pode ser executada em segundo plano enquanto continuamos capturando novos quadros.This approach launches each analysis in a separate Task, which can run in the background while we continue grabbing new frames. Isso impede o bloqueio do thread principal durante a espera do retorno de uma chamada à API. No entanto, perdemos algumas das garantias que a versão simples fornecia – várias chamadas à API podem ocorrer em paralelo e os resultados podem ser retornados na ordem incorreta.It avoids blocking the main thread while waiting for an API call to return, however we have lost some of the guarantees that the simple version provided -- multiple API calls might occur in parallel, and the results might get returned in the wrong order. Essa abordagem também pode fazer com que vários threads entrem na função ConsumeResult() simultaneamente, o que pode ser perigoso, caso a função não seja thread-safe.This approach could also cause multiple threads to enter the ConsumeResult() function simultaneously, which could be dangerous, if the function is not thread-safe. Por fim, esse código simples não acompanha as Tarefas que são criadas e, portanto, as exceções desaparecerão silenciosamente.Finally, this simple code does not keep track of the Tasks that get created, so exceptions will silently disappear. Portanto, o ingrediente final a adicionarmos é um thread "consumidor" que acompanhará as tarefas de análise, acionará exceções, encerrará tarefas de execução longa e garantirá que os resultados sejam consumidos na ordem correta, um de cada vez.Thus, the final ingredient for us to add is a "consumer" thread that will track the analysis tasks, raise exceptions, kill long-running tasks, and ensure the results get consumed in the correct order, one at a time.

Um design produtor-consumidorA Producer-Consumer Design

Em nosso sistema final "produtor-consumidor", temos um thread produtor que se assemelha ao nosso loop infinito anterior.In our final "producer-consumer" system, we have a producer thread that looks similar to our previous infinite loop. No entanto, em vez de consumir os resultados da análise assim que estiverem disponíveis, o produtor simplesmente coloca as tarefas em uma fila para acompanhá-las.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);
    }
}

Também temos um thread consumidor, que está removendo as tarefas da fila, aguardando sua conclusão e exibindo o resultado ou acionando a exceção gerada.We also have a consumer thread, that is taking tasks off the queue, waiting for them to finish, and either displaying the result or raising the exception that was thrown. Usando a fila, podemos garantir que os resultados sejam consumidos um de cada vez, na ordem correta, sem limitar a taxa máxima de quadros do sistema.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);
    }
}

Implementando a soluçãoImplementing the Solution

IntroduçãoGetting Started

Para preparar seu aplicativo e deixá-lo funcional o mais rapidamente possível, implementamos o sistema descrito acima, com o intuito de que ele seja flexível o suficiente para implementar muitos cenários e, ao mesmo tempo, seja fácil de usar.To get your app up and running as quickly as possible, we have implemented the system described above, intending it to be flexible enough to implement many scenarios, while being easy to use. Para acessar o código, acesse https://github.com/Microsoft/Cognitive-Samples-VideoFrameAnalysis.To access the code, go to https://github.com/Microsoft/Cognitive-Samples-VideoFrameAnalysis.

A biblioteca contém a classe FrameGrabber, que implementa o sistema produtor-consumidor abordado acima para processar quadros de vídeo de uma webcam.The library contains the class FrameGrabber, which implements the producer-consumer system discussed above to process video frames from a webcam. O usuário pode especificar a forma exata da chamada à API, e a classe usa eventos para permitir que o código de chamada reconheça quando um novo quadro é adquirido ou um novo resultado de análise fica disponível.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.

Para ilustrar algumas das possibilidades, há dois aplicativos de exemplo que usam a biblioteca.To illustrate some of the possibilities, there are two sample apps that use the library. O primeiro é um aplicativo de console simples e uma versão simplificada do que é reproduzido abaixo.The first is a simple console app, and a simplified version of this is reproduced below. Ele captura quadros da webcam padrão e envia-os para a API de Detecção Facial para a detecção facial.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.
            FaceServiceClient faceClient = new FaceServiceClient("<subscription key>");

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

O segundo aplicativo de exemplo é um pouco mais interessante e permite que você escolha quais API serão chamadas nos quadros de vídeo.The second sample app is a bit more interesting, and allows you to choose which API to call on the video frames. No lado esquerdo, o aplicativo mostra uma visualização do vídeo ao vivo e, no lado direito, mostra o resultado de API mais recente sobreposto no quadro correspondente.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.

Na maioria dos modos, haverá um atraso visível entre o vídeo ao vivo à esquerda e a análise visualizada à direita.In most modes, there will be a visible delay between the live video on the left, and the visualized analysis on the right. Esse atraso é o tempo necessário para fazer a chamada à API.This delay is the time taken to make the API call. A exceção está no modo "EmotionsWithClientFaceDetect", que executa a detecção facial localmente no computador cliente usando o OpenCV, antes de enviar imagens aos Serviços Cognitivos.The exception to this is in the "EmotionsWithClientFaceDetect" mode, which performs face detection locally on the client computer using OpenCV, before submitting any images to Cognitive Services. Ao fazer isso, podemos visualizar a face detectada imediatamente e, em seguida, atualizar as emoções mais tarde, após o retorno da chamada à API.By doing this, we can visualize the detected face immediately, and then update the emotions later once the API call returns. Isso demonstra a possibilidade de uma abordagem "híbrida", na qual uma parte do processamento simples pode ser feita no cliente e, em seguida, as APIs de Serviços Cognitivos podem ser usadas para aumentar isso com uma análise mais avançada, quando necessário.This demonstrates the possibility of a "hybrid" approach, where some simple processing can be performed on the client, and then Cognitive Services APIs can be used to augment this with more advanced analysis when necessary.

Captura de tela do aplicativo LiveCameraSample mostrando uma imagem com marcas exibidas

Integrando sua base de códigoIntegrating into your codebase

Para começar a usar esse exemplo, siga estas etapas:To get started with this sample, follow these steps:

  1. Obtenha chaves de API para as APIs da Pesquisa Visual em Assinaturas.Get API keys for the Vision APIs from Subscriptions. Para a análise de quadro de vídeo, as APIs aplicáveis são:For video frame analysis, the applicable APIs are:

  2. Clone o repositório GitHub Cognitive-Samples-VideoFrameAnalysisClone the Cognitive-Samples-VideoFrameAnalysis GitHub repo

  3. Abra o exemplo no Visual Studio 2015, compile e execute os aplicativos de exemplo:Open the sample in Visual Studio 2015, build and run the sample applications:

    • Para BasicConsoleSample, a chave da API de Detecção Facial é embutida em código diretamente em BasicConsoleSample/Program.cs.For BasicConsoleSample, the Face API key is hard-coded directly in BasicConsoleSample/Program.cs.
    • Para LiveCameraSample, as chaves devem ser inseridas no painel Configurações do aplicativo.For LiveCameraSample, the keys should be entered into the Settings pane of the app. Elas serão persistentes entre as sessões como dados de usuário.They will be persisted across sessions as user data.

Quando estiver pronto para fazer a integração, basta referenciar a biblioteca VideoFrameAnalyzer em seus próprios projetos.When you're ready to integrate, simply reference the VideoFrameAnalyzer library from your own projects.

As funcionalidades de reconhecimento de imagem, voz, vídeo ou texto do VideoFrameAnalyzer usam os Serviços Cognitivos do Azure.The image, voice, video or text understanding capabilities of VideoFrameAnalyzer uses Azure Cognitive Services. A Microsoft receberá as imagens, o áudio, o vídeo e outros dados que você fizer upload (por meio desse aplicativo) e poderá usá-las para fins de melhoria do serviço.Microsoft will receive the images, audio, video, and other data that you upload (via this app) and may use them for service improvement purposes. Pedimos sua colaboração para proteger as pessoas cujos dados são enviados por seu aplicativo aos Serviços Cognitivos do Azure.We ask for your help in protecting the people whose data your app sends to Azure Cognitive Services.

ResumoSummary

Neste guia, você aprendeu a executar análises quase em tempo real em fluxos de vídeo ao vivo usando as APIs de Detecção Facial, de Pesquisa Visual Computacional e de Detecção de Emoções, e como é possível usar nosso código de exemplo para começar.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 you can use our sample code to get started. Comece compilando o aplicativo com as chaves de API gratuitas na página de entrada dos Serviços Cognitivos do Azure.You can get started building your app with free API keys at the Azure Cognitive Services sign-up page.

Fique à vontade para fornecer comentários e sugestões no repositório GitHub ou comentários mais abrangentes sobre a API em nosso site UserVoice.Please feel free to provide feedback and suggestions in the GitHub repository, or for more broad API feedback, on our UserVoice site.