거의 실시간으로 비디오 분석

이 문서에서는 Azure AI Vision API를 사용하여 라이브 비디오 스트림에서 가져온 프레임에 대해 거의 실시간으로 분석하는 방법을 보여 줍니다. 이러한 분석의 기본 요소는 다음과 같습니다.

  • 비디오 원본에서 프레임 획득
  • 분석할 프레임 선택
  • 이러한 프레임을 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);
    }
}

또한 큐에서 작업을 시작하고 작업이 완료될 때까지 기다렸다가 결과를 표시하거나 throw된 예외를 발생시키는 소비자 스레드를 만듭니다. 이 큐를 사용하면 시스템의 최대 프레임 속도를 제한하지 않고 결과가 한 번에 하나씩 올바른 순서로 소비되도록 보장할 수 있습니다.

// 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 호출을 지정할 수 있으며, 클래스에서는 이벤트를 사용하여 새 프레임을 획득하거나 새 분석 결과를 사용할 수 있게 되면 그 사실을 호출 코드에 알립니다.

샘플 구현 보기

몇 가지 가능성을 설명하기 위해 라이브러리를 사용하는 두 개의 샘플 앱을 제공해드렸습니다.

첫 번째 샘플 앱은 기본 웹캠에서 프레임을 가져와 얼굴 감지용 Face 서비스로 전송하는 간단한 콘솔 앱입니다. 앱의 간소화된 버전은 다음 코드에 표시됩니다.

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 Azure AI 서비스에 이미지를 제출하기 전에 OpenCV를 사용하여 클라이언트 컴퓨터에서 로컬로 얼굴 감지를 수행하는 모드입니다.

이 접근 방식을 사용하면 감지된 얼굴을 즉시 시각화할 수 있습니다. 그런 다음 나중에 API 호출이 반환된 후 특성을 업데이트할 수 있습니다. 이는 "하이브리드" 접근 방식의 가능성을 보여줍니다. 즉, 간단한 처리 작업을 클라이언트에서 수행한 다음, 필요할 때 Azure AI 서비스 API를 사용하여 이 처리 기능을 보다 발전된 분석으로 보강할 수 있습니다.

The LiveCameraSample app displaying an image with tags

코드베이스에 샘플 통합

이 샘플을 시작하려면 다음 단계를 수행합니다.

  1. Azure 계정을 만듭니다. 이미 있는 경우 다음 단계로 건너뛸 수 있습니다.
  2. Azure Portal에서 Azure AI 비전 및 Face용 리소스를 만들어 키와 엔드포인트를 가져옵니다. 설치 중에 체험 계층(F0)을 선택했는지 확인합니다.
    • Azure AI Vision
    • Face 리소스를 배포한 후 리소스로 이동을 선택하여 각 리소스에 대한 키와 엔드포인트를 수집합니다.
  3. Cognitive-Samples-VideoFrameAnalysis GitHub 리포지토리를 복제합니다.
  4. Visual Studio 2015 이상에서 샘플을 열고, 샘플 애플리케이션을 빌드 및 실행합니다.
    • BasicConsoleSample의 경우 Face 키가 BasicConsoleSample/Program.cs에 직접 하드 코딩되어 있습니다.
    • LiveCameraSample의 경우 앱의 설정 창에 키를 입력합니다. 키는 사용자 데이터로 세션 간에 지속됩니다.

샘플을 통합할 준비가 되면 고유한 프로젝트에서 VideoFrameAnalyzer 라이브러리를 참조하세요.

VideoFrameAnalyzer이미지, 음성, 비디오 및 텍스트 이해 기능은 Azure AI 서비스를 사용합니다. Microsoft는 이 앱을 통해 업로드하는 이미지, 오디오, 비디오 및 기타 데이터를 수신하며 서비스 개선을 위해 사용할 수 있습니다. 앱이 개인 데이터를 Azure AI 서비스에 전송하는 사용자를 보호할 수 있도록 도와주시기 바랍니다.

다음 단계

이 문서에서는 Face 및 Azure AI 비전 서비스를 사용하여 라이브 비디오 스트림을 거의 실시간으로 분석하는 방법을 알아보았습니다. 샘플 코드를 사용하여 시작하는 방법도 배웠습니다.

GitHub 리포지토리에서 자유롭게 피드백과 의견을 남겨주세요. 보다 광범위한 API 피드백을 제공하려면 UserVoice 사이트로 이동하세요.