Deeplearning Inference

Deep Learnig C++ Inference on Jetson Nano (2)

dlbuilder 2020. 10. 18. 20:48

이번 블로그에서는 Jetson에서 딥러닝 모델의 인퍼런스를 위해 반드시 필요한 PreProcessing, PostProcessing, Rendering에서 좋은 성능을 낼 수 있는 방법을 소개드리고자 합니다.

대부분의 인터넷 자료들은 손쉬운 방법으로 OpenCV를 많이 사용합니다.

하지만 Jetson에는 고성능의 Jetson Linux Multimedia API가 포함되어 있습니다.

제가 실제 구현을 해보면 Jetson Linux Multimedia API를 활용하는 방법이 훨씬 좋은 성능을 보이는 것을 확인할 수 있었습니다.

 

이를 위해 이번 시간에는 Jetson Linux Multimedia API sample 중 위 목적에 가장 부합한 예제 12_camera_v4l2_cuda(https://docs.nvidia.com/jetson/l4t-multimedia/l4t_mm_v4l2_cam_cuda_group.html)를 변형하여 Webcam 영상을 입력받은후 여기에 AlphaBlending 효과를 주고 이를 화면에 표출하는 예제를 만들어보고자 합니다.

 

Jetson Linux Multimedia API에 대한 상세한 사항은 다음 링크를 참조해주시기 바랍니다.

docs.nvidia.com/jetson/l4t-multimedia/index.html

 

Jetson Linux Multimedia API Reference: Main Page

Welcome to the NVIDIA Jetson Linux Multimedia API Reference. Documentation is preliminary and subject to change. Multimedia API is a collection of lower-level APIs that support flexible application development. The lower-level APIs enable flexibility by pr

docs.nvidia.com

1. Webcam 정보 분석

  • Webcam 연결 (저는 Logitech C920 모델을 연결하여 사용합니다.)
  • v4l-utils 설치
sudo apt-get install v4l-utils
  • Webcam 정보 조회
v4l2-ctl --list-formats-ex

- 예제를 본격적으로 실행하기 전에 어떤 포맷으로 입력을 받을지 결정이 필요

- 위 명령을 실행하면 카메라가 지원가능한 Pixel Format, 해상도, frame rate 정보를 확인할 수 있음

- 대부분의 카메라가 YUYV, MJPG는 지원하고 있으며, 일부 카메라는 H264 포맷도 지원 (c920은 H264를 지원)

- YUYV는 고해상도로 캡쳐하는 경우 지원가능한 frame rate가 떨어지는 것을 볼 수 있음

   (c920 카메라의 경우 1920x1080일 경우 YUYV로 5fps만 지원)

- 저는 위 제반사항을 고려하여 범용성과 고해상도에서 30fps를 지원하는 MJPG를 입력 포맷으로 결정하고 진행하겠습니다.

2.  Jetson Linux Multimedia API 예제 빌드 및 실행

  • 예제 위치로 이동 (12_camera_v4l2_cuda)
cd /usr/src/jetson_multimedia_api/samples/12_camera_v4l2_cuda
  • 빌드
sudo make
  • MJPG 캡쳐 및 화면에 표출
./camera_v4l2_cuda -d /dev/video0 -s 1920x1080 -f MJPEG -c
  • 예제의 전체적인 flow 설명 (주요 로직은 camera_v4l2_cuda.cpp의 606행 while안에서 이루어짐)

- v4l2를 활용하여 Webcam 영상을 캡쳐

- 캡쳐된 MJPEG 영상 디코딩 (ctx->jpegdec->decodeToFd)

- 디코딩된 영상을 화면 표출 위한 Render Buffer(render_dmabuf_fd)로 변환

  (NvBufferTransform(fd, ctx->render_dmabuf_fd, &transParams))

- Render Buffer를 Cuda Interop (cuda_postprocess) (-c 옵션을 주면 화면 좌상단에 검정색  사각형이 나타나는데, 이 부분이 캡쳐된 영상을 cuda와 interop 시켜서 영상에 사각형을 overlap 해준 것임)

- EGL Renderer로 화면에 Render Buffer 표출 (ctx->renderer->render(ctx->render_dmabuf_fd))

 

3. AlphaBlending 예제로 변환

앞서 camera_v4l2_cuda.cpp의 cuda_postprocess 함수에서 cuda interop을 수행한다고 했습니다.

향후 Inference를 위해서는 위 과정처럼 캡쳐 및 디코딩된 영상을 cuda와 연결하여 다양한 Preprocessing, PostProcessing을 수행하고 이를 바로 Inference 또는 Renderer로 전송하게 될 것입니다.

이 과정은 CPU가 아닌 모두 GPU 내에서 이루어지는 과정으로 이렇게 진행하는 것이 Inference 성능을 가장 최적화 할 수 있는 방법이라고 생각됩니다.

지금은 캡쳐된 영상을 cuda와 연결하여 영상에 alphablending을 수행하는 것을 진행해보겠습니다.

 

수정이 필요한 소스는 다음과 같습니다.

camera_v4l2_cuda.cpp (/usr/src/jetson_multimedia_api/samples/12_camera_v4l2_cuda)

NvCudaProc.h, NvCudaProc.cpp, NvAnalysis.h, NvAnalysis.cu (/usr/src/jetson_multimedia_api/samples/common/algorithm/cuda)

 

변경된 부분은 첨부드린 소스코드를 참고하시면 됩니다. (//changed by DL Builder라고 검색하시면 변경부분들이 검색됩니다.)

 

원래 소스에서는 Render Buffer가 YUV420으로 되어 있으나, 변경된 소스에서는 RGBA32로 하였습니다.

향후 Inference에서 영상위에 Rect, Text 등을 overlay해야 하는데, 이를 지원하는 포맷이 RGBA32 이기 때문입니다.

Rect, Text 등의 overlay 방법은 향후 설명드리겠습니다.

 

input_params.payloadType = NvBufferPayload_SurfArray;
input_params.width = ctx->cam_w;
input_params.height = ctx->cam_h;
input_params.layout = NvBufferLayout_Pitch;
input_params.colorFormat = get_nvbuff_color_fmt(V4L2_PIX_FMT_RGB32); //changed by DL Builder
input_params.nvbuf_tag = NvBufferTag_NONE;

/* Create Render buffer */
if (-1 == NvBufferCreateEx(&ctx->render_dmabuf_fd, &input_params))
    ERROR_RETURN("Failed to create NvBuffer");

 

여기서 강조드리고 싶은 부분은 NvCudaProc.cpp의 아래 함수 부분과 같이 MultiMedia API의 Render Buffer와 Cuda를 interop 해서 사용한다는 것입니다.

 

static void Handle_AlhapBlending(EGLImageKHR image, int width, int height) //changed by DL Builder
{
    CUresult status;
    CUeglFrame eglFrame;
    CUgraphicsResource pResource = NULL;

    cudaFree(0);
    status = cuGraphicsEGLRegisterImage(&pResource, image,
                CU_GRAPHICS_MAP_RESOURCE_FLAGS_NONE);
    if (status != CUDA_SUCCESS)
    {
        printf("cuGraphicsEGLRegisterImage failed: %d, cuda process stop\n",
                        status);
        return;
    }

    status = cuGraphicsResourceGetMappedEglFrame(&eglFrame, pResource, 0, 0);
    if (status != CUDA_SUCCESS)
    {
        printf("cuGraphicsSubResourceGetMappedArray failed\n");
    }

    status = cuCtxSynchronize();
    if (status != CUDA_SUCCESS)
    {
        printf("cuCtxSynchronize failed\n");
    }

    if (eglFrame.frameType == CU_EGL_FRAME_TYPE_PITCH)
    {
        AlphaBlending((CUdeviceptr) eglFrame.frame.pPitch[0], width, height);
    }

    status = cuCtxSynchronize();
    if (status != CUDA_SUCCESS)
    {
        printf("cuCtxSynchronize failed after memcpy\n");
    }

    status = cuGraphicsUnregisterResource(pResource);
    if (status != CUDA_SUCCESS)
    {
        printf("cuGraphicsEGLUnRegisterResource failed: %d\n", status);
    }
}

 

실제 Interop을 수행한 상태에서 NvAnalysis.cu의 다음 부분에서 cuda kernel 함수가 실행이 되면 Render Buffer에 AlphaBlending이 기록되게 됩니다.

 

__global__ void AlphaBlending_Kernel(uint8_t *pDevPtr, int width, int height) //changed by DL Builder
{
    int x = threadIdx.x + blockIdx.x * blockDim.x;
    int y = threadIdx.y + blockIdx.y * blockDim.y;

    if (x >= width || y >= height)
        return;

    float srcAlpha = 0.6;

    //Blended Color R: 255, G: 160, B: 122
    pDevPtr[4 * x + y * width * 4] = srcAlpha * 255 + (1 - srcAlpha) * pDevPtr[4 * x + y * width * 4];
    pDevPtr[4 * x + 1 + y * width * 4] = srcAlpha * 160 + (1 - srcAlpha) * pDevPtr[4 * x + 1 + y * width * 4];
    pDevPtr[4 * x + 2 + y * width * 4] = srcAlpha * 122 + (1 - srcAlpha) * pDevPtr[4 * x + 2 + y * width * 4];
}

 

첨부드린 소스 파일 들로 원래 예제의 파일들을 교체하신 후에,

/usr/src/jetson_multimedia_api/samples/12_camera_v4l2_cuda 경로에서

다시 sudo make로 빌드하시고,

다음 명령으로 실행하시면 화면 전체에 영상이 AlphaBlending된 것을 확인하실 수 있습니다.

./camera_v4l2_cuda -d /dev/video0 -s 1920x1080 -f MJPEG -c

 

다음 블로그부터는 본격적으로 Tensorflow로 학습된 모델을 첫번째 블로그에서 빌드된 Tensorflow c++ 라이브러리와 본 블로그의 Multimedia API를 활용하여 Inference하는 방법을 소개드리겠습니다.

 

첨부 : 소스 파일 링크

drive.google.com/file/d/18p8zn7x9cNZ1YuydaKC4Vff1AmKegE7q/view?usp=sharing

 

camera_v4l2_cuda.cpp

 

drive.google.com

drive.google.com/file/d/1Rha3hol53i90WdgkpuTsabFApffIVlPz/view?usp=sharing

 

NvAnalysis.cu

 

drive.google.com

drive.google.com/file/d/1OZ2MMgHB0ddxo0iG5duzU5BYtM4M-XcQ/view?usp=sharing

 

NvAnalysis.h

 

drive.google.com

drive.google.com/file/d/1EUULGZ6Va-7uCFM94QPi7LkLFx1k7Nz6/view?usp=sharing

 

NvCudaProc.cpp

 

drive.google.com

drive.google.com/file/d/1AXuuwqXO5QYeRJJiWIJ9VxhUNpI3W1br/view?usp=sharing

 

NvCudaProc.h

 

drive.google.com

 

딥러닝 학습모델의 c++ inference에 관심이 있으신분은, 다음 링크(dlbuilder.ai)를 참조해주십시요.
학습완료된 딥러닝 모델을 application으로 손쉽게 변환해주는 서비스를 제공하고 있습니다.