Video-processing

YUV Format (+영상 처리 코드)

동누크 2022. 12. 26. 16:21

1. YUV Format?

  • YUV format 은 RGB(Red, Green, Blue) 3 원색의 format 과 손실없이 1:1 변환(mapping)
  • 빛의 밝기를 나타내는 휘도(Y)와 Chroma Components 로 불리는 2개의 색상 신호(U, V)로 구성한다.
  • 인간의 눈이 색상신호보다 밝기 신호에 민감한 눈의 인지 원리를 이용한다.
  • 밝기를 담당하는 Y sample 은 모두 취하고, 상대적으로 둔감한 색상을 담당하는 U 나 V sample 은 4 개의 픽셀에서 1 개 또는 2 개의 픽셀만 취급하여 저장할 비디오의 용량을 줄일 수 있다.

1-1. YUV Format 종류

  • YUV format 은 Y, U(Cb), V(Cr)의 비율을 어떻게 하냐에 따라, 즉 샘플링 비율에 따라 YUV 444, YUV 422, YUV 420, YUV 411과 같이 이름을 명명
  • Y 가 4 바이트 올 때, U 와 V 가 각각 4 바이트가 오는 경우에는 YUV 444, 각각 2 바이트씩 가지는 경우에는 YUV 422, 각각 1 바이트씩 가지는 경우에는 YUV 411 로 명시한다.
  • 밑의 예제 코드에서는 YUV 420 format 동영상을 사용. YUV 420은 바이트 크기 상으로는 볼 때 YUV 411 format과 유사하지만 U, V sample의 배치할 때 차이가 발생한다.
  • YUV 420 format은 안드로이드 camera 클래스에서 제공되는 YUV format 으로 일반적으로 사용한다.
  • YUV 420 format 또한 앞서 설명한 포맷들과 유사하게 Y 값은 온전히 표현하되 U, V 정보를 줄여 데이터의 양을 줄이게 된다. Y 값을 가로와 세로의 곱한 크기만큼 읽고, U, V 는 Y 값의 1/4 만큼 읽어서 하나의 프레임 단위를 구성한다.

1-2. YUV format의 특징

  • YUV format 의 경우 파일의 정보를 표시하는 ‘헤더’가 별도로 존재하지 않는다. 앞서 설명한 방법으로 Y, U, V 값을 읽어 들여 아래 그림(YUV 420 format)과 같은 구조를 가지게 되고, 바이트 값을 통해 하나의 프레임(사진)을 구분할 수 있다.

2. YUV Format 실습

2-1. YUV Player

  • Sourceforge사이트를 통해 YUV Player를 다운로드받을 수 있다.

  • YUV Player를 이용하면 다음과 같이 영상을 재생할 수 있다.

2-2. 홀수 프레임 추출

  • 우리는 YUV format의 구조를 이해하기 위해 첫 번째로 YUV 420 format 영상을 이용하여 홀수 프레임만 추출해볼 것이다.
  • 주어진 30장의 YUV 파일에서 홀수 번째 Picture만 따로 추출하기 위해 한 프레임(픽쳐)의 크기를 먼저 구했고, 2개의 파일 포인터를 이용하여 하나의 파일 포인터는 Original 파일을 한 프레임의 크기만큼 읽어 들였고, 다른 하나의 파일 포인터는 Target 파일에 홀수 번째 프레임인 경우에만 저장했다.
  • 먼저 한 프레임의 크기를 구하기 위해 YUV 420 format의 데이터 구조를 알아볼 필요가 있다. YUV format에서 한 프레임의 경우 Y는 가로와 세로의 곱만큼의 크기를 갖고, U와 V는 가로와 세로의 곱의 크기의 1/4만큼 각각 갖게 된다. 따라서 한 프레임의 크기는 가로와 세로를 곱한 크기의 3/2배를 한 값을 갖게 된다. 따라서 아래 그림과 같이 버퍼를 할당했다.

  • 이후 반복문을 통해 Original 파일을 읽고, 홀수 번째 프레임의 경우에만 해당 버퍼를 Target 파일에 저장하여 홀수 번째 프레임만 추출한다.

  • 코드를 실행시켰을 때 아래 그림과 같이 정상적으로 홀수 번째 프레임만 추출되어 15개의 프레임만 실행되는 것을 확인할 수 있다. (전체 소스 코드는 글 말미에 첨부)

2-3. Y-Picture 추출

  • YUV 420 format에서 한 프레임(픽쳐) 내에서 Y sample이 차지하는 크기는 가로와 세로를 곱한 크기이고 이 값을 N이라고 칭한다면, 하나의 파일 포인터를 이용하여 매 프레임에서 N만큼 읽은 후 다른 하나의 파일 포인터를 통해 Target 파일에 저장한다. 이후 U, V에 해당하는 바이트 값인 N/2 값을 읽을 때에는 저장하지 않고 넘어간다.

  • 첨부된 코드와 같이 U, V에 해당하는 길이 만큼을 0으로 초기화하여 Target 파일에 저장한다. 해당 길이의 값을 0으로 초기화하여 값을 저장했을 때 다음 그림과 같이 Y picture에 해당하는 프레임만 추출된다.

  • 색상을 담당하는 U, V 값에 0으로 값이 초기화되었기에 초록색 화면으로 영상이 실행되었음을 알 수 있다. U, V 값을 제거하는 구현 과제와는 다르지만 흑백 화면으로 실행시키기 위해 U, V 값을 다음 코드와 같이 수정하여 실행했을 경우 흑백 화면이 나오는 것도 확인할 수 있다.

3. 소스 코드

3-1. 홀수 프레임 추출

#include <stdio.h>
#include <stdlib.h>
int main() {
    FILE *rf = NULL;
    FILE *wf = NULL;
    rf = fopen("./PeopleOnStreet_1280x720_30_Original.yuv", "rb");
    wf = fopen("./PeopleOnStreet_1280x720_15_OddFrames.yuv", "wb");

    int frame_size = 1280 * 720 * 3/2;
    unsigned char* frame = (unsigned char*)malloc(frame_size);
	
    int i = 0;
    for (i=0; i<30; i++) {
        if (i%2 == 0) {
            memset(frame, 0, frame_size);
            fread(frame, sizeof(unsigned char), frame_size, rf);
            fwrite(frame, sizeof(unsigned char), frame_size, wf);
        } else {
            fread(frame, sizeof(unsigned char), frame_size, rf);
            memset(frame, 0, frame_size);
        }
    }
    fclose(rf);
    fclose(wf);
}

3-2. Y-Picure 추출

#include <stdio.h>
#include <stdlib.h>
int main() {
    FILE *rf = NULL;
    FILE *wf = NULL;
    rf = fopen("./PeopleOnStreet_1280x720_30_Original.yuv", "rb");
    wf = fopen("./PeopleOnStreet_1280x720_30_YSamples.yuv", "wb");

    int frame_size = 1280 * 720;
    unsigned char* frame = (unsigned char*)malloc(frame_size);

    int i = 0;
    for (i=0; i<30; i++) {
        memset(frame, 0, frame_size);
        fread(frame, sizeof(unsigned char), frame_size, rf);
        fwrite(frame, sizeof(unsigned char), frame_size, wf);
        fread(frame, sizeof(unsigned char), frame_size/2, rf);
        memset(frame, 0, frame_size);
        fwrite(frame, sizeof(unsigned char), frame_size/2, wf);
    }
    fclose(rf);
    fclose(wf);
}