'개발/Image Processing'에 해당되는 글 18건

  1. 2008.10.23 DCT with JPEG
  2. 2008.10.05 YUV의 종류
  3. 2008.09.29 BMP 파일에서 RGB 데이터 보기
  4. 2008.09.29 YUV -> RGB 변환
  5. 2008.09.29 컬러 모델
2008. 10. 23. 11:49

DCT with JPEG


JPEG

JPEG(보통 ‘제이펙’이라고 읽는다.)은 높은 압축 효율 때문에 GIF와 함께 인터넷에서 가장 널리 쓰이는 그래픽 파일 포맷이다. JPEG은 Joint Photographic Experts Group이라는 위원회에서 정한 그래픽 이미지 압축에 관한 표준을 의미한다. JPEG이라는 이름도 이 위원회의 이름에서 유래한 것이다. (이와 비슷하게 동화상 압축에 관한 표준이 만들어졌는데 그것이 바로 MPEG이다. MPEG은 Motion Picture Experts Group에서 만든 표준이다.) JPEG 표준은 ISO(국제표준화기구) 10918-1에 정의되어 있다. JPEG 표준은 그래픽 이미지 압축에 대한 표준만 정해놓았을 뿐 구체적인 구현 방법은 정의하고 있지 않다. 우리가 지금까지 살펴본 BMP, PCX, GIF 그래픽 파일들은 어떤 헤더 구조를 가지고 있으며 그래픽 정보를 어떻게 저장하는지 각 그래픽 파일 규격에 구체적으로 정의되어 있다. 하지만 JPEG 표준은 그래픽 파일 포맷에 대한 규격은 전혀 언급하고 있지 않다. 심지어 어떤 방식의 색상 체계를 쓰는지도 정의되어 있지 않다. 구체적으로 어떤 색상 체계를 사용하며 어떤 파일 포맷으로 그래픽 이미지를 저장하는지에 대한 규격은 JFIF(JPEG File Interchange Format)에 정의되어 있다. JFIF는 C-Cube Microsystems의 Eric Hamilton에 의해 만들어졌으며 누구나 사용할 수 있도록 public domain에 공개되어 있다. 우리가 흔히 접하는 확장자가 JPG인 JPEG 그래픽 파일은 대부분 JFIF로 되어있다. 사실 JFIF는 JPEG 위원회나 어떤 기구에 의해 표준으로 정해진 포맷은 아니다. 하지만 시간이 가면서 널리 쓰이다 보니 표준 아닌 표준이 되어 버렸다. JPEG 위원회에서 뒤늦게 SPIFF(Still Picture Interchange File Format)라는 파일 포맷 표준을 내놓았지만 이미 수 많은 JPEG 이미지 파일들이 JFIF로 되어 있기 때문에 그렇게 각광을 받지는 못하고 있다. 여전히 수 많은 그래픽 소프트웨어들이 JPEG 이미지 파일로 JFIF를 사용하고 있다. 우리가 살펴볼 JPEG 이미지 파일도 JFIF이다.

JPEG의 종류 :

ISO10918-1 표준은 그림 1과 같이 압축 방식, 코딩 방식, 샘플링 크기 등에 따라 JPEG 모드를 여러 개로 나누어 놓았다.

JPEG

Sequential

Progressive

Lossless

Hierarchical

Huffman

Arithmetic

Huffman

Arithmetic

Original

Lossless

JPEG-LS

8 bit

12bit

8 bit

12bit

8 bit

12bit

8 bit

12bit

그림 1 여러 가지 JPEG 모드

Sequential 방식은 그래픽 이미지 맨 위부터 아래로 순차적으로 데이터를 저장하는 방식이고 Progressive 방식은 GIF의 Interlaced 모드와 비슷하게 그래픽 이미지를 여러 번 스캔하여 점진적으로 이미지가 뚜렷하게 보이도록 하는 방식이다. GIF의 Interfaced 모드는 몇 줄씩 건너뛰면서 여러 번에 걸쳐 그래픽 이미지를 출력한다. 매번 출력 때마다 그 전에 비어있던 줄을 채워 넣어 그래픽 이미지를 완성한다. JPEG progressive 방식도 여러 번에 걸쳐 그래픽 이미지를 출력하지만 GIF처럼 그림의 일부만 출력하는 것이 아니라 일단 전체 이미지를 모두 출력한다. 단, 마치 포커스가 제대로 맞지 않은 사진처럼 흐릿한 이미지만 출력한다. 그 다음 출력 때 점점 포커스가 맞추어지는 것처럼 보다 더 명확한 이미지를 출력한다. GIF의 경우와 마찬가지로 전체 데이터를 다 전송하지 않고도 어떤 이미지인지 알 수 있게 하기 위해 이런 방식을 사용한다. Hierarchical 방식은 Progressive 방식의 보다 더 진보된 형태이다. 같은 그래픽 이미지를 표현하고 있지만 화질이 서로 다른 이미지를 frame 단위로 여러 개 저장하는 방식이다. 전송 속도가 느린 경우 원하는 화질의 이미지 frame만 전송할 수 있다.

Lossless 방식은 말 그대로 화질 저하가 없는 방식이다. 뒤에 설명하겠지만 JPEG은 높은 압축률을 구현하기 위해 사람 눈이 구분하지 못할 정도로 미세하게 데이터를 생략하는 방식을 사용한다. 그래서 BMP 또는 GIF 그래픽 이미지에서 JPEG 이미지로 변환을 하면 약간의 화질 저하가 있다. BMP --> PCX --> BMP 변환을 아무리 여러 번 수행해도 화질의 변화가 없지만 BMP --> JPEG --> BMP 변환을 수행하면 할수록 점점 화질 저하가 누적된다. Lossless 방식은 전혀 압축을 하지 않는 대신 화질의 변화를 주지 않는 방식이다. JPEG-LS 역시 화질의 변화 없이 저장하는 방식인데 원래 Lossless 방식과는 구현 방법이 다르다.

GIF 그래픽 파일에서 LZW 코딩 방식으로 데이터를 압축 저장하듯이 JPEG도 그래픽 데이터를 압축해서 저장하기 위해 Huffman 코딩이라는 압축 방식을 사용한다. Arithmetic 방식은 구현 방법만 다를 뿐 Huffman 코딩 방식과 똑같은 코딩 방식이다. Arithmetic 방식은 특허로 보호 받기 때문에 함부로 사용할 수 없다.

또한 JPEG 그래픽 이미지는 각 픽셀의 색상을 8비트 또는 12비트로 표현할 수 있다. 우리가 흔히 접하는 JPG 파일은 8비트로 색상을 표현한다.

이렇게 많은 종류의 JPEG 모드 가운데 가장 널리 사용되는 방식은 Sequential – Huffman – 8비트 방식이며 Progressive - Huffman - 8비트 방식의 JPEG 파일도 많이 쓰인다.

JPEG 파일의 원리 :

JPEG 파일, 엄밀하게는 JFIF 포맷의 원리와 구조에 대해 간단하게 알아보자. JPEG 파일은 그림 2와 같은 과정을 거쳐 압축이 된다.

그림 2 JPEG 압축 과정

Sampling은 RGB 색상 체계를 Y-Cb-Cr 색상 체계로 변환하는 과정이다. 이때 Cb, Cr 성분을 구할 때 일부 성분들은 생략되므로 압축의 효과를 가져온다. DCT는 샘플링된 데이터를 코사인 함수의 합으로 변환하는 과정이다. 이렇게 변환된 데이터에서 원래 데이터를 크게 변화시키지 않을 만큼 불필요한 성분들을 제거한다. 이 과정을 양자화(quantization)이라고 한다. 양자화를 거처 압축된 데이터는 Huffman 코딩이라는 압축 알고리즘을 통해 압축이 된다. 양자화 과정에서 한번 압축이 일어나고 Huffman 코딩에서 또 한번 더 압축이 일어나 압축 효율이 극대화되는 것이다. 양자화에 의한 압축은 원래 데이터를 일부 제거하기 때문에 JPEG 이미지의 화질 저하의 원인이 된다. Huffman 코딩은 Lossless 압축 방식이므로 JPEG 이미지 화질 저하와는 관련이 없다.

이렇게 압축된 JPEG 이미지를 복원하려면 위의 과정을 역순으로 행하면 된다. 먼저 Huffman 디코딩을 통해 압축을 풀어 양자화된 데이터를 얻어낸다. 양자화된 데이터는 역 양자화(de-quantization)을 통해 원래 DCT 성분으로 복원된다. 물론 양자화 때문에 완벽하게 100% 원래 성분들을 모두 복원할 수는 없으나 사람의 눈에는 거의 뜨이지 않을 정도로 복원이 가능하다. 복원된 DCT 성분들은 IDCT 변환을 통해 Y-Cb-Cr 데이터로 변환된다. 이렇게 해서 얻은 Y-Cb-Cr 데이터는 Up-sampling 과정을 통해 다시 R,G,B 색상 체계로 변환되어 원래 모습을 찾게 된다. 물론 100% 똑같은 이미지는 아니지만.

지금까지 설명한 과정들이 아마도 쉽게 들어오지 않을 것이다. 보다 더 자세하게 각 과정들을 설명하도록 하겠다.

색상 체계 : JPEG 파일이 사용하는 색상 체계는 Y-Cb-Cr 색상 체계로 앞에서 살펴본 YIQ 색상 체계와 비슷한 방식이다. Y는 밝기를 나타내고 Cb, Cr이 색상을 나타낸다. 원래 Y는 0에서 1사이, Cb, Cr은 –0.5에서 0.5 사이의 값을 가지지만 JPEG 파일은 Y, Cb, Cr 모두 0에서 255의 범위를 가진다. 다음은 RGB에서 YCbCr, YCbCr에서 RGB로 변환하는 공식이다.

Y = 0.299 R + 0.587 G + 0.114 B
Cb = - 0.1687 R - 0.3313 G + 0.5 B + 128
Cr = 0.5 R - 0.4187 G - 0.0813 B + 128

R = Y + 1.402 (Cr-128)
G = Y - 0.34414 (Cb-128) - 0.71414 (Cr-128)
B = Y + 1.772 (Cb-128)

JPEG 파일은 YCbCr 세 개의 성분 가운데 어느 한 성분(보통 Y성분)만 저장하거나 세 성분 모두를 저장할 수 있다. Y성분만 저장하는 것은 256 level gray 색상으로 저장하는 것을 의미한다. 실제 JPEG 파일에 Y,Cb,Cr 데이터가 저장될 때는 128씩 뺀 값이 저장된다.

Sampling : 지금까지 살펴본 BMP, PCX, GIF와 같은 그래픽 파일들은 한 픽셀의 색을 나타내기 위해 R, G, B 색상 체계를 사용하였으며 3가지 성분 모두 같은 비율로 저장되었다. 하지만 JPEG 파일에서는 Y, Cb, Cr이 서로 다른 비율로 저장될 수 있다. 예를 들어 매 픽셀의 Y 성분을 모두 저장하는 반면 Cb, Cr은 한 픽셀씩 건너 뛰면서 저장할 수도 있다. 이와 같이 전체 픽셀 가운데 일부 픽셀의 데이터만 선별하여 저장하는 방식을 sampling이라고 한다.

JPEG 파일은 샘플링하는 간격을 샘플링 주기(sampling frequency)로 나타내는데 샘플링 주기는 각 성분의 샘플링 간격의 역수비로 나타낸다. Y 성분은 매 픽셀 마다 샘플링하고 Cb 성분은 두 번째 픽셀마다 샘플링하고 Cr 성분은 네 번째 픽셀마다 샘플링한다면 YCbCr의 샘플링 주기는 4:2:1 이 된다. 사람의 눈은 밝기(Y) 성분에 더 민감하기 때문에 보통 Y 성분의 샘플링 주기가 더 크다. 물론 꼭 Y 성분의 샘플링 주기가 커야 되는 것은 아니다. 필요하다면 Cb, Cr의 샘플링 주기가 더 클 수 있지만 그런 경우는 거의 없다.

또한 가로 방향의 샘플링 주기와 세로 방향의 샘플링 주기가 틀릴 수도 있다. 즉, 가로 방향으로는 모든 픽셀의 데이터를 다 저장하고 세로 방향으로는 한 픽셀 씩 건너 뛰면서 데이터를 저장할 수도 있다. JPEG 파일이 허용하는 최대 샘플링 주기는 4이다.

JPEG 파일에서 주로 쓰이는 샘플링 주기는 2:1:1 또는 1:1:1이다. 이와 같이 각 성분의 샘플링 주기를 다르게 하는 이유는 화질의 크게 떨어뜨리지 않고 데이터의 양을 줄이기 위해서 이다. 가로, 세로 방향의 YCbCr 샘플링 주기를 각각 2:1:1로 하면 1:1:1로 하는 것보다 화질의 저하는 다소 있을 수 있으나 데이터 양을 반으로 줄일 수 있다. 이와 같이 일부 픽셀 데이터만 샘플링하는 것을 Down-sampling이라고 하고 반대로 down sampling된 데이터를 원래 픽셀 개수로 늘이는 것을 Up-sampling이라고 한다. 다운 샘플링하는 방법은 여러 가지가 있을 수 있다. 단순하게 일정 간격마다 건너 뛰면서 픽셀 데이터를 저장할 수도 있고 일정 간격 내의 모든 픽셀 데이터들의 평균을 저장할 수도 있다. 후자가 좀 더 나은 방식인데 어떤 방식으로 샘플링하느냐는 그래픽 소프트웨어와 같은 어플리케이션 프로그램에 의해 결정된다. 그림 3은 샘플링의 예이다. 첫번째 경우는 픽셀 모두를 샘플링하는 경우이고 두 번째는 한 픽셀씩 건너 뛰면서 데이터를 샘플링하는 것이고 세 번째의 경우는 4 픽셀의 평균을 샘플링하는 것이다. 이 세가지 경우 샘플링 주기는 2:1:1이다. 두 번째와 세 번째는 샘플링 주기는 같다. 샘플링 방식만 다를 뿐이다.

그림 3 샘플링의 예

DCT와 양자화 : DCT란 Discrete Cosine Transform(이산 코사인 변환)의 약자로 JPEG 압축 기술의 핵심이라 할 수 있다. DCT는 임의의 데이터 배열을 코사인 함수의 합으로 표현할 수 있다는 성질을 이용한 것이다. 예를 들어 임의의 데이터 배열 f를 코사인 함수의 합으로 표현하면 다음과 같다.

이때 코사인 함수의 진폭 F를 DCT 계수(DCT coefficient)라고 하고 새로운 배열 F를 구하는 것을 DCT라고 한다. DCT 계수는 다음과 같이 구할 수 있다.

DCT 계수를 이용해 원래 데이터 f를 구하는 것을 IDCT(Inverse Discrete Cosine Transform)라고 한다. N개의 데이터 f를 DCT로 변환하면 N개의 DCT 계수 F를 구할 수 있다. 예를 들어 표 1의 f와 같이 8개의 원소로 이루어진 배열이 있다고 해보자. DCT 변환을 하면 8개의 DCT 계수 F를 구할 수 있다. DCT 계수들 중 0차 계수는 다른 계수들과 달리 코사인의 함수가 아니기 때문에 DC 계수라고 하고 다른 계수들은 AC 계수라고 부른다. 보통 DC 계수 값이 AC 계수 값에 비해 크기 때문에 JPEG 파일은 DC 계수와 AC 계수를 서로 다른 방법으로 저장한다. 구체적인 것은 뒤에 다시 설명하겠다.

n

0

1

2

3

4

5

6

7

f(n)

10

9.59

8.07

8.15

11.02

14.04

13.51

9.28

F(n)

29.58

-3.22

0.24

4.40

-2.39

0.35

-0.44

0.08

표 1 DCT 변환의 예

DCT 계수 F를 이용해 IDCT를 수행하면 다시 원래 데이터 f를 구할 수 있다. IDCT를 수행할 때 높은 차수의 DCT 계수(n이 큰 DCT 계수)를 생략할 수도 있는데 이 경우 원래 데이터와 100% 똑같지 않지만 상당히 비슷한 값으로 복원해 낼 수 있다. 그림 4는 DCT 계수를 1개에서부터 8개까지 사용해 IDCT를 수행한 경우 복원된 값을 그래프로 보여주고 있다.

점선이 IDCT로 복원된 값이고 실선이 원래 데이터이다. DCT 계수를 많이 쓸수록 원래 데이터에 근접한 결과를 얻을 수 있음을 알 수 있다. 하지만 N=5, 즉 5차 DCT 계수까지만 사용해 IDCT를 해도 원래 데이터와 상당히 유사한 결과를 얻을 수 있다. 여기에 JPEG 압축의 비밀이 들어 있는 것이다. DCT 계수를 모두 다 저장하지 않고 일부만 저장하여도 나중에 복원할 때 원래 데이터와 상당히 유사한 형태로 복원을 할 수 있는 것이다. 이와 같이 DCT 변환된 데이터 중 일부를 버리는 작업을 양자화(Quantization)라고 한다. JPEG 이미지로 변환할 때 약간의 화질 저하가 생기는 이유도 바로 양자화 때문이다.

그림 4 IDT로 복원할 때 사용한 계수의 개수에 따라 복원 정도가 달라진다.

그러나 양자화가 만능은 아니다. 그림 5는 서로 다른 유형의 데이터를 DCT하고 N=5로 양자화한 것을 다시 IDCT로 복원한 것이다. 왼쪽 그림의 경우 거의 완벽하게 복원이 되었으나 오른쪽 그림의 경우는 제대로 복원이 되지 않았다.

그림 5 양자화가 적합한 경우와 그렇지 않은 경우.

양자화가 힘을 발휘하려면 데이터들의 변화가 되도록이면 적어야 한다. 그래서 색의 변화가 극단적으로 일어나는 도형 이미지를 JPEG 이미지로 만들면 자연스럽지 못한 것도 양자화 때문이다. 그림 6을 보자. 왼쪽 그림은 원래 이미지이고 오른쪽 그림은 JPEG으로 저장한 이미지이다. 도형의 모양과 색이 왜곡된 것을 볼 수 있다.

그림 6 JPEG 이미지로 저장할 때 데이터 손실이 생긴다.

그러나 사진의 경우 색의 변화가 심한 부분이 적기 때문에 양자화를 해도 화질의 저하가 그리 크지 않다. 그래서 JPEG은 사진 이미지에 보다 더 적합한 포맷이다.

앞에서 살펴본 DCT, IDCT는 1차원인데 실제 JPEG 이미지에서는 2차원 DCT, IDCT를 사용한다. 샘플링한 픽셀 데이터를 8x8 블록으로 나누어 각 블록마다 2차원 DCT를 수행한 뒤 양자화를 거쳐 데이터 양을 줄인다. 2차원 DCT, IDCT는 다음과 같이 정의된다.

위의 식을 간단하게 행렬로 표시하면 다음과 같다.

JPEG에서는 N은 항상 8이며 f, F는 8x8 행렬이다. 변환 행렬 M은 다음과 같이 정의된다.

MT 는 행렬 M의 Transpose 행렬로 행렬 의 행과 열을 서로 뒤 바꾼 것이다. 즉, MT (i,j)=M(j,i)이다.

JPEG 파일 안에는 8x8 크기의 Quantization table이라는 것이 보관되어 있는데 이 테이블을 이용해 8x8 DCT 계수를 양자화한다. DCT 계수를 1대1로 대응되는 quantization table 값으로 나눈 뒤 반올림 해주면 양자화된 DCT 행렬을 얻을 수 있다. 이렇게 양자화된 DCT 행렬은 대부분의 값이 0이 된다. 양자화된 DCT 행렬을 다시 원래 상태로 돌리려면 단순히 quantization table 값을 곱해주면 된다.

Interleave와 non-interleave 방식 : JPEG 파일은 DCT를 수행하기 위해 샘플링 데이터들을 8x8 크기의 블록으로 나누어야 한다. 이렇게 나뉘어진 8x8 크기의 데이터 블록을 Data Unit이라고 부른다. 가로 세로 샘플링 간격이 1 x 1 픽셀이라면 8 x 8 픽셀 블록이 하나의 데이터 유닛이 되고 가로 세로 샘플링 간격이 2 x 2 픽셀이라면 16 x 16 픽셀 블록이 하나의 데이터 유닛이 된다. 한 데이터 유닛의 픽셀 데이터를 샘플링하여 DCT 계수를 구하고 양자화를 한 뒤 저장을 한다. 그림 이미지의 위에서부터 아래로, 왼쪽부터 오른쪽으로 가면서 샘플링-DCT-양자화를 반복한다.

Y,Cb,Cr 중 한 가지 성분만 저장할 때는 단순하게 (8 * 가로 샘플링 간격) x (8 * 세로 샘플링 간격) 픽셀 씩 나누어 DCT를 수행해 맨 오른쪽 윗부분부터 순차적으로 저장하면 된다. 하나 이상의 성분을 저장할 때는 조금 복잡해 진다. Y, Cb, Cr 각 성분의 샘플링 주기가 모두 같을 때는 하나의 8 x 8 블록 내에 있는 Y, Cb, Cr 데이터를 순차적으로 저장하고 그 다음 8 x 8 블록의 Y, Cb, Cr 데이터를 순차적으로 저장한다. 샘플링 주기가 다를 때는 샘플링 주기가 가장 작은 성분을 기준 블록으로 삼고 이 블록 안의 Y, Cb, Cr 데이터를 순차적으로 모두 저장한 뒤 그 다음 기준 블록의 Y, Cb, Cr 데이터를 저장한다. 이 기준 블록을 MCU(Minimum Coded Unit)이라고 한다. 이와 같이 여러 개의 성분을 번갈아 가면서 저장하기 때문에 하나 이상의 성분을 저장할 때를 Interleaved 방식이라고 한다. 반면에 하나의 성분만 저장할 때는 Non-interleaved 방식이라고 한다. 그림 7은 Non-interleaved 방식으로 저장하는 예이다.

그림 7 Non-interleave 방식

그림 8은 Y, Cb, Cr의 샘플링 주기가 2:1:1인 경우 interleaved 방식으로 저장하는 예이다. Y성분은 모든 픽셀에서 샘플링하고 Cb, Cr 성분은 한 픽셀씩 건너 뛰면서 샘플링한다. 따라서 Y의 8 x 8 블록은 8 x 8 픽셀로 이루어져 있지만 Cb, Cr의 8 x 8 블록은 16 x 16 픽셀로 이루어져 있다. 즉, MCU는 16 x 16 픽셀로 되어 있으며 각 MCU 별로 Y, Cb, Cr 성분을 저장하게 된다.

그림 8 Interleave 방식

첫번째 MCU 블록에서 Y1, Y2, Y3, Y4 블록의 순으로 Y 데이터를 저장하는데 각 데이터 유닛의 크기는 8 x 8 픽셀이다. 다음 16 x 16 픽셀의 Cb, Cr 데이터(Cb1, Cr1)를 순차적으로 저장하면 첫번째 MUC 블록의 저장이 끝이 난다. 그 다음 두 번째 MCU 블록에서 Y5, Y6, Y7, Y8 블록 순으로 Y 데이터를 저장하고 Cb2, Cr2 데이터를 저장한다. 이런 방식으로 맨 마지막 8번째 MCU 블록까지 저장한다.

Interleaved 방식이던 Non-interleaved 방식이던 항상 MCU 의 배수 크기로 저장을 해야 한다. 따라서 그림 이미지 크기가 정확하게 MCU의 배수 크기가 아니면 실제 크기보다 더 크게 저장이 된다. 물론 복원할 때 원래 그림 이미지 크기에 맞게 불필요한 부분을 잘라낸다. MCU 크기에 맞지 않는 블록에서는 비어있는 부분은 보통 마지막 픽셀을 중복해서 채워넣는다.

JPEG 파일에는 각 성분의 샘플링 주기에 대한 정보와 그림 이미지의 크기에 대한 정보만 있을 뿐 MCU의 크기, 개수에 대한 정보는 저장되어 있지 않다. 따라서 다음과 같은 공식으로 MCU의 크기와 개수를 계산해 낼 수 있다.

MCU 폭 = 최대 샘플링 주기 * 데이터 유닛 폭(항상 8)
MCU 높이 = 최대 샘플링 주기 * 데이터 유닛 높이(항상 8)
가로 방향 MCU 개수 = (이미지 폭 + MCU폭 – 1) / MCU 폭
세로 방향 MCU 개수 = (이미지 높이 + MCU높이 – 1) / MCU 높이

Huffman Coding : 샘플링-DCT-양자화를 거친 데이터를 바로 그냥 저장하는 것이 아니고 한번 더 압축을 해서 저장을 한다. GIF 파일에서 LZW 코딩을 했듯이 JPEG 파일에서는 Huffman 코딩이라는 방법을 써서 압축을 한다. Huffman 코딩의 원리는 간단하다. 보통 한 픽셀 데이터의 길이는 8비트로 고정이 되어 있는데 Huffman 코드는 데이터의 빈도수에 따라 그 길이가 다르다. 빈도수가 많은 코드일수록 길이가 짧기 때문에 길이가 고정된 경우보다 전체 데이터의 크기가 작은 것이다.

Huffman 코드를 만드는 방법은 여러 가지가 있는데 JPEG 파일은 코드 길이(Code length)를 이용한 방법을 쓰고 있다. 알고리즘은 다음과 같다.

1. 데이터 배열에서 각 데이터들의 빈도수를 구한다.
2. 데이터들의 빈도수를 이용해 코드의 길이를 구한다.
3. 코드 길이를 이용해 Huffman 코드를 구한다.

예를 들어 다음과 같은 데이터 배열의 Huffman 코드를 구해보자.

BABDAGBFBDBDBABGGBCDBGBDBGBEFGFFGBFG

데이터의 종류는 A에서 G까지 7가지 이고 각 데이터들의 빈도수는 다음과 같다.

데이터

빈도수

데이터

빈도수

A

3

B

13

C

1

D

5

E

1

F

5

G

8

이를 이용해 코드의 길이를 구한다. 먼저 각 데이터들의 코드 길이를 나타내는 변수를 하나씩 할당해주고 모두 0으로 초기화한다. 다음 가장 빈도수가 작은 데이터 두 개를 골라 합친다. 그리고 각 데이터의 빈도수의 합을 새로운 빈도수로 넣어주고 코드 길이를 1씩 증가시킨다. 아래의 예에서는 빈도수가 1인 C와 E를 합쳐주고 코드 길이를 1씩 더했다. 새로운 테이블에서 다시 가장 빈도수가 작은 데이터 두 개를 골라 합치고 코드 길이를 1씩 증가시킨다.

이 작업을 모든 데이터가 다 합쳐질 때까지 반복한다.

이제 최종적으로 남은 코드 길이가 Huffman 코드의 길이가 된다. 빈도수가 높은 데이터일수록 코드 길이는 작다는 것을 알 수 있다. 다음 알고리즘을 이용해 Huffman 코드를 구할 수 있다.

HuffCodeCount = 0
CurrCodeLength = CodeLength(0)
i = 0
Do While i < NoOfCodes
  Do While CodeLength(i) = CurrCodeLength
   Code(i) = HuffCodeCount
   HuffCodeCount = HuffCodeCount + 1
   i = i + 1
  Loop
  HuffCodeCount = ShiftLeft(HuffCodeCount)
  CurrCodeLength = CurrCodeLength + 1
Loop

이 알고리즘을 한 스텝씩 따라가보면 그림 9와 같은 결과를 얻을 수 있다. 아래 첨자 2는 2진수임을 나타낸다.

그림 9 Huffman code 만들기

마지막 테이블이 최종적으로 구한 Huffman 코드 테이블이다. 이 코드 테이블을 이용해 데이터 배열의 각 데이터들을 Huffman 코드로 바꾸어 주면 압축이 된다. Huffman 코드는 코드마다 길이다 다르기 때문에 한 바이트 안에 여러 개의 코드가 들어갈 수도 있고 두 바이트에 걸쳐 하나의 코드가 들어갈 수도 있다.

얼마나 압축을 할 수 있는지 한번 살펴보자. 원래 데이터들은 A부터 G까지 7가지 이므로 3비트로 표현한다고 해보자. 총 데이터 개수가 36개이므로 전체 데이터 배열의 크기는 3 x 36 = 108 비트이다. 데이터 배열을 Huffman 코드로 표현하면 B는 1비트 코드로 표현할 수 있으며 B의 개수는 13개이므로 13비트가 필요하다. G의 개수는 8개이며 3비트로 표현할 수 있으므로 24비트가 필요하다. 이런 식으로 필요한 모든 비트 수를 계산하면 표 2와 같이 총 89비트가 필요하다. 따라서 압축 효율은 89/108 = 89.8%가 된다.

데이터

코드 길이

개수

필요한 비트

B

1

13

13

D

3

5

15

G

3

8

24

F

3

5

15

A

4

3

12

C

5

1

5

E

5

1

5

36

89

표 2 압축 효율

Huffman 코드로 압축된 데이터를 원래 데이터로 복원하는 것은 더 간단하다. 압축된 파일 안에는 Huffman 코드 테이블을 함께 저장한다. Huffman 코드 테이블을 참조하여 원래 데이터로 바꾸기만 하면 된다. 예를 들어 앞에서 살펴본 데이터 배열의 첫 4개의 데이터를 Huffman 코드로 표현하면 다음과 같다.

BABD ... -->  0112 1002 1002 ...

그런데 Huffman 코드는 길이가 가변이기 때문에 어디에서부터 어디까지가 하나의 코드인지 쉽게 구분이 가지 않는다. 하지만 Huffman 코드를 잘 살펴보면 해답을 찾을 수 있다. 서로 다른 코드는 코드의 맨 앞부분이 서로 중복되지 않는다는 특징을 가지고 있다. 예를 들어 B를 가리키는 코드는 02인데 02로 시작되는 코드는 하나도 없다. 마찬가지로 1002로 시작하는 코드는 D밖에 없다. 따라서 코드 길이 순서대로 코드 테이블을 검색하여 최초로 일치하는 코드가 나오면 그 코드가 바로 찾으려는 코드가 된다. 위의 예에서 첫번째 비트 데이터는 02인데 Huffman 코드 테이블에서 02으로 시작하는 코드는 B 밖에 없으므로 첫번째 비트가 B를 가리키는 Huffman 코드임을 알 수 있다. 그 다음 비트는 12인데 코드 길이가 1인 코드는 02밖에 없으므로 그 다음 비트를 읽어봐야 한다. 그 다음 비트는 12인데 코드 길이가 2인 코드는 없으므로 또 다음 비트를 읽어야 한다. 그 다음 비트까지 읽으면 지금까지 읽은 코드는 1112이 된다. 하지만 Huffman 코드 테이블에는 1112코드는 존재하지 않는다. 따라서 또 다음 비트를 더 읽어와야 한다. 그 다음 비트까지 읽으면 11102이 되는데 11102은 Huffman 코드 테이블에 존재하며 A를 가리킨다. 두 번째 코드를 찾은 것이다. 그 다음 비트는 02인데 02은 B를 가리킨다. 세 번째 코드도 찾았다. 마찬가지로 Huffman 코드 테이블에 존재하는 코드가 나올 때까지 다음 비트들을 읽는다. 그러면 그 다음 코드는 1002가 됨을 알 수 있고 1002는 D를 가리킨다.

JPEG 파일에서 Huffman 코딩 : JPEG 파일에서는 DCT-양자화를 거친 데이터를 Huffman 코딩 방식으로 압축을 한다. Huffman 코드의 최대 길이는 16비트로 제한되어 있다. DCT 계수 가운데 DC 계수와 AC 계수를 따로 분리하여 코딩하고 필요에 따라 YCbCr 성분 별로도 따로 코딩을 한다. Huffman 코딩은 서로 같은 데이터가 많을수록 압축 효율이 좋기 때문에 이렇게 서로 비슷한 성분들끼리 분리하여 코딩을 하는 것이다.

보통 DC 계수의 크기는 AC 계수의 크기보다 크며 양자화된 AC 계수는 대부분이 0이다. AC 계수끼리 묶으면 대부분의 데이터가 0인데 보다 압축 효율을 높이기 위해 0으로 된 데이터들은 PCX 파일에서 사용한 Run-length encoding 방식으로 압축을 한다. 즉, 데이터 자체를 저장하는 것이 아니라 0의 개수를 저장하는 것이다. 0이 아닌 AC 계수와 DC 계수는 Huffman 인코딩을 하는데 값 자체를 인코딩하는 것이 아니고 인접한 값과의 차이를 인코딩한다. 예를 들어 DC 계수 배열이 122, 123, 124, 125, 126 이런 식이라면 실제 값이 아닌 인접한 값과의 차이인 122, 1, 1, 1, 1을 인코딩하는 것이다. 이렇게 하면 서로 똑같은 값들이 많아지기 때문에 값 자체를 인코딩할 때보다 훨씬 더 압축 효율이 높아진다. 이런 이유 때문에 인접한 픽셀들끼리의 변화가 적은 사진 이미지가 도형 이미지보다 압축 효율이 좋다.


이미지 압축 원리
1. BMP (Windows bitmap)
2. PCX
3. GIF (Graphic Interchanging Format)

4. JPEG
5. WMF (Mircosoft Windows Metafile)
6. 부록 - Graphic File Loader 예제 프로그램


JFIF :

지금까지 설명한 원리에 따라 JPEG 이미지가 저장이 되는데 JPEG 파일의 실제 구조는 어떠한지 알아보자. 이미 설명했듯이 JPEG 파일 구조란 JFIF(JPEG File Interchange Format)을 의미한다. JPEG 파일은 그림 1과 같이 여러 개의 블록으로 나뉘어져 있다.

SOI (Start Of Image)
APP0 JFIF Marker
추가 APP Marker (APP0 ~ APP15) 또는 주석 (COM) marker
DQT (Define Quantization Tables)
SOF (Start Of Frame)
DHT (Define Huffman Tables)
SOS (Start Of Scan)
스캔 데이터
EOI (End Of Image)


그림 1 JFIF의 구조

각 블록은 Marker에 의해 구분된다. Marker는 &HFF로 시작되는 두 바이트 데이터로 marker 하나로 끝나는 것도 있고 marker 뒤에 추가 데이터가 더 붙는 것도 있다. 추가 데이터가 더 붙는 경우 marker 뒤에 2바이트의 데이터 길이가 오고 그 다음 추가 데이터가 온다. 데이터 길이는 데이터 길이 필드를 포함한 길이다. 한가지 주의할 것이 지금까지 살펴본 그래픽 파일에서는 상위 바이트와 하위 바이트의 순서가 바뀌어서 저장되어 있는데 JPEG 파일에서는 그 순서가 뒤바뀌어서 저장되지 않는다는 점이다. 예를 들어 &H12 &H3F 순으로 저장되어 있다면 &H3F12가 아니고 &H123F를 가리키는 것이다.

JFIF에서 사용되는 marker는 표 1과 같다.

분류 심볼 설명
추가 데이터 없음 FFD0~FFD7 RST0~RST7 Restart marker
FFD8 SOI Start Of Image
FFD9 EOI End Of Image
추가 데이터 있음 FFC0~FFC2 SOF0~SOF2 Start Of Frame
FFC4 DHT Deine Huffman Tables
FFDA SOS Start Of Scan
FFDB DQT Define Quantization Tables
FFDD DRI Define Restart Interval
FFE0~FFEF APP0~APP15 Application specific data
FFFE COM Commnet

표 1 JFIF에서 사용되는 marker

JPEG 표준에는 이보다 더 많은 marker들이 정의되어 있으나 JFIF에서는 거의 쓰이지 않는다. 각 블록을 더 자세히 살펴보자.

SOI와 APP0 JFIF Marker : 파일의 맨 처음 오는 marker들로 주로 JFIF임을 확인하는 용도로 사용된다. 그 구조는 표 2와 같다.

이름 크기 설명
SOI marker 2 항상 FF D8
APP0 marker 2 항상 FF E0
Data length 2 추가데이터 길이, 바이트 단위, 상위 바이트, 하위 바이트 순으로 저장되며 Data length 필드를 포함한 길이를 나타낸다.
Identifier 5 항상 4A 46 49 46 00. JFIF임을 나타낸다.
Major version ID 1 버전 번호.
Minor version ID 1 버전 번호.
Units 1 그 다음 오는 X, Y 해상도의 단위를 나타냄.

0 - 해상도 사용 안함
1 - 단위 인치 당 픽셀 수
2 - 단위 cm 당 픽셀 수
Xdensity 2 가로 해상도
Ydensity 2 세로 해상도
Xthumbnail 1 미리 보기 이미지의 폭. 미리 보기가 없으면 0.
Ythumbnail 1 미리 보기 이미지의 높이. 미리 보기가 없으면 0.
Thumbnail 가변 미리 보기 이미지. RGB 컬러 순으로 픽셀 데이터가 저장되어 있다.

표 2 SOI와 APP0 JFIF marker의 구조

추가 보조 Marker : APP0 marker 외에 미리 보기 이미지를 추가로 더 넣거나 주석을 달기 위해 몇 가지 marker가 더 올 수 있다. 보통 이들 marker들은 그림 이미지 자체에 전혀 영향을 주지 않으며 어플리케이션 프로그램에서 보조 자료로 쓰일 뿐이다. marker 뒤에 추가 데이터 길이 필드가 오는데 이를 이용해 다음 marker로 건너 뛸 수 있다.

DQT marker : DQT는 양자화 테이블(Quantization table)을 보관하는 블록이다. 표 3과 같은 구조로 되어 있다.

이름 크기 설명
DQT marker 2 항상 FF DB
Data length 2 추가 데이터 길이.
Quantization table   양자화 테이블. 최대 4개의 테이블을 가질 수 있다.

표 3 DQT marker의 구조

JFIF는 최대 4개의 양자화 테이블을 보관할 수 있다. 각각의 양자화 테이블 구조는 표 4와 같다. 테이블 값은 8비트 샘플링의 경우 1바이트, 12비트 샘플링의 경우 2바이트 크기를 가진다.

Table Identifier 1 상위 4비트 : 테이블 값 크기. 0= 1바이트. 1=2바이트.
하위 4비트 : 테이블 ID. 0~3 사이의 값을 가진다.
Table 64 또는 128 양자화 테이블. 8 x 8 테이블이 지그재그 순서로 저장되어 있다. 그림 2참고

표 4 양자화 테이블의 구조


그림 2 양자화 테이블 값이 저장된 순서

SOF marker : SOF는 그림 이미지의 크기 및 샘플링에 관한 정보를 보관하고 있다. 표 5는 SOF 블록의 구조이다.

이름 크기 설명
SOF marker 2 항상 FFC0 ~ FFC2

FFC0 - baseline
FFC1 - extened sequential
FFC2 - progressive
Data length 2 추가 데이터 길이.
Sampling precision 1 샘플링 비트 수 8 또는 12
Image height 2 이미지 높이. 픽셀 단위.
Image width 2 이미니 폭. 픽셀 단위.
No fo components 1 Y, Cb, Cr 성분 수. 1 ~ 3.
Component specific   각 성분에 대한 정보.

표 5 SOF marker 구조

JFIF는 Y, Cb, Cr 세 가지 성분 중 하나 또는 3가지 모두를 저장할 수 있다. 각 성분에 대한 정보는 표 6과 같은 구조로 저장되어 있다.

이름 크기 설명
Component ID 1 각 성분을 구분하기 위한 ID. 0~255 사이의 값을 가질 수 있으나 JFIF에서는 Y는 1, Cb는 2, Cr은 3으로 고정되어 있다.
Sampling Frequency 1 상위 4비트 : 해당 성분의 가로 샘플링 주기 (1~4)
하위 4비트 : 해당 성분의 세로 샘플링 주기 (1~4)
Quantization table ID 1 해당 성분에서 사용할 양자화 테이블의 ID 0 ~ 3의 값을 가진다.

표 6 YCbCr 성분에 대한 정보

SOF는 JPEG 파일 안에 오직 하나만 존재할 수 있다.

DHT marker : DHT는 Huffman 코드 테이블을 보관하고 있는 블록이다. Huffman 코드 테이블은 AC 계수용, DC 계수용이 따로 보관되며 각각 최대 4개의 테이블을 가질 수 있다. 각 테이블의 구조는 표 7과 같다.

이름 크기 설명
DHT marker 2 항상 FF C4
Data length 2 추가 데이터길이.
Huffman table   Huffman table 구조 참고

하나의 Huffman table 구조

이름 크기 설명
Table ID 1 상위 4비트 : Table class. 0은 DC 1은 AC 테이블을 의미.
하위 4비트 : Table ID. 0 ~ 3의 값을 가진다.
Codelength Counter 16 코드 길이가 1 ~16인 코드의 개수를 보관하고 있다.
Symbols 가변 Huffman 코드 순서대로 코드가 가리키는 실제 데이터를 보관하고 있다. 전체 크기는 codelength counter의 총 합.

표 7 DHT marker

SOS marker : SOS는 각 성분들이 어떤 Huffman table을 사용할지 알려주는 블록이다. 그 구조는 표 8과 같다.

이름 크기 설명
SOF marker 2 항상 FF DA
Data length 2 추가 데이터 길이.
Component count 1 성분 개수
Scan description   각 성분이 사용할 Huffman table의 ID
Spectral selection start 1 DCT 를 최적화할 때 사용하는 값.
Spectral selection end 1
Successive approximation 1

표 8 SOS marker

Scan description은 성분의 개수만큼 있는데 각각의 구조는 표 9와 같다.

이름 크기 설명
Component ID 1 각 성분을 구분하는 ID
Huffman table ID 1 상위 4비트 : 해당 성분이 사용할 DC Huffman 테이블의 ID
하위 4비트 : 해당 성분이 사용할 AC Huffman 테이블의 ID

표 9 각 성분의 scan description

SOS가 시작되기 전에 최소한 하나 이상의 DHT, DQT와 하나의 SOF가 있어야 한다.

스캔 데이터 : SOS 바로 다음에 스캔 데이터 블록이 오게 되는데 스캔 데이터는 픽셀들의 색 정보를 샘플링-DCT-양珉?Huffman coding 과정을 거쳐 만들어낸 데이터이다. 이렇게 해서 만들어진 스캔 데이터를 다시 원래 픽셀 정보로 바꾸려면 역으로 Huffman decoding-역양자화(Dequantization)-IDCT-업 샘플링 과정을 거치면 된다.

Restart marker : restart marker (RST)는 유일하게 스캔 데이터 안에 존재하는 marker로 손상된 JPEG을 최대한 복원하기 위해 사용하는 marker이다.

SOS 이전에 DRI(Define Restart Interval) marker로 restart 간격을 정의를 한다. DRI marker의 구조는 표 10과 같다.

이름 크기 설명
DRI marker 2 항상 FF DD
Data length 2 추가 데이터 길이.
Restart interval 2 Restart 간격. 0이면 restart marker(RST)가 쓰이지 않음을 의미.

표 10 Restart marker

DRI에 정의된 restart 간격이 0보다 크면 그 간격만큼의 MCU 마다 RST marker가 놓이게 된다. 예를 들어 restart 간격이 N이라면 그림 3과 같이 N개의 MCU 데이터 마다 RST marker가 놓인다.


그림 3 RST marker

RST marker는 RST0부터 RST7까지 8개가 있는데 순차적으로 나타나며 RST7 다음에는 RST0이 다시 나오게 된다.



출처 : http://php.chol.com/~mrjin 
2008. 10. 5. 22:44

YUV의 종류

1. YUV 4:1:1
 
콤포넌트 비디오의 휘도신호(Y)와 색차신호(R-Y, B-Y)를 디지털화하는데 사용되는 표본화 주파수의 비율. Y신호는 13.5MHz, 색차신호 R-Y와 B-Y는 각각 3.37MHz로 표본화된다.
TV화면 등의 주사선에 포함되는 정보신호 중 휘도 정보(Y)와 색차 정보(Cb, Cr)의 성분비율은 보통 : : 으로 표시되는데 이러한 표시는 휘도 신호(Y)와 두 개의 색차 Cb, Cr 신호의 표본화 주파수의 비율을 나타낸다. 여기서 4:1:1의 비율이란 휘도 신호 Y의 표본화 주파수 13.5㎒를 4로 할 때 색차 신호 R-Y, B-Y는 각각 1이기 때문에 휘도 신호의 표본화 주파수 13.5㎒의 1/4이 되므로 색차 신호 R-Y와 B-Y는 각각 3.37㎒가 된다. 또한 4:1:1의 신호처리 기법은 4:2:2의 신호처리 기법 등에 비해서 해상도 등이 다소 떨어지는 단점이 있으나 비교적 적은 데이터 비율로 디지털 영상신호를 기록 수 있다는 점에서 경제성이 우수하며 이러한 포맷은 가정용 DVTR(Digital VTR)의 세계 통일 규격인 DV(DVC)와 업무 및 방송용 포맷으로 널리 이용되는 DVCAM과 DVCPRO(25Mbps 규격) 등에 제한적으로 이용되고 있다.
참조 : 4:2:2
 
 
2. YUV 4:2:0
 
Y신호가 매 라인마다 13.5MHz로 표본화될 때 R-Y와 B-Y는 한 라인을 건너뛰어 6.75MHz로 표본화되는 (즉, 한 라인은 4:0:0으로 다음 라인은 4:2:2로 표본화) 휘도와 색차신호의 표본화 주파수 비율.
4:2:2 표본화에서는 수직라인의 색해상도가 수평라인 해상도의 1/2인 반면, 4:2:0 표본화에서는 수평과 수직해상도가 같다.
TV화면 등의 주사선에 포함되는 정보신호 중 휘도 신호 Y의 표본화 주파수 13.5㎒를 4로 할 때 두 개의 색차 신호 Cb, Cr의 종 방향 및 횡 방향 모두를 Y의 표본화 주파수에 대해 1/2로 표본화하는 신호처리 기법이다. 즉 라인마다 Y의 13.5㎒에 대해 R-Y를 Y신호의 1/2인 6.75㎒로 표본화하는 동시에 B-Y는 6.75㎒로 표본화하지 않고, 이와 반대로 Y의 13.5㎒에 대해 R-Y를 6.75㎒로 표본화하지 않는 동시에 B-Y는 6.75㎒로 표본화하는 상태를 교대로 되풀이하는 것을 말하는데 이것을 비율로 나타내는 경우 4:2:0과 4:0:2로 반복이 된다. 따라서 이러한 포맷으로 표현되는 표본화 주파수비율을 일반적으로 4:2:0으로 나타내고 있다.
참조 : 4:2:2, DVC
 
 
3. YUV 4:2:2

콤포넌트 비디오의 휘도(Y)와 색차신호(R-Y, B-Y)를 디지털화하는데 사용되는 표본화 주파수의 비율. 4:2:2라는 용어는 Y가 4번 표본화될 때 R-Y와 B-Y는 2번 표본화되는 것을 의미하는데, 이는 4:1:1에 비하여 휘도에 대한 색도대역폭을 더 많이 할당한 것이다. CCIR 601에서 4:2:2 표본화는 디지털 스튜디오 장비의 표준으로서, 4:2:2와 CCIR 601이라는 두 용어가 일반적으로 동의어로 사용되지만 기술적으로 정확히 같은 의미는 아니다.
Y의 표본화 주파수는 13.5MHz이고, R-Y와 B-Y는 각각 6.75MHz로서 고품질 크로마키에 적합한 3.37MHz의 최대 가능 색대역폭을 제공한다.
TV화면 등의 주사선에 포함되는 정보신호 중 휘도 신호 Y의 표본화 주파수 13.5㎒를 4로 할 때 두 개의 색차 신호 Cb, Cr를 횡 방향으로 Y의 표본화 주파수에 대해 1/2로 표본화하는 신호처리 기법이다. 즉 Y의 표본화 주파수 13.5㎒에 대해 색차 신호 R-Y, B-Y는 각각 6.75㎒가 된다. 이러한 신호처리 기법은 8비트 양자화 규격의 D-1, DVCPRO50, Digital-S, Betacam SX 그리고 10비트 양자화 규격의 D-5, 디지털 베타캄과 같은 방송·업무용 포맷의 콤포넌트 DVTR 등에 이용되고 있으나 ITU-R-BT.601에서 규정하는 Y신호의 최대 주파수가 5.5㎒인 점을 고려하면 충분한 샘플링 주파수이긴 하지만 데이터 비율은 콤포지트 방식의 약 2∼2.5배에 이르는 216/270Mbps가 되므로 이로 인해 발생된 막대한 디지털 데이터를 비디오 테이프에 여유 있게 기록하기 위해서는 고효율의 압축 알고리즘이 사용되는 경우가 있다.
참조 : CCIR 601
 
4. YUV 4:2:2:4
 
4:2:2와 같으나 4번째 요소로서 13.5MHz로 표본화된 키신호를 포함한다.
 
 
5. YUV 4:2:2:P@ML
 
MPEG-2에서는 여러 가지 도구(Tool)가 제공되고 있어서 대단히 광범위한 응용에 대응할 수 있다. 한편 하드웨어 관점에서 바라보면 지원해야 할 사항이 너무 많아 모든 것을 실현하기가 대단히 어렵다. 가령 A사의 부호기가 4:2:2를 직접 부호화 할 수 있도록 만들어진 것이라도 B사의 복호기가 4:2:0만을 복호 할 수 있다면 A사의 비트열(부호기에 의해 생성된 데이터)의 실용성은 낮아지게 된다. 이와 같은 일이 자주 일어나면 모처럼의 국제표준이 정보의 상호 이용성에 있어서 사실상 무용지물이 되어버린다. 따라서 이와 같은 사태를 피하기 위하여 MPEG-2에서는 "프로파일(Profile)"과 "레벨(Level)"이라고 하는 개념을 도입하여 MPEG-2의 복호기의 특성을 나누고 있다. 즉 메인 프로파일의 메인레벨은 "MP@ML(Main Profile at Main Level)이라고 약칭하게 되는 것이다. ☞ MPEG
 
6. YUV 4:4:4

콤포넌트 비디오의 휘도(Y)와 색차신호(R-Y, B-Y)나 R·G·B신호를 디지털화하는데 사용되는 표본화 주파수의 비율. 4:4:4에서 모든 콤포넌트들은 동일한 수의 표본들로 이루어진다.
TV화면 등의 주사선에 포함되는 정보신호 중 휘도 신호 Y와 두 개의 색차 신호 Cb, Cr 모두의 표본화 주파수를 13.5㎒로 하는 신호처리 기법이다. 한편으로는 그래픽환경에서 사용되는 고해상도 Component 디지털 비디오 신호의 샘플링 비를 나타내는 약어의 표현을 말한다. 이들 숫자의 의미는 3개의 콤포넌트 채널(Y, Pb, Pr 또는 R, G, B)을 나타내며 3개의 채널을 각각 13.5㎒로 샘플링 한다. 즉, 비디오 신호의 루미넌스와 크로미넌스 차이를 나타내는 Y, R-Y, B-Y에 사용되는 샘플링 주파수는 13.5㎒로 각각 모두 같다. 4:4:4는 통상적으로 컴퓨터를 기본으로 하는 장비에서 장비의 특성이나 성능을 나타내는 기준으로 삼고 있다.
 
7. YUV 4:4:4:4
 
13.5MHz로 표본화된 키신호가 추가로 포함되어 있는 것을 제외하고는 4:4:4와 동일하다

2008. 9. 29. 23:19

BMP 파일에서 RGB 데이터 보기

http://cafe.naver.com/carroty/12008

그럼, 이번에 공구한 TFT-LCD는 보통 QVGA급(320 x 240; Quarter VGA, 즉, 640 x 480 VGA의 1/4에 해당하는 크기를 말함)이므로 일단 320 x 240 사이즈의 테스트용 비트맵 파일을 가지고 실험을 해봤다.

테스트 사진 중에 가로로 긴 사진이 없어서 세로로 긴 사진을 가지고 테스트했다.

(그러나, 사진을 보면 잘했다는 생각이 들것임. *^^*) 일단, 완소 김태희 양의 사진을 가지고 분석했다.
 

1.jpg

 
완소 김태희 사진이 들어있는 BMP파일을 첨부파일을 울트라 에디터로 열어보면 바이너리 모드로 열린다. 그리고, 탐색기에서 해당 비트맵 파일의 등록정보를 열어서 파일 사이즈 및 비트맵 정보를 확인하자. 마우스로 오른쪽 클릭하고 등록정보를 확인하면 파일 크기가 230,454 바이트(=16진수로는 0x38436 바이트)이고, 비트 깊이는 24비트임을 알 수 있다. 물론 크기는 240 x 320 픽셀이다.

 자, 그 다음에 울트라 에디터로 살짝 파일을 열어보면 파일 앞 부분이 다음과 같이 되어 있음을 알 수 있다.

2.jpg

 위의 그림에서 빨간색 박스로 그려진 부분이 BMP파일의 헤더에 해당한다. 위의 헤더파일의 구조는 다음과 같다.

3.jpg

 생각보다 BMP 파일의 헤더 구조는 단순하다. 그리고, 저 헤더부분 뒤에 이미지의 RGB 데이터가 바로 이어지기 때문에 BMP파일에서 RGB 데이터를 추출하고자 할 때에는 헤더부분을 제외한 다음 부분부터 파싱을 하면 된다. 헤더 부분을 잠시 살펴보면 모든 BMP 파일은 첫 두바이트가 0x41, 0x4D로 시작한다. 이를 보통 매직코드라고 부른다. 대부분의 특정한 포맷을 가진 파일들은 위와 같은 매직코드를 가지고 있는 경우가 많다. 그 다음 필드는 BMP 파일의 사이즈인데, 아까 등록정보로 0x38436 바이트라고 했는데, 그 값이 뒤집혀서(즉, 리틀 엔디안 형식으로) 저장되어 있다. 그리고, 다섯번째 필드를 보면 실제 이미지 데이터가 시작하는 오프셋의 위치를 담고 있는데... 0x36번째 부터란다. 헤더 부분의 총크기를 계산하면 0 ~ 0x35이므로 총 0x36바이트를 가진다. 따라서, 오프셋의 위치와 정확히 일치함을 알 수 있다. 그리고, image width/height in pixels 필드는 그림 파일의 가로/세로 픽셀 수에 해당하는데 저 값을 10진수로 바꿔보면 240 x 320이다. 그 다음 가장 중요한 "size of image data in bytes"는 실제 이미지 데이터의 크기를 말한다(데이터 크기가 정말 저 값이 맞는지는 각자 확인하기 바람 *^^*). 또한, number of bits per pixel 은 한 픽셀당 비트수를 말하는데, 0x18이니까 십진수로 24비트이다. 아까 등록정보에서 확인한 값과 일치한다. 이런 식으로 헤더정보를 파싱하면 된다.


이제, 헤더의 다음부분 부터 이미지 데이터가 어떤 식으로 저장되어 있는지 살펴보면...

신기하게도... 좌측상단의 점부터 데이터를 저장하고 있는 것이 아니라, 맨 아래줄부터 데이터가 저장된다.

무슨 말인고 하면...

 

4.jpg

 

위의 그림처럼 BMP파일의 그림 데이터는 라인단위로 저장되는데... 이미지의 가장 아래 라인부터 역순으로 저장된다. 게다가, 저장되는 순서도 RGB(8비트, 8비트, 8비트) 순서로 저장되는 것이 아니라 BGR 순서로 저장된다. 정말???

5.jpg

 

위의 모서리 값은 실제 이미지 데이터의 RGB값을 그래픽 뷰어 프로그램으로 찾아 본 것인데, 헤더 부분의 그 다음 3바이트를 확인해보면...

 

EC, EF, E6

 

임을 알 수 있는데, 그 값은 정확히 위 의 그림에서 ③번 모서리의 RGB의 역순임을 알 수 있다. 이런 식으로 BMP파일에서 RGB 데이터를 추출하면 된다.


2008. 9. 29. 19:09

YUV -> RGB 변환


미디어 세상이 되면서 YUV -> RGB 변환이 많이 중요해졌죠.

동영상 압축 포맷에서 압축을 풀면 영상 기기에 적합한 YUV 포맷으로 데이터가 나오고,

화면에 표현하려면 RGB로 변환이 필요하죠.

함수는 convert() 함수하나면 됩니다.

예제는 qcif 파일 읽어서 한 프레임만 BMP 파일로 저장합니다.(단 BMP로 못 여는 데, 이유는 BMP 헤더를

작성하지 않았기 때문입니다. BMP는 헤더 + 데이터로 구성되는데, 헤더가 없습니다. 어디까지나 동영상 재생용이므로)

변환 테스트였고, convert() 함수를 한 번만 호출합니다.

YUV 플레이어라는 건 1초에 24번씩 convert() 함수를 호출하면서 이미지를 바꾸면 된다는 겁니다.

영상 샘플은
http://daci.digitalsystems.cs.tut.fi/daci_release/ra_daci_videobench_mpeg4_sp_vbr.html

에 종류별로 있습니다.

숫자나 파일명은 전부 하드코드되어 있습니다. ㅎㅎ

GUI 작업하는 사람은 convert()만 호출하면 됩니다. convert() 호출해서 사용법만 알면 되고,

YUV -> RGB 변환에 대해서는

http://wiki.kldp.org/wiki.php/DemuxMPEG
http://minzkn.wowdns.com:2744/tattertools/130
http://www.asmlove.co.kr/zBdC7/viewtopic.php?p=235
http://kelp.or.kr/korweblog/stories.php?story=06/12/14/1451655

이 정도면 충분할 듯.

왜 소스 코드 구현은 위 답변과 다른가? 라는 의문이 있다면

표준 MPEG Decoder 레퍼런스 구현을 이용해서 그렇고,

그 레퍼런스 구현이란 건 최적화 하기 위해 실수 연산을 제거하기 위한 비트 연산으로 대체하기

신공을 사용하기 보단

변환 공식에 따른 미리 계산된 테이블 만들기 신공으로 최적화를 해버렸기 때문.

즉, 변환 공식에 따라 계산하지 않고 y, b, r의 값이 뭐뭐면 결과값은 A라는 식으로 정리된

변환 공식 테이블이라 생각하면 됨.

Visual C++에서 컴파일하려면 cl /Zp2로 정렬 옵션 주면되고,

gcc에서는 어떻게 했더라. __attribute(packed)__ 이렇게 썼던 것 같기도 하네요.(검색하면 나옴)

구현은 C이므로 윈도우, 리눅스, 임베디드 모두 잘 돌아갑니다.

YUV Player 제작에 도움이 되시길. 이해하려 하지 말고 그냥 쓰시길.

ps. 이런 소스 코드 공개에도 망설여야하니,
돈이 개입된 곳은 참 지저분해~

// compile: cl.exe /Zp2     정렬제한을 2로 컴파일

#include <stdio.h>
#include <stdlib.h>

long int tab_76309[256];
long int crv_tab[256];
long int cbu_tab[256];
long int cgu_tab[256];

long int cgv_tab[256];

long int bilevel_tab_76309[256];
unsigned char* clp;

void init_dither_tab();
void init_clp();


void convert( unsigned char *src0, unsigned char *src1,
      unsigned char *src2, unsigned char *dst_ori,
      int width, int height, int imtype );


int main(void)
{
 FILE *in, *out;
 unsigned int picture_size = 176 * 144;
 //int timespan = 1000/atoi(30);
  unsigned char *buffer = (unsigned char *)malloc( 176 * 144 * 3 );
  unsigned char *y_data = (unsigned char *)malloc( 176 * 144 );
  unsigned char *u_data = (unsigned char *)malloc( 88 * 72  );
  unsigned char *v_data = (unsigned char *)malloc( 88 * 72 );
 
  in = fopen( "walk_qcif.yuv", "rb" );
  out = fopen( "walk.rgb", "wb" );
 
  // width * 144 * current_frame_no * 3 / 2
  //  fseek( in, 176 * 144 * 375 * 3 / 2, SEEK_SET );
  fread( y_data, 1, 176 * 144, in );
  fread( u_data, 1, 88 * 72 , in );
  fread( v_data, 1, 88 * 72, in );

  init_dither_tab();
  init_clp();

  convert( y_data, u_data, v_data, buffer, 176, 144, 420 );
 
 fwrite( buffer, sizeof( char ), 176 * 144 * 3, out );
 fclose( in );
 fclose( out );
}

 


// src0 -> y data, src1 -> u data, src2 -> v data
void convert( unsigned char *src0, unsigned char *src1,
      unsigned char *src2, unsigned char *dst_ori,
      int width, int height, int imtype )
{
    int i, j, k, u, v, u2g, u2b, v2g, v2r, y11, y12, y21, y22;
    int width4 = width + (4 - (width%4))%4;
    unsigned char* pucY0, * pucY1, * pucU, * pucV, * pucRaster0, * pucRaster1;
    pucY0 = src0;
    pucY1 = src0 + width;
    pucU = src1;
    pucV = src2;
    pucRaster0 = dst_ori + width4 * (height-1)*3;
    pucRaster1 = pucRaster0 - width4*3;

 switch( imtype )
 {
      case 444:
        for (i = 0; i < height; i++) {
            for (j = 0; j < width; j++) {
                y11 = tab_76309[*pucY0++];
                u = *pucU++;
                v = *pucV++;
                u2g = cgu_tab[u];
                u2b = cbu_tab[u];
                v2g = cgv_tab[v];
                v2r = crv_tab[v];
                *pucRaster0++ = clp[(y11 + u2b)>>16];
                *pucRaster0++ = clp[(y11 - u2g - v2g)>>16];
                *pucRaster0++ = clp[(y11 + v2r)>>16];
            }
            for (;j < width4; j++) {
                for (k = 0; k < 3; k++) {
                    *pucRaster0++ = 0;
                }
            }
            pucRaster0 = pucRaster0 - 2*width4*3;
        }
        break;

  case 422:
        for (i = 0; i < height; i++) {
            for (j = 0; j < width; j+=2) {
                y11 = tab_76309[*pucY0++];
                y12 = tab_76309[*pucY0++];
                u = *pucU++;
                v = *pucV++;
                u2g = cgu_tab[u];
                u2b = cbu_tab[u];
                v2g = cgv_tab[v];
                v2r = crv_tab[v];
                *pucRaster0++ = clp[(y11 + u2b)>>16];
                *pucRaster0++ = clp[(y11 - u2g - v2g)>>16];
                *pucRaster0++ = clp[(y11 + v2r)>>16];

                *pucRaster0++ = clp[(y12 + u2b)>>16];
                *pucRaster0++ = clp[(y12 - u2g - v2g)>>16];
                *pucRaster0++ = clp[(y12 + v2r)>>16];
            }
            for (;j < width4; j++) {
                for (k = 0; k < 3; k++) {
                    *pucRaster0++ = 0;
                }
            }
            pucRaster0 = pucRaster0 - 2*width4*3;
        }
        break;

     case 420:
   for (i = 0; i < height ; i+=2) {
  
            for (j = 0; j < width ; j+=2) {
    
                y11 = tab_76309[*pucY0++];
                y12 = tab_76309[*pucY0++];
                y21 = tab_76309[*pucY1++];
                y22 = tab_76309[*pucY1++];
                u = *pucU++;
                v = *pucV++;
                u2g = cgu_tab[u];
                u2b = cbu_tab[u];
                v2g = cgv_tab[v];
                v2r = crv_tab[v];

                *pucRaster0++ = clp[(y11 + u2b)>>16];
                *pucRaster0++ = clp[(y11 - u2g - v2g)>>16];
                *pucRaster0++ = clp[(y11 + v2r)>>16];
                *pucRaster0++ = clp[(y12 + u2b)>>16];
                *pucRaster0++ = clp[(y12 - u2g - v2g)>>16];
                *pucRaster0++ = clp[(y12 + v2r)>>16];

                *pucRaster1++ = clp[(y21 + u2b)>>16];
                *pucRaster1++ = clp[(y21 - u2g - v2g)>>16];
                *pucRaster1++ = clp[(y21 + v2r)>>16];
                *pucRaster1++ = clp[(y22 + u2b)>>16];
                *pucRaster1++ = clp[(y22 - u2g - v2g)>>16];
                *pucRaster1++ = clp[(y22 + v2r)>>16];

            }
            for (;j < width4; j++) {
                for (k = 0; k < 3; k++) {
                    *pucRaster0++ = 0;
                    *pucRaster1++ = 0;
                }
            }
            pucRaster0 = pucRaster0 - 3*width4*3;
            pucRaster1 = pucRaster1 - 3*width4*3;
            pucY0 += width;
            pucY1 += width;
        }
  break;

      case 24: // 24-bit RGB
        for (i = 0; i < height; i++) {
            for (j = 0; j < width; j++) {
                y11 = *pucY0++;
                u = *pucU++;
                v = *pucV++;
                *pucRaster0++ = v;
                *pucRaster0++ = u;
                *pucRaster0++ = y11;
            }
            for (;j < width4; j++) {
                for (k = 0; k < 3; k++) {
                    *pucRaster0++ = 0;
                }
            }
            pucRaster0 = pucRaster0 - 2*width4*3;
        }
        break;

      case 256: // Gray Level
        for (i = 0; i < height; i++) {
            for (j = 0; j < width; j++) {
                y11 = clp[tab_76309[*pucY0++] >> 16];
                *pucRaster0++ = y11;
                *pucRaster0++ = y11;
                *pucRaster0++ = y11;
            }
            for (;j < width4; j++) {
                for (k = 0; k < 3; k++) {
                    *pucRaster0++ = 0;
                }
            }
            pucRaster0 = pucRaster0 - 2*width4*3;
        }
        break;
 }
 
}

void init_clp()
{
  int i;
  clp = (unsigned char *)malloc(1024);
  clp += 384;

  for (i=-384; i<640; i++)
    clp[i] = (i<0) ? 0 : ((i>255) ? 255 : i);
}

void init_dither_tab()
{
  long int crv,cbu,cgu,cgv;
  int i;  
 
  crv = 104597; cbu = 132201;  /* fra matrise i global.h */
  cgu = 25675;  cgv = 53279;
 
  for (i = 0; i < 256; i++) {
    crv_tab[i] = (i-128) * crv;
    cbu_tab[i] = (i-128) * cbu;
    cgu_tab[i] = (i-128) * cgu;
    cgv_tab[i] = (i-128) * cgv;
    tab_76309[i] = 76309*(i-16);
  }
  bilevel_tab_76309[0] = tab_76309[0];
  for (i = 1; i < 256; i++) {
   bilevel_tab_76309[i] = tab_76309[255];
  }

}

2008. 9. 29. 18:28

컬러 모델

색상을 표현하는 방법을 나타내며 다음과 같은 모델이 사용되어진다.

가. RGB 모델

 - 색상이 Red, Green, Blue의 3색 강도를 통해 표현된다.

 - 컬러 CRT의 R, G, B 전자총에 쉽게 매핑되므로 비디오 디스플레이 드라이버에 편리한 모델

 

나. CMY 모델

 - Cyan, Magenta, Yellow

 - subtractive 모델 [C, M, Y] = [1, 1, 1] - [R, G, B]

 - 컬러 프린터에서 흰색 바탕에 색을 뿌릴 때 사용됨.

 - C + M + Y로 best black을 만들지 못하므로 K(pure black)을 추가(CMYK)

 

다. CIE color space

 - Commission Internationale de I'Eclarage에 의한 규격

 - 휘도(luminance, Y)와 두 색상값(chrominance value; x, y)

 

라. YIQ(YUV, YCrCb) 모델

 - 휘도(luminance) - 색상(chrominance; color difference)

 - YIQ(NTSC), YUV(PAL, SECAM)

 - 흑백 TV와의 호환(Y 신호가 gray level 제공)

 - 변환계

 - Y(luminance: CIE의 Y) = 0.3R + 0.59G + 0.11B

   I(chrominance: orange - cyan hue) = 0.6R - 0.28G - 0.32B

   Q(chrominance: green - magenta hue) = 0.21R - 0.52G + 0.31B

   R = Y + 0.956I + 9.62Q

   G = Y - 0.272I - 0.647Q

   B = Y - 1.108I - 1.705Q

- Y = 0.3R + 0.59G + 0.11B

   U = (B-Y) * 0.493

   V = (R-Y) * 0.877   

   R = Y + 0.956U + 9.621V

   G = Y - 0.272U - 0.647V

   B = Y - 1.1061U - 1.703V

- YCrCb(MPEG)

  Y = 0.3R + 0.59G + 0.11B

  Cr = R - Y

  Cb = B - Y

 

마. HSI 모델

 - 색조(hue), 채도(saturation), 밝기(intensity) 값

 - 영상처리에 적합한 모델