노이즈 심한 저해상도 비디오의 품질을 개선해 쾌적한 사용자 경험 누리는 방법 (3)

by NVIDIA Korea

API를 사용해 다중의 비디오 효과 기능 연결하기

효과를 다중으로 연결하면 여러 애플리케이션에서 흥미로운 결과를 낼 수 있습니다. 본 포스팅은 서로 잘 어울리는 두 가지 효과, 아티팩트 감소와 업스케일러를 연결하는 방법을 집중적으로 살펴봅니다. 또 다른 사례는 노이즈가 심한 웹캠 스트림의 처리를 위해 비디오 잡음 제거와 슈퍼 해상도 또는 업스케일러를 실행하는 것입니다. 여러분의 활용 사례에 가장 적합한 효과를 선택해 사용할 수 있습니다.

API와 그 사용법에 대한 더 자세한 내용은 다음과 같습니다. Figure 6은 Video Effects SDK의 여러 기능을 활용한 고급 프로세스를 보여줍니다.

  • 효과의 생성과 구성
  • CUDA 스트림 구성, 버퍼 할당, 모델 로드
  • 데이터 로드와 효과 실행
The process includes the following steps: Create the effect, load the model, and use the effect.
Figure 6. Video Effects SDK API의 간단한 3단계 활용법

다음의 영상은 앞서 설명한 고급 기능의 플로우를 보여줍니다. 그러나 이 프로세스에는 다양한 세부 내용이 있으며, 이와 관련해서는 본 포스팅의 후반부에서 논하도록 하겠습니다. 이 영상은 또한 Maxine 가상 배경을 위해 GPU와 API의 세부 정보를 활용할 때 반드시 알아야 할 기본 사항들도 함께 제공합니다. 본 포스팅의 모든 코드 예제는 Video Effects SDK의 샘플 애플리케이션에서 찾을 수 있습니다.

Video 1. 자신만의 가상 배경 생성하기

효과의 생성과 구성

첫 번째 단계는 사용할 효과를 생성하는 것입니다. 이번 포스팅에서는 아티팩트 감소와 업스케일러를 다룹니다. NvVFX_CreateEffect 함수로 지정된 유형의 비디오 효과 필터 인스턴스를 생성할 수 있습니다. 이 함수는 효과 선택기(effect selector)를 요하며, 해당 효과의 핸들(handle)을 반환합니다. 효과 선택기는 생성할 효과를 선택할 수 있는 문자열을 의미합니다.

NvVFX_Handle _arEff;
NvVFX_Handle _upscaleEff;
NvVFX_EffectSelector first;
NvVFX_EffectSelector second;

NvVFX_CreateEffect(first, &_arEff);
NvVFX_CreateEffect(second, &_upscaleEff);

그런 다음 NvVFX_SetString 함수를 사용해 해당 기능의 모델 위치를 지정합니다.

NvVFX_SetString(_arEff, NVVFX_MODEL_DIRECTORY, modelDir);
NvVFX_SetString(_upscaleEff, NVVFX_MODEL_DIRECTORY, modelDir);

대부분의 Video Effects SDK 기능에는 모드가 있습니다. 앞서 논의한 바와 같이 이러한 모드는 기본적으로 동일한 효과를 두 가지 형태로 나누어 놓은 것입니다. 이때 아티팩트 감소 기능의 두 가지 모드는 NvVFX_SetU32 함수로 설정할 수 있습니다. 업스케일러의 경우 NvVFX_SetF32 함수로 0과 1 사이의 숫자를 설정하는 부동 소수점을 활용합니다.

int FLAG_arStrength = 0;
float FLAG_upscaleStrength= 0.2f;

NvVFX_SetU32(_arEff, NVVFX_STRENGTH, FLAG_arStrength);
NvVFX_SetF32(_upscaleEff, NVVFX_STRENGTH, FLAG_upscaleStrength);

CUDA 스트림 구성, 버퍼 할당, 모델 로드

효과를 생성한 다음 CUDA를 사용하고 모델을 로드하는 방법을 살펴보겠습니다. ‘CUDA 스트림’은 명령 순서를 엄수해 실행되는 연산의 집합입니다. 이 점을 염두에 두고 진행할 첫 단계는 스트림을 생성하는 것입니다. NvVFX_CudaStreamCreate 함수로 이 스트림을 생성할 수 있습니다.

CUstream _stream;
NvVFX_CudaStreamCreate(&_stream);

스트림이 생성되었으므로 효과를 할당합니다. 이 작업은 NvVFX_SetCudaStream 함수로 수행합니다.

NvVFX_SetCudaStream(_arEff, NVVFX_CUDA_STREAM, stream));
NvVFX_SetCudaStream(_upscaleEff, NVVFX_CUDA_STREAM, stream);

CUDA 스트림이 생성됐으면 데이터를 이동합니다. 본 예제의 경우에는 이미지 프레임을 이동하게 됩니다. GPU의 사용이 처음이라면 “데이터를 왜, 그리고 어디로 옮기는 것일까?”라는 의문이 생길 수 있는데요.

GPU는 일반적으로 자체 비디오 램(VRAM)을 가지고 있습니다. 이는 시스템의 머더보드(motherboard)에 꽂혀 있는 일반적인 램과 같습니다. 전용 VRAM의 주요 이점은 이 메모리에 저장된 데이터가 일반 램에 저장된 데이터보다 훨씬 빠르게 처리된다는 것입니다. 우리가 “데이터를 CPU 메모리에서 GPU 메모리로 옮긴다”고 할 때 실은 이 두 가지 유형의 램 사이에서 진행되는 메모리 전송을 뜻합니다.

Memory transfer between CPU and GPU memory goes both ways.
Figure 7. CPU 대 GPU 버퍼의 개괄

단일 효과를 사용하는 일반적 시나리오에서 이 전송은 용이하게 이뤄지며, 2개의 CPU 메모리 버퍼와 2개의 GPU 버퍼를 필요로 합니다. 두 경우 모두에서 칩 하나는 소스용이고 다른 하나는 처리된 프레임용입니다.

Memory transfer for single video effect with CPU and GPU memory for source and processed frame.
Figure 8. GPU와 CPU의 서로 다른 메모리 버퍼 사이에서 데이터 이동하기

두 개의 서로 다른 이미지 픽셀 레이아웃이 필요한 기능을 연결할 때는 레이어에 복잡성이 더해집니다. GPU에는 두 개의 버퍼가 더 있어야 하는데요. 하나는 첫 번째 효과의 아웃풋 프레임을 저장하고 다른 하나는 두 번째 효과의 인풋을 저장하는 용도입니다. Figure 9는 이 플로우를 보여줍니다. 함수명은 아직 신경 쓰지 마세요. 본 포스팅 후반부의 효과 실행하기 섹션에서 다룰 예정입니다.

Memory transfer for chained video effects with additional intermediate buffer layer.
Figure 9. 픽셀 형식을 고려해 GPU와 CPU의 서로 다른 메모리 버퍼 사이에서 데이터 이동하기

이처럼 고급 수준의 이해를 바탕으로 이제 파이프라인 설정 방법으로 넘어가보겠습니다. 파이프라인의 설정은 메모리를 할당한 뒤 인풋과 아웃풋 버퍼를 지정하는 2단계로 진행됩니다.

먼저 NvCVImage_Alloc 함수를 사용해 GPU 버퍼용 메모리를 할당합니다.

NvCVImage _srcGpuBuf;
NvCVImage _interGpuBGRf32pl;
NvCVImage _interGpuRGBAu8;
NvCVImage _dstGpuBuf;

// GPU Source Buffer
NvCVImage_Alloc(&_srcGpuBuf, _srcImg.cols, _srcImg.rows, NVCV_BGR, NVCV_F32, NVCV_PLANAR, NVCV_GPU, 1); 

// GPU Intermediate1 Buffer
NvCVImage_Alloc(&_interGpuBGRf32pl, _srcImg.cols, _srcImg.rows, NVCV_BGR, NVCV_F32, NVCV_PLANAR, NVCV_GPU, 1);

// GPU Intermediate2 Buffer
NvCVImage_Alloc(&_interGpuRGBAu8, _srcImg.cols, _srcImg.rows, NVCV_RGBA, NVCV_U8, NVCV_INTERLEAVED, NVCV_GPU, 32);

// GPU Destination Buffer
NvCVImage_Alloc(&_dstGpuBuf, _dstImg.cols, _dstImg.rows, NVCV_RGBA, NVCV_U8, NVCV_INTERLEAVED, NVCV_GPU, 32);

복잡한 함수처럼 보이지만, 고급 수준에서는 주어진 유형의 이미지 프레임에 이상적인 버퍼 유형에 따라 기본 파라미터를 지정할 수 있습니다. 예를 들어 이미지가 RGBA인지 여부, 각 구성 요소에 8 비트가 포함되어 있는지 여부, 해당 비트의 형식 등을 고려하게 됩니다. 자세한 내용은 Setting the Input and Output Image Buffers를 참고하세요.

다음으로 각 효과별로 생성한 인풋과 아웃풋 버퍼를 NvVFX_SetImage 함수로 지정합니다.

// Setting buffers for 
NvVFX_SetImage(_arEff, NVVFX_INPUT_IMAGE,  &_srcGpuBuf);
NvVFX_SetImage(_arEff, NVVFX_OUTPUT_IMAGE, &_interGpuBGRf32pl);

NvVFX_SetImage(_upscaleEff, NVVFX_INPUT_IMAGE, &_interGpuRGBAu8);
NvVFX_SetImage(_upscaleEff, NVVFX_OUTPUT_IMAGE, &_dstGpuBuf);

마지막으로 모델을 로드합니다. NvVFX_Load 함수도 동일한 기능을 수행합니다. 또한 효과를 위해 선택된 파라미터의 유효성을 검증합니다.

NvVFX_Load(_arEff);
NvVFX_Load(_upscaleEff);

효과 실행하기

이제 파이프라인이 설정되었으므로 효과를 실행할 수 있습니다. 프레임을 CPU/GPU 소스에서 해당 인풋 버퍼로 옮깁니다. NvCVImage_Transfer 함수를 사용해 프레임을 이동할 수 있으며, NvVFX_Run 함수로 효과를 실행합니다.

// Frame moves from CPU buffer to GPU src buffer
NvCVImage_Transfer(&_srcVFX, &_srcGpuBuf, 1.f/255.f, stream, &_tmpVFX);

// Running Artifact Reduction
NvVFX_Run(_arEff, 0);

// Frame moves from GPU intermediate buffer 1 to buffer 2
NvCVImage_Transfer(&_interGpuBGRf32pl, &_interGpuRGBAu8, 255.f, stream, &_tmpVFX);

// Running Upscaler
NvVFX_Run(_upscaleEff, 0));

// Frame moves from GPU destination buffer to CPU buffer
NvCVImage_Transfer(&_dstGpuBuf, &_dstVFX, 1.f, stream, &_tmpVFX));

첫 번째 시도에서는 다중의 과정이 진행되는 것처럼 보일 수 있지만 주요하게는 효과의 생성, CUDA 스트림의 구성과 데이터 플로우 관리, 효과의 실행이라는 3단계로만 구성됩니다.

Maxine의 세 가지 SDK인 Video Effects SDKAudio Effects SDK, Augmented Reality SDK는 서로 유사하게 설계됐습니다. 본 포스팅에서 살펴본 콘셉트를 경미하게 수정해 Audio Effects와 Augmented Reality SDK에 적용할 수 있죠.

Video Effects SDK를 여러분의 애플리케이션에 통합하세요

이번 포스팅에서 살펴본 바와 같이 Maxine Video Effects SDK가 제공하는 여러 AI 기능을 사용하면 노이즈가 심한 저해상도 비디오를 개선해 고품질 비디오를 생성할 수 있습니다. 또한 여러 효과를 연결해 하나의 비디오 파이프라인을 구축할 수도 있습니다. 이 시각 효과들을 여러분의 화상 회의와 스트리밍, 무선 통신 애플리케이션에 적용하려면 Maxine Getting Started page를 참고하세요. 의견이나 질문이 있을 경우 여기를 방문하세요.