실시간 동영상 분석하기

등록일시: 2017-11-30 08:00,  수정일시: 2018-01-25 07:17
조회수: 5,753
이 문서는 Cognitive Services 기술을 널리 알리고자 하는 개인적인 취지로 제공되는 번역문서입니다. 이 문서에 대한 모든 저작권은 마이크로소프트에 있으며 요청이 있을 경우 언제라도 게시가 중단될 수 있습니다. 번역 내용에 오역이 존재할 수 있고 주석은 번역자 개인의 의견일 뿐이며 마이크로소프트는 이에 관한 어떠한 보장도 하지 않습니다. 번역이 완료된 이후에도 대상 제품 및 기술이 개선되거나 변경됨에 따라 원문의 내용도 변경되거나 보완되었을 수 있으므로 주의하시기 바랍니다.
본문에서는 라이브 동영상 스트림으로부터 가져온 프레임을 대상으로 실시간에 가까운 분석을 수행하는 방법을 살펴봅니다.

본문에서는 라이브 동영상 스트림으로부터 가져온 프레임을 대상으로 거의 실시간에 가까운 분석을 수행하는 방법을 살펴봅니다. 이를 위한 시스템의 기본 구성 요소들은 다음과 같습니다:

  • 동영상 원본에서 프레임 가져오기
  • 분석할 프레임 선택하기
  • 선택한 프레임을 API에 제출하기
  • API 호출로부터 반환된 각각의 분석 결과 사용하기

본문의 예제들은 C#으로 작성되었으며, 실제 코드는 GitHub의 https://github.com/Microsoft/Cognitive-Samples-VideoFrameAnalysis/ 저장소에서 확인할 수 있습니다.

접근방식

실시간에 근접한 동영상 스트림 분석을 수행하기 위한 방법은 다양합니다. 지금부터 보다 세련된 순서에 따라 세 가지 접근방식을 개략적으로 살펴보겠습니다.

간단한 접근방식

실시간에 가까운 분석 시스템을 구현하기 위한 가장 간단한 설계는 무한 루프로, 매번 루프가 반복될 때마다 프레임을 잡아서 분석한 다음, 그 결과를 사용합니다:

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

만약 필요로 하는 분석이 가벼운 클라이언트 측 알고리즘으로 구성된다면 이렇게 간단한 접근방식도 무난할 것입니다. 그러나 이 방식은 클라우드에서 분석이 처리되는 동안 필연적으로 대기 시간이 발생하게 되며, 경우에 따라서는 API 호출이 완료될 때까지 몇 초가 걸릴 수도 있습니다. 따라서 그 동안에는 이미지 캡처 작업이 진행되지 않을뿐만 아니라, 본질적으로 스레드는 아무런 작업도 수행하지 않습니다. 결국 이 방식의 최대 프레임 속도는 API 호출의 대기 시간에 의해서 결정됩니다.

API 호출 병렬화

간단한 단일-스레드 루프는 가벼운 클라이언트 측 알고리즘에는 적합하지만, 클라우드 API 호출과 관련된 대기 시간이 발생한다는 문제점이 존재합니다. 이 문제점에 대한 한 가지 해결 방안은 장기 실행 API 호출과 프레임 캡처 작업을 병렬로 동시에 수행하는 것입니다. 가령, C#에서는 다음과 같이 Task 기반 병렬 처리를 통해서 이를 구현할 수 있습니다:

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

이 방식을 사용하면 각각의 분석이 별도의 Task로 시작되므로, 새로운 프레임을 가져오는 동안 백그라운드에서는 계속해서 분석 처리를 수행할 수 있습니다. 다만, 호출한 API가 반환되기를 기다리는 동안 메인 스레드가 잠기는 상황은 피할 수 있지만, 앞에서 살펴본 간단한 버전의 구현이 보장해주는 이점을 일부 잃어버리게 됩니다. 즉, API가 동시에 여러 번 호출될 수 있으며, 결과가 잘못된 순서로 반환될 수도 있습니다. 여러 스레드가 동시에 ConsumeResult() 함수에 진입할 수 있으며, 결과적으로 이 함수가 스레드에 안전하지 않을 경우 위험해질 수도 있습니다. 마지막으로, 이 간단한 코드는 생성된 Task들을 추적하지 않기 때문에, 예외가 소리소문 없이 사라집니다. 따라서 마지막으로 검토해 볼 접근방식은, 분석 작업을 추적하고, 예외를 발생시키며, 장기 실행 작업을 중단하고, 올바른 순서로 결과를 한 번에 하나씩 소비할 수 있게 보장해주는 "소비자" 스레드입니다.

생산자-소비자 디자인 패턴

마지막으로 살펴볼 "생산자-소비자" 시스템은 앞에서 살펴본 무한 루프와 매우 비슷한 생산자 스레드를 갖고 있습니다. 그러나 생산자는 분석 결과를 그 즉시 사용하는 대신, 그저 단순히 작업을 큐에 넣고 이를 추적합니다.

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

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

솔루션 구현하기

시작하기

가급적 간단하게 응용 프로그램을 구현하고 실행해볼 수 있도록, 지금까지 설명한 시스템을 예제로 구현했습니다. 다양한 시나리오에 대응한 구현이 가능하면서도 사용이 쉽도록 유연하게 작성되었습니다. 실제 코드는 https://github.com/Microsoft/Cognitive-Samples-VideoFrameAnalysis/ 저장소에서 확인할 수 있습니다.

라이브러리에는 웹 캠의 동영상 프레임을 처리하기 위해 앞에서 살펴본 생산자-소비자 시스템을 구현한 클래스인 FrameGrabber 클래스가 포함되어 있습니다. 사용자는 API 호출의 정확한 형식을 지정할 수 있으며, 이 클래스는 이벤트를 이용해서 호출한 코드가 새 프레임을 가져왔는지, 또는 새로운 분석 결과를 사용할 수 있는지 여부를 알려줍니다.

사용 가능한 몇 가지 사례를 설명하기 위한, 라이브러리를 활용한 두 가지 예제 응용 프로그램이 제공됩니다. 먼저 첫 번째 예제는 간단한 콘솔 응용 프로그램으로, 다음은 이 예제를 간단히 정리한 코드입니다. 이 예제 응용 프로그램은 기본 웹 캠의 프레임을 잡고, 이를 Face API에 제출해서 얼굴을 인식합니다.

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

두 번째 예제 응용 프로그램은 보다 흥미로운데, 이번에는 UI에서 동영상 프레임을 대상으로 호출할 API를 선택할 수 있습니다. 응용 프로그램의 좌측에는 실시간 동영상의 미리보기가 출력되고, 그 우측에는 해당 프레임에 대해 가장 최근에 반환된 API의 결과가 출력되어 표시됩니다.

대부분의 모드에서 좌측의 라이브 동영상과 우측의 시각화된 분석 결과 간에 두드러지는 지연이 존재하는 것을 확인할 수 있습니다. 이는 API 호출을 수행하는 데 소요되는 시간으로 인한 것입니다. 한 가지 예외는 이미지를 Cognitive Services에 제출하기 전, OpenCV를 이용해서 클라이언트 컴퓨터에서 로컬로 미리 얼굴 인식을 수행하는 "EmotionsWithClientFaceDetect" 모드입니다. 이 방식을 사용하면, 우선 감지된 얼굴부터 즉시 시각화한 다음, 이어서 API 호출이 반환되면 감정에 관한 정보를 갱신할 수 있습니다. 이 예제는, 먼저 클라이언트 측에서 수행할 수 있는 일부 간단한 처리를 수행한 다음, 필요할 경우에만 Cognitive Services API를 이용한 보다 고급의 분석으로 결과를 보완하는 "하이브리드" 접근 방식의 가능성을 보여줍니다.

HowToAnalyzeVideo

자신의 코드베이스에 통합하기

본문의 예제를 시작하려면 다음 과정을 수행해야 합니다:

  1. Cognitive Services 체험하기 페이지에서 Vision API들의 API 키를 발급받습니다. 동영상 프레임 분석을 위해서 필요한 API들은 다음과 같습니다:
  2. Cognitive-Samples-VideoFrameAnalysis GitHub 저장소를 복제합니다.

  3. Visual Studio 2015 이상에서 예제 솔루션을 열고 빌드한 다음, 응용 프로그램을 실행합니다:

    • BasicConsoleSample 예제의 Face API 키는 BasicConsoleSample/Program.cs 파일에 직접 하드 코딩되어 있습니다.
    • LiveCameraSample 예제의 키들은 응용 프로그램의 Settings 패인에 입력해야 합니다. 이 정보들은 사용자 데이터로 세션 간에 유지됩니다.

통합할 준비를 마쳤다면, 여러분의 프로젝트에서 VideoFrameAnalyzer 라이브러리를 참조하기만 하면 됩니다.

개발자 준수 사항

모든 다른 Cognitive Services와 마찬가지로, API 및 본문의 예제를 활용하여 개발을 진행하는 개발자는 "Microsoft Cognitive Services 개발자 준수 사항"을 준수해야 합니다.

VideoFrameAnalyzer 라이브러리의 이미지, 음성, 비디오 및 텍스트 인식 기능은 Microsoft Cognitive Services를 사용합니다. Microsoft는 서비스 개선을 목적으로 예제 응용 프로그램을 통해서 업로드 한 이미지, 오디오, 비디오 및 기타 데이터를 활용할 수 있습니다. 여러분의 응용 프로그램이 Microsoft Cognitive Services로 전송하는 사용자의 데이터를 보호할 수 있도록 여러분의 도움을 요청합니다.

요약

본문에서는 Face, Computer Vision 및 Emotion API를 사용해서 라이브 동영상 스트림을 실시간에 가깝게 분석하는 방법과, 예제 코드를 활용하고 시작하는 방법을 알아봤습니다. Cognitive Services 체험하기 페이지에서 무료로 발급받을 수 있는 API 키만으로도 충분히 응용 프로그램 구축을 시작할 수 있습니다

언제든지 GitHub 저장소를 통해서 의견이나 제안을 보내주십시오. 또는 UserVoice 사이트를 통해서 API에 관한 보다 다양한 의견도 받습니다.