ITU-R BT.601 정의에 따르면

Y 변환식은
Y = 0.299R + 0.587G + 0.114B
계수값들이 눈에 거슬린다. 왜 저런 값이 나왔을까?
우리 눈은 녹색>빨강>파랑 순으로 밝다고 느낀다. Y 값의 목적은 밝기를 나타내는 것이기 때문에
계수 선정은 이 사실에 기초해야 한다. G값 성분비율을 크게 하고 그 다음에 빨강, 파랑.
이렇게 얻은 값을 gray로 나타내면 흑백이 된다.
나도 한번 새로운 계수를 만들어 볼까?
계수의 합이 1이 되게하고 계수값을 적당히 조정해서 gray로 나타냈을때
앞의 계수보다 보기 좋은 흑백이 나타난다면 그 계수를 사용하면 될것이다.
표준으로 저 값을 쓴다고 하니 써야지 뭐...물론 테스트 된 최적값이겠지...
R,G,B 값의 범위를 [0,1] 했을때 Y의 범위는 [0,1]이다.

 

U 변환식은
U = B - Y
  = (1-0.114)B - 0.299R - 0.587G
  = 0.886B - 0.299R - 0.587G

R,G,B 값의 범위를 [0,1] 했을때 U의 범위는 [-0.886, +0.886] 이다.
즉 B 계수 값의 +/-이다.

 

V 변환식은
V = R - Y
  = (1-0.299)R - 0.587G - 0.114B
  = 0.701R - 0.587G - 0.114B      

R,G,B 값의 범위를 [0,1] 했을때 V의 범위는 [-0.701, +0.701] 이다.
즉 R 계수 값의 +/-이다.


정리하자면 R,G,B의 범위가 [0,1]일 경우

[식 1]
Y = 0.299R + 0.587G + 0.114B   Y의 범위는 [0,1]
U = 0.886B - 0.299R - 0.587G   U의 범위는 [-0.886, +0.886]
V = 0.701R - 0.587G - 0.114B   V의 범위는 [-0.701, +0.701]

 

 

////////////////////////////////////////////////////////////////////////////////
위 식의 문제점은 값의 범위가 일정하지 않다는 점이다. 값의 범위를 스케일링 해보자.

U, V의 범위를 [-0.5, +0.5] 로 스케일링 해보자.
U, V 문자를 쓰는 대신 Pb, Pr로 쓰자. (TV 뒤를 보면 이런문자를 보았나요^^)
U의 범위는 B의 계수 값이 결정하므로 각 계수들을 0.5/0.886 로 곱한다.
Pb = 0.5B -0.169R -0.331G
V의 범위는 R의 계수 값이 결정하므로 각 계수들을 0.5/0.701 로 곱한다.
Pr = 0.5R -0.419G -0.081B


정리하자면 아래식은 R,G,B의 범위가 [0,1]로 스케일링된 식이다.

[식 2]
Y  =   0.299R + 0.587G + 0.114B   Y의 범위는   [0,1]
Pb = -0.169R - 0.331G + 0.500B   Pb의 범위는 [-0.5, +0.5]
Pr =   0.500R - 0.419G - 0.081B   Pr의 범위는  [-0.5, +0.5]

 

 

////////////////////////////////////////////////////////////////////////////////
Y,Pb,Pr의 범위 [16, 240]로 스케일링 해보자. 변환된 Pb, Pr 대신 Cb, Cr로 쓰자.

Y,Pb,Pr의 범위를 적용하면 쉽게 아래식을 유도한다.

 

Y  = 16 + 219*Y
Cb = 128 + 224*Pb
Cr = 128 + 224*Pr

 

이식이 맞는지 조사해보자. 

예를들어 하나만 해보면 Pb의 범위가 [-0.5, +0.5]이므로 Pb의 최소값이 -0.5이면 Cb값은 16이 된다.

R,G,B의 범위를 [0,1] 대신 [0,255]로 하려면 R 대신 R/255, G 대신 G/255, B 대신 B/255로 쓰면 된다.

 

정리하자면 아래식은 R,G,B의 범위는 [0,255],  Y,Cb,Cr의 범위는 [16, 240] 로 스케일링 된 식이다.

[식 3]

Y  = 16 + 219*Y/255      =     0.257R + 0.504G + 0.098B + 16
Cb = 128 + 224*Pb/255  =   -0.148R - 0.291G + 0.439B + 128
Cr = 128 + 224*Pr/255   =     0.439R - 0.368G - 0.071B + 128

 

역변환 식은
R = 1.164(Y-16)                        + 1.596(Cr-128)
G = 1.164(Y-16) - 0.391(Cb-128) - 0.813(Cr-128)
B = 1.164(Y-16) + 2.018(Cb-128)

이 식은 유명한(??) 외국책에 나온다. MSDN에서도 이 식을 사용하는데 Cb,Cr 문자를 사용안하고

Y,U,V 문자를 사용한다. 그래서 헷갈리는것 같다.

 

 

 

////////////////////////////////////////////////////////////////////////////////
Y, Pb, Pr의 범위를 [0.255]로 할수 없을까? 당연히 할수 있다. 변환된 Pb, Pr 대신 U, V로 쓰자.

Y,Pb,Pr 식을 이용하여 변환하면 된다.

 

아래식은 R,G,B 범위를 [0,255]로, Y,U,V 범위 [0,255]로 스케일링 된 식이다.

[식 4]

Y =   0.299R + 0.587G + 0.114B           Y의 범위는 [0, +255]
U = -0.169R - 0.331G + 0.500B + 128    U의 범위는 [0, +255]
V =   0.500R - 0.419G - 0.081B + 128   V의 범위는 [0, +255]

 

역변환 식은
R = Y                        + 1.4075(V-128)
G = Y - 0.3455(U-128) - 0.7169(V-128)
B = Y + 1.7790(U-128)

 

결국 4가지의 식이 존재하는것은 모두 스케일링이 달라서 이다. 

 

참고로 ITU-R BT.709 정의

Y = 0.2125R + 0.7154G + 0.0721B  이식은 HD tv에서 쓴다고 한다. G값 비율을 높혔네...

[출처] YUV, YCbCr (1)|작성자 늘푸른숲

 

 

 

 

변환 공식중 다음 공식에서 float 연산을 빼서 계산을 빠르게 만들어 보자

 

Y   =     0.257R + 0.504G + 0.098B + 16
Cb  =    -0.148R - 0.291G + 0.439B + 128
Cr  =     0.439R - 0.368G - 0.071B + 128

 

윗식에서 반올림을 할수 있게 0.5를 더하자.


Y   =     0.257R + 0.504G + 0.098B + 0.5 + 16
Cb  =    -0.148R - 0.291G + 0.439B + 0.5 + 128
Cr  =     0.439R - 0.368G - 0.071B + 0.5 + 128


윗식에서 float 연산을 없애기 위해서 256을 곱하고 256으로 나누자

Y   =    (256*( 0.257R + 0.504G + 0.098B + 0.5 ))/256 + 16
Cb  =    (256*(-0.148R - 0.291G + 0.439B + 0.5 ))/256 + 128
Cr  =    (256*( 0.439R - 0.368G - 0.071B + 0.5 ))/256 + 128

 

 

256 나누기는 shift 연산으로 대치한다.

Y   =    ( 256*( 0.257R + 0.504G + 0.098B + 0.5 ) ) >> 8 + 16
Cb  =    ( 256*(-0.148R - 0.291G + 0.439B + 0.5 ) ) >> 8 + 128
Cr  =    ( 256*( 0.439R - 0.368G - 0.071B + 0.5 ) ) >> 8 + 128

 

 

연산을 하고 정리하면

Y   =    ( (  66*R + 129*G +  25*B + 128 )  >> 8  ) + 16
Cb  =    ( ( -38*R -  74*G + 112*B + 128 )  >> 8  )  + 128
Cr  =    ( ( 112*R -  94*G -  18*B + 128 )  >> 8  ) + 128

 

아래의 역변환 식도 변환해보자.

R = 1.164(Y-16)                        + 1.596(Cr-128)
G = 1.164(Y-16) - 0.391(Cb-128) - 0.813(Cr-128)
B = 1.164(Y-16) + 2.018(Cb-128)

 

역변환 식도 정리하면

R =  ( 298*(Y-16)                + 409*(Cr-128) + 128 ) >> 8
G =  ( 298*(Y-16) - 100*(Cb-128) - 208*(Cr-128) + 128 ) >> 8
B =  ( 298*(Y-16) + 516*(Cb-128)                + 128 ) >> 8

 

R, G, B 의 값이 [0,255] 범위에 오도록 클립한다.

#define CLIP(x) ( ((x) < 0 ) ? 0 : (((x) > 255 ) ? 255 : (x) ))

 

 

R =  CLIP( ( 298*(Y-16)                       + 409*(Cr-128) + 128 ) >> 8 )
G =  CLIP( ( 298*(Y-16) - 100*(Cb-128) - 208*(Cr-128) + 128 ) >> 8 )
B =  CLIP( ( 298*(Y-16) + 516*(Cb-128)                       + 128 ) >> 8 )


위 공식은 MSDN에 나온다.

 

더 빠르게 계산하는 방법을 생각해 보자. 방법은 lookup 테이블을 이용.

 

//전역변수에 미리 값을 계산하자.

int table_298[256];
int table_409[256];
int table_100[256];
int table_208[256];
int table_516[256];

// 클립자체도 테이블을 만들자.
unsigned char Temp_Clip[1024];
unsigned char * Clip;

 

 

// 프로그램 시작 위치에 다음 코드를 넣자

   int i;
   Clip = Temp_Clip + 384;

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

 

   for( i = 0 ; i < 256 ; i++ )
   {
    table_298[i] = 298*(i-16) + 128;
    table_409[i] = 409*(i-128);
    table_100[i] = 100*(i-128);
    table_208[i] = 208*(i-128);
    table_516[i] = 516*(i-128);
   }

 

 

 

// 만들어지 테이블값을 참조만 하여 값을 얻는다.

   R =  Clip[ ( table_298[Y]                      + table_409[Cr] ) >> 8 ] ;
   G =  Clip[ ( table_298[Y] - table_100[Cb] - table_208[Cr] ) >> 8 ] ;
   B =  Clip[ ( table_298[Y] + table_516[Cb]                       ) >> 8 ] ;

lookup 테이블을 이용하니까 10ms->5ms 로 반절정도로 계산시간이 줄었다.

 

또 한가지. 정밀도를 높이기 위해서 8bit shift 연산 대신 16bit shift 연산을 생각해 볼수 있다.  

계수값이 달라지겠지만 충분히 유도가능하다.

[출처] YUV RGB 변환 (2)|작성자 늘푸른숲

 

 

 

 

 

RGB의 범위가 [0,255]
Y  =   0.299R + 0.587G + 0.114B   Y의 범위는   [0,255]
Cb =  -0.169R - 0.331G + 0.500B   Cb의 범위는 [-127.5, +127.5]//-128 ~ 127 로 조율해야 한다.
Cr =   0.500R - 0.419G - 0.081B   Cr의 범위는  [-127.5, +127.5]//-128 ~ 127 로 조율해야 한다.

빠른 변환을 위해 LUT를 쓰자.

반올림/반내림을 위해 ±0.5를 해주고 우변을 256으로 곱했다가 나눈다.

Y  =   (0.299R + 0.587G + 0.114B + 0.5) * 256 / 256
Cb =  (-0.169R - 0.331G + 0.500B - 0.5) * 256 / 256
Cr =   (0.500R - 0.419G - 0.081B - 0.5) * 256 / 256

나누기 256은 shift연산으로 바꾸고 식을 정리하면

Y  =   (77R + 150G + 29B + 128) >> 8
Cb =  (-43R - 85G + 128B - 128) >> 8
Cr =   (128R - 107G - 21B - 128) >> 8

//전역변수에 미리 값을 계산하자.
int table_077[256];
int table_150[256];
int table_029[256];
int table_043[256];
int table_128[256];
int table_085[256];
int table_107[256];
int table_021[256];

for( i = 0 ; i < 256 ; i++ )
{
    table_077[i] = 77 * i;
    table_150[i] = 150 * i;
    table_029[i] = 29 * i;
    table_043[i] = 43 * i ;
    table_128[i] = 128 * i;
    table_085[i] = 85 * i;
    table_107[i] = 107 * i;
    table_021[i] = 21 * i;
}

테이블만 참조해서 계산하면 된다.
Y  =   (table_077[R] + table_150[G] + table_029[B] + 128) >> 8
Cb =  (-table_043[R] - table_085[G] + table_128[B] - 128) >> 8
Cr =   (table_128[R] - table_107[G] - table_021[B] - 128) >> 8

[출처] YUV, YCbCr|작성자 늘푸른숲