RGBとYCrCbの変換(明度と色差)

BMP(DIB)をはじめとするビットマップデータでは、多くの場合ピクセルの色をRGBの3原色で表すRGB色空間が使われています。

RGBは色の成分を直接混合(加色)することを前提とする色空間ですが、色空間には他に「色」と「明るさ」に分ける方法もあります。

色と明るさを分けることで明るさと色を独立に扱う事ができるため、明るさだけを増減したり明るさを変えないで色のバランスを変える、といった処理も可能になります。今回は、24ビットフルカラーDIBRGB成分を明度YCr,Cbという二つの「色差」に分けるYCrCbに変換してみることにしました。

RGBとYCrCbの対応

RGB形式のフレームバッファ(ビットマップ)をYCrCbに変換するには、各画素に対応するRGBから「明るさ」と「色差」を計算する必要があります。その式は、以下のとおりです。

Y= 0.299R+0.587G+0.114B
Cr= 0.500R-0.419G-0.081B
Cb= -0.169R-0.332G+0.500B

 このうちYは、グレースケールで表す時の明度ですね。このYでピクセルの「明るさ」を、そしてCrCbで色を表す、と。逆に、YCrCbからRGBを求めるには、上の式を行列で表して係数の逆行列を求めれば良いので以下のようになります。

R= Y+1.402Cr
G= Y-0.714Cr-0.344Cb
B= Y+1.772Cb

YCrCb変換処理

では、実際に各ピクセルのRGBYCrCbに変換してみましょう。

今回は、YCrCbそれぞれに8ビットずつ割り当てる事にします。上の式を見るとCrCbは0を中心にプラス・マイナス両方向に広がりそうですね。これを0-255の範囲に対応させるため、計算結果に128を加算したのちに0-255に丸めることにしましょう。


  y=0.299*r+0.587*g+0.114*b;
  cr=0.5*r-0.419*g-0.081*b+128;
  cb=-0.169*r-0.332*g+0.5*b+128;

  if (y<0)
      y=0;
  if (y>255)
      y=255;

  if (cr<0)
      cr=0;
  if (cr>255)
      cr=255;

  if (cb<0)
      cb=0;
  if (cb>255)
      cb=255;

プログラムでは、この処理をビットマップの各ピクセルに対して行います。今回は、元のDIBを24ビットフルカラーBMPから作成するので、YCrCbへの変換結果もビット数では同じです(ただし、誤差が入るため画質は劣化するはず)。プログラムでは、ビットマップを読みこんだらそのビットマップを変換してカレントディレクトリのtest.yuvというファイルに保存する事にしました。ファイルの構造は、先頭8バイトに縦横のサイズ(それぞれ4バイト)が入りその後に変換した結果のYCrCbビット列をそのまま書き出します。

yuvファイルの読み込み

では、次にこうして作ったyuvファイルを読みこんでみましょう。

まず先頭2ダブルワードでビットマップの大きさを取得したらその大きさの24ビットフルカラーDIBを作り、yuvファイルのピクセル列からDIBのピクセルを作成します。各ピクセルのRGBは、y/cr/cbの値を変換式に当てはめて算出するだけです。

読み込んだファイルの先頭をlpWork、サイズがdwWidth×dwHeightDIBのピクセル列の先頭をlpPixelとすれば以下のようになりますね。


  for (i=0;i<dwHeight;i++)
      for (j=0;j<dwWidth;j++) {

          y=lpWork[j*3+i*dwWidth*3+8];
          cr=lpWork[j*3+i*dwWidth*3+9]-128;
          cb=lpWork[j*3+i*dwWidth*3+10]-128;

          r=y+1.402*cr;
          g=y-0.714*cr-0.344*cb;
          b=y+1.772*cb;

          if (r<0)
              r=0
          if (r>255)
              r=255;

          if (g<0)
              g=0
          if (g>255)
              g=255;

          if (b<0)
              b=0
          if (b>255)
              b=255;

          lpPixel[j*3+i*dwLength]=(BYTE)b;
          lpPixel[j*3+i*dwLength+1]=(BYTE)g;
          lpPixel[j*3+i*dwLength+2]=(BYTE)r;

      }

変換の時にCrCbに128を足したので、RGBへの変換処理の前に128を引いています。

プログラム

実行したら、ウインドウに適当なビットマップファイルをドロップしてください。ビットマップの各ピクセルがRGBからYCrCbに変換され、test.yuvというファイルが作成されます。このファイルをウインドウにドロップするとRGBに復元されて表示されるので、続いて元のビットマップをドロップして画像を比べてみましょう。

プログラムソース表示

今回は、YCrCb各8ビットで処理したため画質が劣化しやすくなっていますが、それぞれ16ビット(48ビットYCrCb)で処理する形にすれば、画質の劣化を最小限に抑えて明度や彩度などに対する画像処理を行えるようになるでしょう。