DJI 드론 생방송 영상의 YUV 영상을 RGB 영상으로 변환

이 기사에서는 DJI 드론의 영상 스트리밍에서 얻은 YUV 이미지의 RGB 변환을 소개한다.

DJI 무인기 실황 녹음 영상의 YUV 영상 획득


참고스트리밍에서 YUV 이미지 가져오기는 DJI Mobile SDK로 무인항공기의 영상 흐름 전송으로 YUV 이미지를 얻었다.
    public void onYuvDataReceived(MediaFormat format, 
                                  final ByteBuffer yuvFrame, 
                                  int dataSize, 
                                  final int width, 
                                  final int height) {
// The obtained data of format is MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar 
// and the width and height must be even.
    }

이른바 YUV


이른바 YUV, YCbCr, YPbPr, 밝기 신호 Y와 두 개의 색차 신호로 표시된 색 공간.

왜 YUV 포맷?


인간의 눈은 밝기의 변화에 민감하지만 색의 변화에 둔감하기 때문에 색도를 억제하고 밝기를 통해 더 넓은 주파수와 비트수를 분배함으로써 적은 손실과 높은 효율의 전송과 압축 형식을 실현한다.

RGB 및 YCbCr


RGB 방식에서는 레드, 그린, 블루 등 3가지 원색을 섞어 색을 표현하는 방법이다.
YCbCr 방식에서는 밝기 성분의 Y와 파란색에서 밝기를 뺀 파란색 차 Cb(B-Y), 빨간색에서 밝기를 뺀 빨간색 차 Cr(R-Y) 3가지로 색을 표현한다.
RGB와 YCbCr는 다음 계산 공식을 통해 서로 변환할 수 있다
RGB to YUV
Y =  0.299R + 0.587G + 0.114B
U = -0.169R - 0.331G + 0.500B
V =  0.500R - 0.419G - 0.081B
YUV to RGB
R = 1.000Y          + 1.402V
G = 1.000Y - 0.344U - 0.714V
B = 1.000Y + 1.772U
밝기 Y에는 G 성분이 많이 들어있고 B 성분은 매우 적다.사람의 눈에는 녹색 G는 밝고 파란색 B는 어둡기 때문에 이런 전환식이 된다.

YUV 형식


동영상 업계 전체가 다양한 YUV 형식을 정의했다.
MSDN 각 형식에 대한 설명을 시작한다.
색도 채널(색차 Cb/Cr)은 감지 품질을 크게 잃지 않고 루미난 채널(밝기 Y)의 샘플링 확률보다 낮을 수 있다.
루미난스의 견본은 교차로 표시한다
색상 채널(Cb/Cr)의 샘플은 원으로 표시됩니다.
  • 4:4:4는 색도 채널이 샘플링을 낮추지 않았음을 나타낸다.
  • 4:2:2는 2대1의 수평에서 샘플을 채취하는 것을 의미하며 수직에서 샘플을 채취하는 것을 사용하지 않는다.
  • 4:2:0은 2:1은 수직으로 샘플을 채취하고 2:1의 수평으로 샘플을 채취하는 것을 말한다.
  • 4:1:1은 수직 샘플링을 사용하지 않고 4:1의 수평 샘플링을 의미한다.


  •     4:4:4 (32 bpp)
            AYUV
        4:2:2 (16 bpp)
            YUY2
            UYVY
        4:2:0 (16 bpp)
            IMC1
            IMC3
        4:2:0 (12 bpp)
            IMC2
            IMC4
            YV12
            NV12
    

    4:4:4 형식, 32비트/픽셀


    AYUV
    모든 픽셀은 알파 값을 포함한 네 개의 연속 바이트로 인코딩된다.최고 품질의 화질.

    4:2:2 형식, 16비트/픽셀


    매 매크로 픽셀은 두 개의 픽셀이고, 두 픽셀은 네 개의 연속 바이트로 인코딩된다.그 결과 포화도 수준이 떨어지면 샘플링이 2배나 된다.
    YUY2
    데이터를 기호가 없는char값으로 배열 처리할 수 있습니다.첫 번째 바이트는 첫 번째 y 샘플을, 두 번째 바이트는 첫 번째 U(Cb) 샘플을, 세 번째 바이트는 두 번째 y(Cr) 샘플을 포함한다.

    UYVY
    YUY2와 같은 형식으로, 아르바이트 순서가 반전되는 지점이 있다.

    4:2:0 형식, 픽셀당 16비트


    두 개의 4:2:016비트/픽셀(bpp) 형식으로 물색 채널은 수평방향과 수직방향의 두 차원에서 두 개의 계수로 샘플링을 진행한다.
    IMC1
    모든 Y 견본은 메모리에서 먼저 기호가 없는 char 값의 배열로 표시됩니다.이후 모든 V(Cr) 샘플과 모든 U(Cb) 샘플은 계속된다.

    Stride는 화면 너비(바이트 단위)입니다.
    BYTE* pV = pY + (((Height + 15) & ~15) * Stride);
    BYTE* pU = pY + (((((Height * 3) / 2) + 15) & ~15) * Stride);
    
    IMC3
    이 형식은 IMC1과 마찬가지로 U 및 V의 평면 교환 점이 다릅니다.

    4:2:0 형식, 픽셀당 12비트


    4개의 4:2:012-bpp 형식으로 채널 채널은 수평방향과 수직방향의 두 차원에서 2개의 계수로 서브샘플링을 한다.
    IMC2
    다음과 같은 형식을 제외하고 IMC1과 같습니다.V(Cr)와 U(Cb)의 행은 반stride 경계로 교차합니다.IMC1보다 주소 공간이 더 효율적입니다.

    IMC4
    IMC2와 마찬가지로 U(Cb) 및 V(Cr) 행이 서로 바뀝니다.

    YV12
    V 평면의 선은 Y 평면의 선의 절반입니다.V 평면에는 Y 평면의 절반 수량의 선이 포함됩니다.V 평면 다음에 V 평면과 같은 강도와 선 수를 가진 모든 U(Cb) 샘플이 나옵니다.


    NV12
    Y 평면에 이어 포장된 U(Cb) 샘플과 V(Cr) 샘플을 포함한 무기호 char 값이 배열됐다.



    YUV 포맷에 대한 자세한 설명

    부분 코드 변환

    #define CLIP(x) do{if(x < 0){x = 0;} else if(x > 255){x = 255;}} while(0)
    #define CONVERT_R(Y, V)    ((298 * (Y - 16) + 409 * (V - 128) + 128) >> 8)
    #define CONVERT_G(Y, U, V) ((298 * (Y - 16) - 100 * (U - 128) - 208 * (V - 128) + 128) >> 8)
    #define CONVERT_B(Y, U)    ((298 * (Y - 16) + 516 * (U - 128) + 128) >> 8)
    
    void NV12ToRGB(u8* rgbBuffer, u8* yuvBuffer, int width, int height)
    {
        u8* uvStart = yuvBuffer + width * height;
        u8 y[2] = { 0, 0 };
        u8 u = 0;
        u8 v = 0;
        int r = 0;
        int g = 0;
        int b = 0;
        for (int rowCnt = 0; rowCnt < height; rowCnt++)
        {
            for (int colCnt = 0; colCnt < width; colCnt += 2)
            {
                u = *(uvStart + colCnt + 0);
                v = *(uvStart + colCnt + 1);
    
                for (int cnt = 0; cnt < 2; cnt++)
                {
                    y[cnt] = yuvBuffer[rowCnt * width + colCnt + cnt];
    
                    r = CONVERT_R(y[cnt], v);
                    CLIP(r);
                    g = CONVERT_G(y[cnt], u, v);
                    CLIP(g);
                    b = CONVERT_B(y[cnt], u);
                    CLIP(b);
                    rgbBuffer[(rowCnt * width + colCnt + cnt) * 3 + 0] = (u8)r;
                    rgbBuffer[(rowCnt * width + colCnt + cnt) * 3 + 1] = (u8)g;
                    rgbBuffer[(rowCnt * width + colCnt + cnt) * 3 + 2] = (u8)b;
                }
            }
    
            uvStart += width * (rowCnt % 2);
        }
    }
    

    좋은 웹페이지 즐겨찾기