실시간으로 비디오를 분석하는 방법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-VideoFrameAnalysisThese 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 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);
    }
}

분석이 경량 클라이언트 쪽 알고리즘으로 구성되어 있는 경우 이 접근 방식이 적합합니다.If our analysis consisted of a lightweight client-side algorithm, this approach would be suitable. 그러나 분석이 클라우드에서 수행되는 경우 관련된 대기 시간은 API 호출이 몇 초 정도 걸릴 수 있음을 의미하며, 이 시간 중에는 이미지를 캡처하지 않고 스레드에서 근본적으로 아무 작업도 수행하지 않습니다.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. 최대 프레임 속도는 API 호출의 대기 시간에 의해 제한됩니다.Our maximum frame-rate is limited by the latency of the API calls.

API 호출 병렬 처리Parallelizing 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 approach launches each analysis in a separate Task, which can run in the background while we continue grabbing new frames. 그러면 API 호출이 반환될 때까지 기다리는 동안 주 스레드가 차단되지 않지만, 제공된 간단한 버전인 여러 API 호출이 병렬로 발생하지 않을 수 있으며 결과가 잘못된 순서로 반환될 수 있습니다.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. 이 방법을 사용하면 여러 스레드가 동시에 ConsumeResult() 함수를 시작할 수 있으므로 함수가 스레드로부터 안전하지 않은 경우 위험할 수 있습니다.This approach 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. 따라서 추가할 최종 요소는 분석 작업을 추적하고 예외를 발생시키며 장기 실행 작업을 종료하고 결과가 한 번에 하나씩, 올바른 순서로 사용되도록 하는 “소비자” 스레드입니다.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.

생산자-소비자 디자인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);
    }
}

또한 큐에서 작업을 시작하고 완료될 때까지 기다린 다음, 결과를 표시하거나 throw된 예외를 발생하는 소비자 스레드도 있습니다.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. 큐를 사용하면 시스템의 최대 프레임 속도를 제한하지 않고 결과가 한 번에 하나씩, 올바른 순서로 사용되도록 할 수 있습니다.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, we have implemented the system described above, intending it to be flexible enough to implement many scenarios, while being easy to use. 코드에 액세스하려면 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 this is reproduced below. 이 앱은 기본 웹캠에서 프레임을 잡아 얼굴 감지를 위해 Face 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.
            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();
        }
    }
}

두 번째 샘플 앱은 좀 더 흥미로우며, 비디오 프레임에 대해 호출할 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. 단, Cognitive Services에 이미지를 제출하기 전에 OpenCV를 사용하여 클라이언트 컴퓨터에서 로컬로 얼굴을 감지하는 “EmotionsWithClientFaceDetect” 모드는 예외입니다.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. 이렇게 하면 감지된 얼굴을 즉시 시각화한 다음, 나중에 API 호출이 반환된 후 감정을 업데이트할 수 있습니다.By doing this, we can visualize the detected face immediately, and then update the emotions later once the API call returns. 이 방법은 일부 간단한 처리를 클라이언트에서 수행한 다음, Cognitive Services API를 사용하여 필요한 경우 이 기능을 보다 고급 분석으로 보강할 수 있는 “하이브리드” 접근 방식의 가능성을 보여 줍니다.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.

HowToAnalyzeVideo

코드베이스에 통합Integrating into your codebase

이 샘플을 시작하려면 다음 단계를 따르세요.To get started with this sample, follow these steps:

  1. 구독에서 Vision API에 대한 API 키를 가져옵니다.Get API keys for the Vision APIs from Subscriptions. 비디오 프레임 분석에 적용할 수 있는 API는 다음과 같습니다.For video frame analysis, the applicable APIs are:
  2. Cognitive-Samples-VideoFrameAnalysis GitHub 리포지토리 복제Clone the Cognitive-Samples-VideoFrameAnalysis GitHub repo

  3. Visual Studio 2015에서 샘플을 열고 샘플 응용 프로그램을 빌드 및 실행합니다.Open the sample in Visual Studio 2015, build and run the sample applications:

    • BasicConsoleSample의 경우 Face 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, simply reference the VideoFrameAnalyzer library from your own projects.

개발자 준수 사항Developer Code of Conduct

모든 Cognitive Services와 마찬가지로 Microsoft API 및 샘플을 사용하여 개발하는 개발자는 “Microsoft Cognitive Services에 대한 개발자 준수 사항”을 따라야 합니다.As with all the Cognitive Services, Developers developing with our APIs and samples are required to follow the "Developer Code of Conduct for Microsoft Cognitive Services."

VideoFrameAnalyzer의 이미지, 음성, 비디오 또는 텍스트 해석 기능은 Azure Cognitive Services를 사용합니다.The image, voice, video or text understanding capabilities of VideoFrameAnalyzer uses Azure Cognitive Services. Microsoft는 이 앱을 통해 업로드하는 이미지, 오디오, 비디오 및 기타 데이터를 수신하며, 서비스 개선 목적으로 사용할 수 있습니다.Microsoft will receive the images, audio, video, and other data that you upload (via this app) and may use them for service improvement purposes. 앱이 개인 데이터를 Azure Cognitive Services에 전송하는 사용자를 보호할 수 있도록 도와주시기 바랍니다.We ask for your help in protecting the people whose data your app sends to Azure Cognitive Services.

요약Summary

이 가이드에서는 Face, Computer Vision 및 Emotion 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 you can use our sample code to get started. Microsoft Cognitive Services 등록 페이지에서 체험 API 키를 사용하여 앱 빌드를 시작할 수 있습니다.You can get started building your app with free API keys at the Microsoft Cognitive Services sign-up page.

GitHub 리포지토리에서 자유롭게 피드백과 제안 사항을 말씀해 주셔도 되고, 보다 광범위한 API 피드백의 경우 UserVoice 사이트를 이용하셔도 됩니다.Please feel free to provide feedback and suggestions in the GitHub repository, or for more broad API feedback, on our UserVoice site.