Análisis de vídeos en tiempo realHow to analyze videos in real time

En esta guía se demostrará cómo realizar el análisis casi en tiempo real de fotogramas procedentes de una secuencia de vídeo en directo.This guide will demonstrate how to perform near-real-time analysis on frames taken from a live video stream. Los componentes básicos en un sistema de este tipo son:The basic components in such a system are:

  • Adquirir fotogramas desde un origen de vídeoAcquire frames from a video source
  • Seleccionar los fotogramas que se van a analizarSelect which frames to analyze
  • Enviar estos fotogramas a la APISubmit these frames to the API
  • Consumir cada resultado del análisis que se devuelve de la llamada APIConsume each analysis result that is returned from the API call

Estos ejemplos están escritos en C#, y el código puede encontrarse en GitHub aquí: 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.

El enfoqueThe Approach

Hay varias formas de solucionar el problema de la ejecución del análisis casi en tiempo real en secuencias de vídeo.There are multiple ways to solve the problem of running near-real-time analysis on video streams. Empezaremos por esbozar estos tres enfoques en niveles cada vez mayores de sofisticación.We will start by outlining three approaches in increasing levels of sophistication.

Un enfoque sencilloA Simple Approach

El diseño más sencillo para un sistema de análisis casi en tiempo real es un bucle infinito, donde en cada iteración se toma un fotograma, se analiza y, a continuación, se consume el 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);
    }
}

Si nuestro análisis consistió en un algoritmo de cliente ligero, este enfoque sería adecuado.If our analysis consisted of a lightweight client-side algorithm, this approach would be suitable. Sin embargo, cuando el análisis tiene lugar en la nube, la latencia que conlleva significa que una llamada API puede tardar varios segundos, durante los cuales no se capturan imágenes y el subproceso básicamente no hace 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. La velocidad de fotogramas máxima está limitada por la latencia de las llamadas API.Our maximum frame-rate is limited by the latency of the API calls.

Paralelización de llamadas APIParallelizing API Calls

Si bien un bucle simple de un único subproceso tiene sentido para un algoritmo de cliente ligero, no se ajusta bien a la latencia involucrada en las llamadas API de la nube.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. La solución a este problema es permitir que las llamadas API de larga ejecución se ejecuten en paralelo con la captura de fotogramas.The solution to this problem is to allow the long-running API calls to execute in parallel with the frame-grabbing. En C#, se podría lograr esto usando paralelismo basado en tareas, por ejemplo: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);
        }
    }
}

Este método inicia cada análisis en una tarea independiente, que se puede ejecutar en segundo plano mientras se siguen capturando fotogramas nuevos.This approach launches each analysis in a separate Task, which can run in the background while we continue grabbing new frames. Esto evita bloquear el subproceso principal mientras se espera que se devuelva una llamada API; sin embargo, se han perdido algunas de las garantías que proporcionaba la versión sencilla: pueden producirse varias llamadas API en paralelo y los resultados podrían devolverse en el orden equivocado.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. Además, este método podría provocar que varios subprocesos ingresaran en la función ConsumeResult() al mismo tiempo, lo que podría ser peligroso si la función no es segura para subprocesos.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 último, este código simple no realiza un seguimiento de las tareas que se crean, por lo que las excepciones desaparecerán de manera silenciosa.Finally, this simple code does not keep track of the Tasks that get created, so exceptions will silently disappear. Por lo tanto, el ingrediente final que se debe agregar es un subproceso de "consumidor" que realice un seguimiento de las tareas de análisis, produzca excepciones, elimine tareas de larga duración y asegure que los resultados se consuman en el orden correcto, uno a la 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.

Un diseño de productor-consumidorA Producer-Consumer Design

En el sistema final de "productor-consumidor", se cuenta con un subproceso productor que es similar al bucle infinito anterior.In our final "producer-consumer" system, we have a producer thread that looks similar to our previous infinite loop. No obstante, en lugar de consumir los resultados del análisis en cuanto están disponibles, el productor simplemente coloca las tareas en una cola para realizarles un seguimiento.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);
    }
}

También se tiene un subproceso consumidor, que toma tareas de la cola, esperando a finalicen, y muestra el resultado, o bien genera la excepción que se produjo.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. Mediante el uso de la cola, es posible garantizar que resultados se consuman uno a la vez, en el orden correcto, sin limitar la velocidad de fotogramas máxima del 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);
    }
}

Implementación de la soluciónImplementing the Solution

IntroducciónGetting Started

Para que la aplicación esté en funcionamiento lo más rápido posible, se ha implementado el sistema descrito anteriormente, con la intención de que sea lo suficientemente flexible como para implementar muchos escenarios, a la vez de que sea 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 obtener acceso al código, vaya a https://github.com/Microsoft/Cognitive-Samples-VideoFrameAnalysis.To access the code, go to https://github.com/Microsoft/Cognitive-Samples-VideoFrameAnalysis.

La biblioteca contiene la clase FrameGrabber, que implementa el sistema productor-consumidor descrito anteriormente para procesar fotogramas de vídeo desde una cámara web.The library contains the class FrameGrabber, which implements the producer-consumer system discussed above to process video frames from a webcam. El usuario puede especificar la forma exacta de la llamada API, y la clase utiliza eventos para comunicar al código de llamada cuándo se adquiere un nuevo fotograma, o cuándo hay disponible un resultado del análisis.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 algunas de las posibilidades, hay dos aplicaciones de ejemplo que utilizan la biblioteca.To illustrate some of the possibilities, there are two sample apps that use the library. La primera es una aplicación de consola simple; una versión simplificada de esta se reproduce a continuación.The first is a simple console app, and a simplified version of this is reproduced below. Toma fotogramas de la cámara web predeterminada y los envía a Face API para la detección de caras.It grabs frames from the default webcam, and submits them to the Face API for face detection.

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

La segunda aplicación de ejemplo es un poco más interesante y le permite elegir a qué API llamar en los fotogramas 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. En el lado izquierdo, la aplicación muestra una vista previa del vídeo en directo; en el lado derecho, muestra el resultado más reciente de la API superpuesto con el fotograma correspondiente.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.

En la mayoría de los modos, habrá un retraso visible entre el vídeo en directo a la izquierda y el análisis visualizado a la derecha.In most modes, there will be a visible delay between the live video on the left, and the visualized analysis on the right. Este retraso es el tiempo necesario para realizar la llamada API.This delay is the time taken to make the API call. La excepción a esto es el modo "EmotionsWithClientFaceDetect", que realiza la detección de caras localmente en el equipo cliente utilizando OpenCV, antes de enviar las imágenes a Cognitive Services.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. Al hacerlo, se posible visualizar la cara detectada inmediatamente y, a continuación, actualizar las emociones posteriormente una vez que se devuelve la llamada API.By doing this, we can visualize the detected face immediately, and then update the emotions later once the API call returns. Esto demuestra la posibilidad de un enfoque "híbrido", donde se puede realizar un procesamiento simple en el cliente y, a continuación, puede usarse Cognitive Services APIs para intensificar esto con un análisis más avanzado, cuando sea necesario.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 pantalla de la aplicación LiveCameraSample que muestra una imagen con etiquetas

Integración en el código baseIntegrating into your codebase

Para empezar a trabajar con este ejemplo, siga estos pasos:To get started with this sample, follow these steps:

  1. Obtenga las claves de la API de Vision API de Suscripciones.Get API keys for the Vision APIs from Subscriptions. Para el análisis de fotogramas de vídeo, las API correspondientes son:For video frame analysis, the applicable APIs are:

  2. Clone el repositorio Cognitive-Samples-VideoFrameAnalysis de GitHub.Clone the Cognitive-Samples-VideoFrameAnalysis GitHub repo

  3. Abra el ejemplo en Visual Studio 2015, o cualquier versión posterior, y compile y ejecute las aplicaciones de ejemplo:Open the sample in Visual Studio 2015 or later, build and run the sample applications:

    • Para BasicConsoleSample, la clave de Face API está codificado de forma rígida directamente en BasicConsoleSample/Program.cs.For BasicConsoleSample, the Face API key is hard-coded directly in BasicConsoleSample/Program.cs.
    • Para LiveCameraSample, las claves se deben escribir en el panel de configuración de la aplicación.For LiveCameraSample, the keys should be entered into the Settings pane of the app. Se conservarán de una sesión a otra como datos de usuario.They will be persisted across sessions as user data.

Cuando esté listo para la integración, simplemente haga referencia a la biblioteca VideoFrameAnalyzer desde sus propios proyectos.When you're ready to integrate, simply reference the VideoFrameAnalyzer library from your own projects.

Las funcionalidades de comprensión de imágenes, voces, vídeos o texto de VideoFrameAnalyzer emplean Microsoft Cognitive Services.The image, voice, video or text understanding capabilities of VideoFrameAnalyzer uses Azure Cognitive Services. Microsoft recibirá las imágenes, el audio, el vídeo y otros datos que usted cargue (a través de esta aplicación) y podrá usarlos para fines de mejora del servicio.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 su ayuda para proteger a las personas cuyos datos envíe la aplicación a Azure Cognitive Services.We ask for your help in protecting the people whose data your app sends to Azure Cognitive Services.

ResumenSummary

En esta guía, ha aprendido a ejecutar análisis casi en tiempo real en las secuencias de vídeo en directo mediante Face API y Computer Vision API, así como a utilizar el código de ejemplo para empezar a trabajar.In this guide, you learned how to run near-real-time analysis on live video streams using the Face and Computer Vision APIs, and how you can use our sample code to get started. Puede empezar a compilar su aplicación con las claves de API gratuitas disponibles en la página de registro de Azure Cognitive Services.You can get started building your app with free API keys at the Azure Cognitive Services sign-up page.

No dude en enviar comentarios y sugerencias al repositorio de GitHub o, para enviar comentarios más amplios sobre la API, a nuestro sitio de UserVoice.Please feel free to provide feedback and suggestions in the GitHub repository, or for more broad API feedback, on our UserVoice site.