API를 사용해 다중의 비디오 효과 기능 연결하기
효과를 다중으로 연결하면 여러 애플리케이션에서 흥미로운 결과를 낼 수 있습니다. 본 포스팅은 서로 잘 어울리는 두 가지 효과, 아티팩트 감소와 업스케일러를 연결하는 방법을 집중적으로 살펴봅니다. 또 다른 사례는 노이즈가 심한 웹캠 스트림의 처리를 위해 비디오 잡음 제거와 슈퍼 해상도 또는 업스케일러를 실행하는 것입니다. 여러분의 활용 사례에 가장 적합한 효과를 선택해 사용할 수 있습니다.
API와 그 사용법에 대한 더 자세한 내용은 다음과 같습니다. Figure 6은 Video Effects SDK의 여러 기능을 활용한 고급 프로세스를 보여줍니다.
- 효과의 생성과 구성
- CUDA 스트림 구성, 버퍼 할당, 모델 로드
- 데이터 로드와 효과 실행
다음의 영상은 앞서 설명한 고급 기능의 플로우를 보여줍니다. 그러나 이 프로세스에는 다양한 세부 내용이 있으며, 이와 관련해서는 본 포스팅의 후반부에서 논하도록 하겠습니다. 이 영상은 또한 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 메모리로 옮긴다”고 할 때 실은 이 두 가지 유형의 램 사이에서 진행되는 메모리 전송을 뜻합니다.
단일 효과를 사용하는 일반적 시나리오에서 이 전송은 용이하게 이뤄지며, 2개의 CPU 메모리 버퍼와 2개의 GPU 버퍼를 필요로 합니다. 두 경우 모두에서 칩 하나는 소스용이고 다른 하나는 처리된 프레임용입니다.
두 개의 서로 다른 이미지 픽셀 레이아웃이 필요한 기능을 연결할 때는 레이어에 복잡성이 더해집니다. GPU에는 두 개의 버퍼가 더 있어야 하는데요. 하나는 첫 번째 효과의 아웃풋 프레임을 저장하고 다른 하나는 두 번째 효과의 인풋을 저장하는 용도입니다. Figure 9는 이 플로우를 보여줍니다. 함수명은 아직 신경 쓰지 마세요. 본 포스팅 후반부의 효과 실행하기 섹션에서 다룰 예정입니다.
이처럼 고급 수준의 이해를 바탕으로 이제 파이프라인 설정 방법으로 넘어가보겠습니다. 파이프라인의 설정은 메모리를 할당한 뒤 인풋과 아웃풋 버퍼를 지정하는 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 SDK와 Audio Effects SDK, Augmented Reality SDK는 서로 유사하게 설계됐습니다. 본 포스팅에서 살펴본 콘셉트를 경미하게 수정해 Audio Effects와 Augmented Reality SDK에 적용할 수 있죠.
Video Effects SDK를 여러분의 애플리케이션에 통합하세요
이번 포스팅에서 살펴본 바와 같이 Maxine Video Effects SDK가 제공하는 여러 AI 기능을 사용하면 노이즈가 심한 저해상도 비디오를 개선해 고품질 비디오를 생성할 수 있습니다. 또한 여러 효과를 연결해 하나의 비디오 파이프라인을 구축할 수도 있습니다. 이 시각 효과들을 여러분의 화상 회의와 스트리밍, 무선 통신 애플리케이션에 적용하려면 Maxine Getting Started page를 참고하세요. 의견이나 질문이 있을 경우 여기를 방문하세요.