2010.12. 11.
石立 喬

Visual C++ 2010 Expressを用いた易しい画像処理(2)

――原画像のピクセル毎のRGBデータを加工する――


 画像処理を大きく分けると、原画像のピクセルの色情報を単独で処理するものと、注目するピクセルとその周辺のピクセル相互の関係を利用して処理するものとに分けられる。前者には、コントラスト、色相、彩度、明度の変更があり、後者には平滑化、鮮鋭化などがある。
 ここでは、ピクセル値を単独で個別に変換する画像処理の一つである「ガンマ補正」について紹介する。画像全体のヒストグラムを採り、それによって自動的に個別のピクセルの補正を行う方法(別項で後述する)と比べ、効果は限定的である。

ガンマ(Gamma)特性とガンマ補正
 
ガンマ特性とは、べき乗の形で表現される特性を言う。当初は、ブラウン管の特性を表すTV用語であった。すなわち、入力輝度信号電圧と、蛍光体の発する輝度との関係がガンマ乗の関係にあり、そのガンマ値は約2.2であった。
 それ以来、ガンマ特性とは、入力と出力の関係を表す一般用語となり、特に、「ガンマ補正」は写真用語としても広く使われるようになった。画像処理的に言うと、画像を構成する各点のピクセル濃度を一定の関係式により変換することである。

トーンカーブ(Tone Curve)
 トーンは調子、階調のことである。トーンカーブは、画像ソフトなどで、明るさやコントラストを調整する際に用いる入出力関係を表す関数を図示したもので、ガンマ補正の具体的な方法を示す。

ここで使用するピクセル値変換の原理

 図1にトーンカーブを示すように、角度0ラジアンからπ/2ラジアンの間の三角関数を用いて、RGB各成分の濃度変換を行うと、比較的簡単にガンマ補正ができる。図で左の濃度変換はsin関数を用いたもので、低濃度部を持ち上げて暗部をより明るくする。中央は、-cos関数により高濃度部を押し下げて明部をより暗くする(これは、sin関数に置き換えることもできる)。右は、sin二乗関数を用いたもので、暗部はより暗く、明部はより明るくして中濃度部の濃淡(コントラスト)を増強する。


図1 三角関数を用いた濃度変換


ガンマ補正の効果

 一般のカラー画像は、RGB各色から成り、RGB個別にガンマ補正をすると、色調が変わってしまう恐れがある。ガンマ補正の効果が適切なのは、やはりグレイ(白黒)画像であり、グレイの階調に対して、上記の変換を行ったのが図2である。「低濃度部拡張画像」は、低濃度部(暗部)を見やすくするために、全体に明るい画像になっていて、明部は飛んでしまっている。「高濃度部拡張画像」は、高濃度部(明部)を見やすくするために、全体的に暗い画像になっていて、暗部は潰れてしまっている。「中濃度部拡張画像」は、明部は飛び、暗部は潰れているが、メリハリの利いた画像になっている。


図2 ガンマ補正の効果(プログラムにより作成、付録参照)


プログラム

private: System::Void Form1_Paint(System::Object^ sender, System::Windows::Forms::PaintEventArgs^ e) {

   Graphics^ gr=e->Graphics;

   int WIDTH=320,HEIGHT=240;
   int X0=10,Y0=10;
   int X1=WIDTH+20,Y1=10;
   int X2=10,Y2=HEIGHT+20;
   int X3=WIDTH+20,Y3=HEIGHT+20;

   Color color_src,color_dst;
   Byte r,g,b,r1,g1,b1;

   Bitmap^ bmap_expand_low=gcnew Bitmap(WIDTH,HEIGHT);
   Bitmap^ bmap_expand_high=gcnew Bitmap(WIDTH,HEIGHT);
   Bitmap^ bmap_expand_mid=gcnew Bitmap(WIDTH,HEIGHT);

   //濃度変換テーブルを用意する
   array<int>^ expand_low=gcnew array<int>(256);
   array<int>^ expand_high=gcnew array<int>(256);
   array<int>^ expand_mid=gcnew array<int>(256);
   for(int i=0;i<256;i++){
      expand_low[i]=(int)(255*Math::Sin(Math::PI/(2*255)*i));
      expand_high[255-i]=255-expand_low[i];
      expand_mid[i]=(int)(expand_low[i]*expand_low[i]/255.0f);
   }

   //原画像のBitmapを生成する
   Bitmap^ bmap_src=gcnew Bitmap("source.JPG");

   //原画像を左上に表示する
   gr->DrawImage(bmap_src,X0,Y0);

   //原画像をRGBに分解し、補正して新しく画像を構成する
   for(int j=0;j<HEIGHT;j++)
      for(int i=0;i<WIDTH;i++){
         color_src=bmap_src->GetPixel(i,j);
         r=color_src.R;
         g=color_src.G;
         b=color_src.B;

         //expand_low変換
         r1=expand_low[r];
         g1=expand_low[g];
         b1=expand_low[b];
         color_dst=Color::FromArgb(r1,g1,b1);
         bmap_expand_low->SetPixel(i,j,color_dst);

         //expand_high変換
         r1=expand_high[r];
         g1=expand_high[g];
         b1=expand_high[b];
         color_dst=Color::FromArgb(r1,g1,b1);
         bmap_expand_high->SetPixel(i,j,color_dst);

         //expand_mid変換
         r1=expand_mid[r];
         g1=expand_mid[g];
         b1=expand_mid[b];
         color_dst=Color::FromArgb(r1,g1,b1);
         bmap_expand_mid->SetPixel(i,j,color_dst);
      }

   gr->DrawImage(bmap_expand_low,X1,Y1);
   gr->DrawImage(bmap_expand_high,X2,Y2);
   gr->DrawImage(bmap_expand_mid,X3,Y3);

}

得られた結果
 図3で、左上は原画像、右上は暗部の階調を拡大した画像、左下は明部の階調を拡大した画像、右下は中間部の階調を拡大し、コントラストを強調した画像である。画像のどの部分の改善を期待するかによって、評価は分かれる。


図3 得られた画面


付録:ガンマ補正の効果を示す図2を作成したプログラム


   Color color1;
   Pen^ pen1;
   Byte d;

   gr->FillRectangle(Brushes::Yellow,0,0,390,210);        //黄色の背景を描く
   gr->DrawString("原画像",Font,Brushes::Black,310,25);
   gr->DrawString("低濃度部拡張画像",Font,Brushes::Black,280,75);
   gr->DrawString("高濃度部拡張画像",Font,Brushes::Black,280,125);
   gr->DrawString("中濃度部拡張画像",Font,Brushes::Black,280,175);

   for(int i=0;i<256;i++){
      //原画像の濃度
      color1=Color::FromArgb(i,i,i);
      pen1=gcnew Pen(color1);
      gr->DrawLine(pen1,10+i,10,10+i,50);
      //低濃度部拡張濃度
      d=expand_low[i];
      color1=Color::FromArgb(d,d,d);
      pen1=gcnew Pen(color1);
      gr->DrawLine(pen1,10+i,60,10+i,100);
      //高濃度部拡張濃度
      d=expand_high[i];
      color1=Color::FromArgb(d,d,d);
      pen1=gcnew Pen(color1);
      gr->DrawLine(pen1,10+i,110,10+i,150);
      //中濃度部拡張濃度
      d=expand_mid[i];
      color1=Color::FromArgb(d,d,d);
      pen1=gcnew Pen(color1);
      gr->DrawLine(pen1,10+i,160,10+i,200);
   }


「Visual C++ の勉強部屋」(目次)へ