2006.9,2.
2006.9.13.追補
2007.1.29.訂正(原画像ファイルの読み込み)
2007.5.13.追補(SetPixelの使い方)
石立 喬

Visual C++ 2005 Express Editionを用いた易しい画像処理(1)

――原画像を読み込み、RGBとCMYの三色に分解する――


 技術者にとって、画像処理は自作プログラムを応用する主要な分野の一つである。画像処理のシリーズを始めるに当たり、最初に、画像処理の対象となる原画像を取り込み、RGBとCMYの三色に分解する方法を説明する。これにより、画像データの仕組みが理解できる。
 従来のVisual C++ 6.0の場合と異なり、画像処理に便利なクラスやメソッドが多数用意されているので、これを使用するとプログラムが格段と容易に作成できるようになった。
 すでに公開されている「Visual C++ 2005 Express Editionの易しい使い方」シリーズに述べてあることは省略されていることが多いので、それらを十分参照してほしい。

画像処理の対象として使用する原画像を準備する
 画像処理を行うには、元となる原画像が必要である。パソコン画面から考えると、画像処理の結果も合わせて表示しようとすると、原画像のサイズは、320×240程度が適当である。形式はBMP、JPEG、PNG、GIFのいずれでも良い。
 原画像ファイルをImageクラスの画像にするには(下記の例はFDDから読み込む場合)、
   Image^ img=Image::FromFile(”A:/sample.jpg”)
などを使用する。
 Bitmapクラスにするには、コンストラクタを用いて、
   Bitmap^ bmap=gcnew Bitmap("A:/sample.jpg");
などを使用する。
 ただし、RGBへの分解など、ピクセル単位での操作には、Bitmapクラスが便利である。

 「A:\」のように「\」を使用すると、エスケープシーケンスと認識されてエラーになる場合が時々見られた(常時ではないが不思議)ので、そのような場合には、「\」を省くか、「\」の代わりに「/」や「\\」を用いて欲しい。
 「マイ ドキュメント/Visual Studio 2005/Projects/<プロジェクト名>/<プロジェクト名>」に原画像ファイルを置いておけば、フルパス指定の必要はない。

Form上への表示
 Imageクラスの画像に対しては、
   g->DrawImage(img,X0,Y0);
でForm上に簡単に描画することができる。
 Bitmapクラスの画像の場合も、
   g->DrawImage(bmap,X0,Y0);
で同様に描画できる。X0とY0は、画像の左上の座標である。

画像の各ピクセルを読み取り、それらをRGB三色に分解する
 上述の方法で取得したBitmapクラスの画像bmapをピクセル単位で読み取るには、ピクセル当りのColor構造体を下記のようにして取得し、
   Color color=bmap->GetPixel(i,j);
得られたcolorをRGBに分解するには、
   r=color.R;
   g=color.G;
   b=color.B;
を使用する。i、jは画像のX座標、Y座標である。

RGBからColor構造体を生成する
 r、g、bのデータからColor構造体のcolorを生成するには、
   Color color=Color::FromArgb(r,g,b);
を使用する。

画面上に点を打つ
 Visual C++ 6.0では、pdC->SetPixel(x,y,color)が使えたが、Visual C++ 2005 Express editionでは使えない。代わりに
   g->DrawRectangle(pen,x,y,1,1);
で、一辺が長さ1の矩形を描くか(辺の長さを0にすると描画されない)、
   g->DrawLine(pen,x,y,x+1,y+1);
で長さ1の線を引く(線の長さを0にする、すなわち、x,y,x,yとすると描画されない)。
 これは不便なことで、画像のサイズが少し大きくなる(縦横共に1ピクセル分だけ)欠点がある。
 しかし、直接描画するのではなく、
   Bitmap^ bmap=gcnew Bitmap(width,height);
   bmap->SetPixel(x,y,Color::Black);
   g->DrawImage(bmap,X0,Y0;
とすれば、SetPixelが使える。

プログラムの作成方法(念のために)
1)「Visual C++ 2005 Express Edition」統合開発環境で、「ファイル」→「新規作成」→「プロジェクト」を選択する。
2)「新しいプロジェクト」ウインドウで、
  プロジェクトの種類 ---- CLR
  テンプレート --------- Windowsフォームアプリケーション
  プロジェクト名 -------- (任意の名称を入力する)
  ソリューションのディレクトリの作成 --- (チェックのついたままにしておく)
として、「OK」をクリックする。
3)「フォームデザイナ」ウインドウが開き、「Form1」だできているので、その中央部で右クリックし、「プロパティ」を選択する。
4)「プロパティ」ウインドウで、
  Size ---------- 600,600(後で細かく調整するが、とりあえず)
  BackColor ----- Window
  Text --------- 色の分解(一例)
と設定する。
5)同じ「プロパティ」ウインドウで、稲妻の描かれた「イベント」アイコンをクリックし、「Paint」をダブルクリックする。
6)「コードエディタ」が開き、Form1_Paintのスケルトンができているので、そこにプログラムを記述する。

ビルドと実行(念のために)
1) 統合開発環境のメニューで、「ビルド」→「(プロジェクト名)のビルド」をクリックする。
2) ビルドが正常終了した場合には、「デバッグ」→「デバッグなしで開始」をクリックする。

プログラムA
 以下は、原画像sample.jpgをFDDから読み込み、R、G、Bの各成分を表示するプログラムである。

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

   int X0=10,Y0=10;
   int X1=340,Y1=10;
   int X2=10,Y2=260;
   int X3=340,Y3=260;

   Color color_src,color_dest;
   Byte r,g,b;
   Pen^ pen;

   Graphics^ gr=e->Graphics;

   //原画像のビットマップを生成する
   Bitmap^ bmap=gcnew Bitmap("A:sample.jpg");

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

   //原画像をRGBに分解し、表示する
   for(int j=0;j<240;j++)
      for(int i=0;i<320;i++){
         color_src=bmap->GetPixel(i,j);
         r=color_src.R;
         g=color_src.G;
         b=color_src.B;

         //R成分
         color_dest=Color::FromArgb(r,0,0);
         pen=gcnew Pen(color_dest);
         gr->DrawRectangle(pen,X1+i,Y1+j,1,1);

         //G成分
         color_dest=Color::FromArgb(0,g,0);
         pen=gcnew Pen(color_dest);
         gr->DrawRectangle(pen,X2+i,Y2+j,1,1);

         //B成分
         color_dest=Color::FromArgb(0,0,b);
         pen=gcnew Pen(color_dest);
         gr->DrawRectangle(pen,X3+i,Y3+j,1,1);
      }

}

プログラムB
 以下は、プログラムAの一部を変更し、印刷で使われるCMY系への分解を行った例である。実際には、黒を含むCMYKが使用されるが、ここでは省略した。変更部分のみを示す。

//原画像をYMCに分解し、表示する
for(int j=0;j<240;j++)
   for(int i=0;i<320;i++){
      color_src=bmap->GetPixel(i,j);
      r=color_src.R;
      g=color_src.G;
      b=color_src.B;

      c=255-r;
      m=255-g;
      y=255-b;

      //C成分
      color_dest=Color::FromArgb(0,c,c);
      pen=gcnew Pen(color_dest);
      gr->DrawRectangle(pen,X1+i,Y1+j,1,1);

      //M成分
      color_dest=Color::FromArgb(m,0,m);
      pen=gcnew Pen(color_dest);
      gr->DrawRectangle(pen,X2+i,Y2+j,1,1);

      //Y成分
      color_dest=Color::FromArgb(y,y,0);
      pen=gcnew Pen(color_dest);
      gr->DrawRectangle(pen,X3+i,Y3+j,1,1);

   }

プログラムAで得られた画面
 RGBへの分解は、真っ黒のディスプレイ上にそれらを加算して元に戻すので、白い部分は、R、G、Bともに最大の強さとなっている。すなわち、赤、緑、青が共に最も強い部分が白である。




プログラムBで得られた画面
 CMYへの分解は、真っ白の印刷用紙上にそれらを塗り重ねて元に戻すので、白い部分は、C、M、Yともにゼロ(真っ黒、すなわち塗らない)となっている。黒い部分ほど色を濃く塗るので、ネガ状になる。