2006. 9.23.
石立 喬

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

――3×3画素のオペレータを用いた画像の平滑化、鮮鋭化、線画化――


 「易しい画像処理(4)」までで紹介した、ピクセル毎の単独処理とは別に、近傍ピクセル値を含めて演算を行う画像処理がある。この代表的なものに、コンボリューションと呼ぶ処理がある。 コンボリューションは、原画像の平滑化(ノイズを除去したり、軟調化したりする)、鮮鋭化(ボケた画像をシャープにする)、エッジ検出(線画のように輪郭を抽出する)などに広く用いられる。

コンボリューション(Convolution)
 コンボリューションは、「畳み込み」と訳され、図1のように、原画像とカーネル(Kernel、核心部)またはオペレータ(Operator、演算子)の間で積和演算を行うことである。Java2 Standard Edition 6.0 には、ConvolveOpクラスがあり、制約はあるものの、使いやすいメソッドがある。.NET Framework 2.0 には、このようなクラスが用意されていないので、コンボリューション用のメソッドを自作した。


図1 コンボリューションの方法


 オペレータには、用途に応じて色々なマトリックスが用いられる。図2は、その一例で、他にも縦方向、横方向専用のエッジ検出などがあり、ガウスぼかしには、σに応じてさらに大きいマトリックスも用いられる。



図2 オペレータの一例


プログラムの概要
  プログラムは、次の部分からなっている。
◎Form1起動時に実行するもの
1) 原画像を読み込む。
2) 原画像から二次元のピクセルデータ(Color構造体の)を取得する。
3) 周辺処理のために、原画像の二次元ピクセルデータを拡張(四辺を外側にコピー)する。
4) 平滑化オペレータでコンボリューションする。
5) 鮮鋭化オペレータでコンボリューションする。
6) エッジ検出オペレータでコンボリューションする。
7) 上記のエッジ検出結果に対して、R、G、B各成分のいずれかが閾値(ここでは20を使用)を超える場合は濃度dを0(黒)に、それ以外は255(白)に設定する。
◎Form1が画面に表示される度に実行するもの
 原画像、平滑化画像、エッジ検出画像を描画する。

プログラム

private:
   /// <summary>
   /// 必要なデザイナ変数です。
   /// </summary>
   System::ComponentModel::Container ^components;

   static int WIDTH=320,HEIGHT=240;

   Bitmap^ bmap_src;
   Bitmap^ bmap_blur;
   Bitmap^ bmap_sharp;
   Bitmap^ bmap_edge;

private: System::Void Form1_Load(System::Object^ sender, System::EventArgs^ e) {

   int i,j;
   Color color;
   int r,g,b,d;

   bmap_blur=gcnew Bitmap(WIDTH,HEIGHT);
   bmap_sharp=gcnew Bitmap(WIDTH,HEIGHT);
   bmap_edge=gcnew Bitmap(WIDTH,HEIGHT);

   array<Color,2>^ pdata=gcnew array<Color,2>(WIDTH,HEIGHT);       //原画像のピクセルデータ
   array<Color,2>^ pdata1=gcnew array<Color,2>(WIDTH+2,HEIGHT+2);   //拡張画像のピクセルデータ

   array<double,2>^ operator1={{0.11,0.11,0.11},    //平滑化
                     {0.11,0.12,0.11},
                     {0.11,0.11,0.11}};

   array<double,2>^ operator2={{ 0.0,-0.3, 0.0},    //鮮鋭化
                     {-0.3, 2.2,-0.3},
                     { 0.0,-0.3, 0.0}};

   array<double,2>^ operator3={{ 0.0,-1.0, 0.0},    //エッジ検出
                     {-1.0, 4.0,-1.0},
                     { 0.0,-1.0, 0.0}};

   //画像ファイルを読み込んで原画像とする
   bmap_src=gcnew Bitmap("A:/sample.jpg");

   //Bitmapを二次元PixelDataにする
   for(j=0;j<HEIGHT;j++)
      for(i=0;i<WIDTH;i++)
         pdata[i,j]=bmap_src->GetPixel(i,j); //二次元配列の添え字の書き方に注意!!

   //二次元PixelDataを拡張する
   //中心部
   for(j=0;j<HEIGHT;j++)
      for(i=0;i<WIDTH;i++)
         pdata1[i+1,j+1]=pdata[i,j];
   //上辺と下辺
   for(i=0;i<WIDTH+2;i++){
      pdata1[i,0]=pdata1[i,1];
      pdata1[i,HEIGHT+1]=pdata1[i,HEIGHT];
      }
   //左辺と右辺
   for(j=0;j<HEIGHT+2;j++){
      pdata1[0,j]=pdata1[1,j];
      pdata1[WIDTH+1,j]=pdata1[WIDTH,j];
   }

   //拡張PixelDataとKernelでコンボリューションする
   bmap_blur=makeConvolution(pdata1,operator1);     //平滑化
   bmap_sharp=makeConvolution(pdata1,operator2);    //鮮鋭化
   bmap_edge=makeConvolution(pdata1,operator3);    //エッジ検出

   //エッジ検出画像を閾値を用いて反転
   for(j=0;j<HEIGHT;j++)
      for(i=0;i<WIDTH;i++){
         color=bmap_edge->GetPixel(i,j);
         r=color.R;
         g=color.G;
         b=color.B;
         if(r>20 || g>20 || b>20)  d=0;
         else             d=255;
         bmap_edge->SetPixel(i,j,Color::FromArgb(d,d,d));
      }

}

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

   Graphics^ gr=e->Graphics;

   int X0=10,Y0=10;
   int X1=340,Y1=260;

   //原画像を描画する
   gr->DrawImage(bmap_src,X0,Y0,WIDTH,HEIGHT);

   //加工画像を描画する
   gr->DrawImage(bmap_blur,X1,Y0);    //平滑化
   gr->DrawImage(bmap_sharp,X0,Y1);   //鮮鋭化
   gr->DrawImage(bmap_edge,X1,Y1);    //エッジ検出

}

private: Bitmap^ makeConvolution(array<Color,2>^ pdata1,array<double,2>^ opr){

   int i,j,m,n;
   Color color;
   int r,g,b;
   double r1,g1,b1;
   Bitmap^ bmap=gcnew Bitmap(WIDTH,HEIGHT);

   //拡張PixelDataとKernelでコンボリューションする
   for(j=0;j<HEIGHT;j++)
      for(i=0;i<WIDTH;i++){
         r1=0.0;g1=0.0;b1=0.0;
         for(n=-1;n<=1;n++)
            for(m=-1;m<=1;m++){
               color=pdata1[i+m+1,j+n+1];
               r1+=color.R*opr[m+1,n+1];
               g1+=color.G*opr[m+1,n+1];
               b1+=color.B*opr[m+1,n+1];
            }

         r=(int)r1;
         if(r<0)       r=0;
         else if(r>255)   r=255;
         g=(int)g1;
         if(g<0)       g=0;
         else if(g>255)  g=255;
         b=(int)b1;
         if(b<0)       b=0;
         else if(b>255)  b=255;

         bmap->SetPixel(i,j,Color::FromArgb(r,g,b));

      }

   return bmap;

}


得られた結果
 図3の左上は原画像で、意図的にソフトな画像を使用している。右上は原画像を平滑化した画像で、さらにソフトになっている。左下は、鮮鋭化した画像で、原画像がソフトにもかかわらず、十分シャープに見える。右下は、原画像のエッジを検出し、一定量以上の場所を黒く表示している。簡単なエッジ検出オペレータを使用したので、十分満足な画像が得られたとは言えない。



図3 得られた結果