2007.2.26.
石立 喬

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

―――― テンプレートマッチング法を用いた顔画像の検索 ――――


 与えられた原画像の中から、特定の顔、特定の車両、または一般的な人間の顔や車などを探し出すのにも画像処理が用いられる。これは画像処理を画像認識に応用するもので、監視装置やデジカメにも用いられている。

概要
 画像の中から特定のパターンを探し出すには、テンプレートマッチング法が良く用いられる。テンプレートとは型紙のことで、それを画像上で移動させながら比較して行くやり方である。ただし、テンプレートに対して傾斜を持っている画像や、相似形であるが大きさが異なる画像も対象にしようとすると、面倒な処理と時間が必要になる。
 ここでは、最も簡単な方法について紹介するので、あまり実用的とは言えないが、これを基にして改善して行くと良い。ここで解説したプログラムでは、二値化やマッチングの判定条件などに試行錯誤的な部分があるので、これらの自動化を図るのも面白い。

顔の検索
 テンプレートマッチングの応用で、比較的良く用いられるのは、人の顔の検索である。最近のデジカメには、自動的に顔を探し出して、そこにピントを合わせるものがあり、画像処理ソフトで赤目修正ができるものもある。顔の検索には、単に人の顔であることを認識すれば良いものから、特定の個人を認識するまで、必要とされる精度によって技術レベルに幅があり、パターン認識の学会などで盛んに議論される分野である。
 最近はほとんどがカラー画像から検索するため、顔を抽出するには、まず肌色の検出が行なわれる。人種によって肌色は異なるが、主な使用目的に対して試行錯誤的に決定する。次は、眼を探すことが多く、横方向に二個並んでいる特徴を利用する。

ここで使用したテンプレートマッチング
 テンプレートマッチングで非常に高い一致を望むことは出来ない。人の顔の場合、個性があり、顔の向き、傾き、光線の具合などでバラツキが生じる。ここで使用した例では、テンプレートの大きさ(29 x 38= 1102ピクセル)に対して、770ピクセル(約70%)を越える一致で顔と判断している。これにより、顔以外のものが顔として認識される偽陽性(false-positive)の恐れも存在する。

プログラムの説明
 プログラムは、次の部分からなっている。
1) 被検出原画像とテンプレート画像を、それぞれBitmapとしてファイルから読み込む。
2) テンプレート画像を二値データとして配列temp[i,j]に格納する。
3) テンプレートマッチングの前処理として、被検出原画像に対して下記を実行する。
  ・ RGB系からHSV(またはHSB)系に変換し、明度Brightnessの閾値を110(0〜255段階の)として、二値画像bmap_binを得る。
  ・ 同様に、色相Hueを5(0〜360度の)以上45(0〜360度の)以下の範囲で抽出し、二値画像bmap_hueを得る。
  ・ 上記二つの画像の白部分のANDを取り、被検出二値画像bmap_destを得る。
4) 原画像、明度二値画像、色相二値画像、被検出二値画像およびテンプレート画像をパソコン画面上に表示する。
5) テンプレートマッチングを実行する。
  ・ テンプレートのデータと被検出二値画像のデータをスキャンし、もし一致すると、一致した数のカウンタmatch_numberをインクリメントする。
  ・ match_numberが770を越えると、それが顔であると認識し、一致した範囲を赤色の矩形で表示する。
6) すべての範囲のマッチング比較が終了したら、「検出終了」と表示する。

プログラム

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

   Graphics^ gr=e->Graphics;

   int WIDTH=320,HEIGHT=240;   //被検出画像のサイズ
   int TWIDTH=29,THEIGHT=38;   //テンプレート画像のサイズ

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

   int i,j,m,n;
   int d,d1,d2;
   float h,v;
   int match_number;

   Color color1;
   array<int,2>^ temp=gcnew array<int,2>(29,38);   //テンプレートデータ用配列

   Bitmap^ bmap_bin=gcnew Bitmap(WIDTH,HEIGHT);    //二値化画像
   Bitmap^ bmap_hue=gcnew Bitmap(WIDTH,HEIGHT);    //顔色検出画像
   Bitmap^ bmap_dest=gcnew Bitmap(WIDTH,HEIGHT);   //最終画像

   //原画像を読み込む
   Bitmap^ bmap_src=gcnew Bitmap("A:/faces.jpg");
   //テンプレート画像を読み込む
   Bitmap^ bmap_temp=gcnew Bitmap("A:/template.gif");

   //テンプレートデータの配列を用意する
   for(j=0;j<THEIGHT;j++)
      for(i=0;i<TWIDTH;i++){
         color1=bmap_temp->GetPixel(i,j);
         if(color1.R==0)  temp[i,j]=0;      //黒
         else          temp[i,j]=255;    //白
      }

   for(j=0;j<HEIGHT;j++)
      for(i=0;i<WIDTH;i++){
         color1=bmap_src->GetPixel(i,j);

         //Brightnessを取得する
         v=color1.GetBrightness();
         //Hueを取得する
         h=color1.GetHue();

         //Brightnessを二値化する
         d1=(int)(255*v);
         if(d1>110) d1=255;
         else d1=0;
         color1=Color::FromArgb(d1,d1,d1);
         bmap_bin->SetPixel(i,j,color1);

         //顔の色を抽出する
         if(h<45.0f && h>5.0f) d2=255;
         else d2=0;
         color1=Color::FromArgb(d2,d2,d2);
         bmap_hue->SetPixel(i,j,color1);

         //論理積をとる
         d=Math::Min(d1,d2);
         color1=Color::FromArgb(d,d,d);
         bmap_dest->SetPixel(i,j,color1);

      }

   //原画像を表示する
   gr->DrawImage(bmap_src,X0,Y0,WIDTH,HEIGHT);
   //二値化Brightness画像を表示する
   gr->DrawImage(bmap_bin,X1,Y1,WIDTH,HEIGHT);
   //顔色抽出Hue画像を表示する
   gr->DrawImage(bmap_hue,X2,Y2,WIDTH,HEIGHT);
   //顔色検出用画像を表示する
   gr->DrawImage(bmap_dest,X3,Y3,WIDTH,HEIGHT);
   //テンプレート画像を表示する
   gr->DrawImage(bmap_temp,X4,Y4,TWIDTH,THEIGHT);

   //テンプレートマッチングの実行
   for(j=0;j<HEIGHT-THEIGHT;j++)
      for(i=0;i<WIDTH-TWIDTH;i++){
         //マッチング検出
         match_number=0;
         for(n=0;n<THEIGHT;n++)
            for(m=0;m<TWIDTH;m++){
               color1=bmap_dest->GetPixel(i+m,j+n);
               if(color1.R==temp[m,n]) match_number++;    //一致した
            }
         //一致したピクセルが770個以上
         if(match_number>770)
         gr->DrawRectangle(Pens::Red,X3+i,Y3+j,TWIDTH,THEIGHT);
      }

   //検出終了を表示
   System::Drawing::Font^ font1=gcnew System::Drawing::Font("MS ゴシック",10);
   gr->DrawString("検出終了",font1,Brushes::Black,X4+100,Y4);

}

得られた結果
 検出閾値match_number=770とした場合の画面を図1に示す。左上は原画像、右上は明度Brightnessによって得た二値画像、左下は色相Hueによって肌色を抽出した二値画像、右下は、それらを総合した被検出二値画像である。被検出画像の上には、テンプレートマッチングによって検出された「顔」が赤色で囲まれている。光線による明暗が影響して、認識精度を悪くしていることが解る。


図1 実行画面の例


 図2は、閾値match_number=740と、すこし条件をゆるくした場合を示す。「顔」として正しく検出された数は増えてなく、顔以外の部分が「顔」として誤って認識されている(false-positive)。条件を緩めても、一部が隠れている顔の検出は厳しいようである。


図2 閾値を緩和した例