OpenCVで学ぶ画像認識

第3回オブジェクト検出してみよう

第1回第2回と画像認識の基礎とOpenCVについて紹介してきました。第3回目の今回は、いよいよ本連載の目玉であるOpenCVを使ったオブジェクト検出に挑戦してみます。

オブジェクト検出の仕組み

基本原理のおさらい

オブジェクト検出のプログラムを書き始める前に、そもそもどんな仕組みでオブジェクト検出を行っているのかを理解しましょう。 第1回では画像認識の原理として、学習フェーズと認識フェーズがあることを説明しましたが、OpenCVに実装されているオブジェクト検出プログラムもこの流れに従います。つまり、画像から特徴量を抽出し、学習アルゴリズムによってオブジェクトを学習します(詳しくは第1回を参照してください⁠⁠。

図1 画像認識の流れ
図1 画像認識の流れ

OpenCVに実装されているオブジェクト検出プログラムは、Paul Violaらのオブジェクト検出の研究[1]をベースに、Rainer Lienhartらが改良した研究[2]が実装されています。これらの研究では、画像特徴量としてHaar-Like特徴量、学習アルゴリズムとしてAdaboostと呼ばれるアルゴリズムを使用しており、非常に高速にオブジェクト検出が行えます。

なお、以下で説明するアルゴリズムは、考え方を伝えるためにかなり乱暴に簡略化しています。詳しいやり方に興味のある方は、以下の原著を読んでみてください。

Adaboost

OpenCVのオブジェクト検出器は、学習にAdaBoost(エイダブースト、またはアダブースト)と呼ばれる方法を用いています。 AdaBoostは一つひとつはあまり判別能力の高くない(ただしランダムに判定するよりは若干ましという程度の)弱い識別器を、たくさん繋げることで強い識別器を作るという考え方の、学習・判別アルゴリズムです。強い識別器は、弱い識別器をその重要度にあわせて重みをつけて、それらを組み合わせたものになります。

図2 Adaboostの概要
図2 Adaboostの概要

例えば、ある画像が車かどうかを判断する識別器を作りたいとします。車かどうかを判定させるために、画像中に丸があるかを判断する識別器や、三角があるかを判断する識別器、四角があるかどうかを判断する識別器など、単純な図形に反応する複数の識別器を用意したとしましょう。

ここに車の画像を入力したところ、タイヤなどのせいで丸を判定する識別器が強い出力を返しました。その場合、丸を判定する識別器は車かどうかを判定する上で重要なので、重みを重くします。次に車以外の画像を入力(例えばオニギリの画像)した際、三角の識別器が反応したとします。この場合は三角の識別器の重みを小さくします。

このような処理を繰り返すことで、車を入力すると強く反応し、車以外を入力すると反応しない強い識別器を作成することができます。

このように、正解画像と非正解画像、及び教師信号(画像が正解画像か不正解画像かを教える信号)を与えて学習をさせると言うやり方はほとんどの機械学習で一般的な方法です。

弱識別器とHaar-Like特徴量

それでは次に、画像からどのような特徴量を取得するかを考えます。OpenCVのオブジェクト検出器で用いている特徴の例を下の図に示しました。

図3 オブジェクト検出特徴の例
図3 オブジェクト検出特徴の例

入力画像上の任意の場所に、図のような検出窓をおいた場合、検出窓の中の画像の特徴量は、以下の式で計算されます。

特徴量 = 矩形特徴の黒領域の画素値の合計 - 矩形特徴の白領域の画素値の合計

この矩形領域の白黒パターンはHaar-Like(ハールライク)特徴と呼ばれるもので、以下のパターンを用意しています。

図4 矩形領域のパターン
図4 矩形領域のパターン

このように、検出窓内の矩形の位置とパターンの組み合わせによって、約12万通りのオブジェクト検出特徴が用いられます。 この特徴パターン一つ一つが、AdaBoostの弱識別器の役割を果たします。

例えば、オブジェクト検出特徴を顔画像の上においた場合を考えます。

図5 検出特徴を顔に適用したの例
図5 検出特徴を顔に適用したの例

通常の顔画像では、目の領域の画素は周辺よりも暗いと考えられるので、上図のような検出特徴の特徴量は高くなることが予想されます。このような特徴に対しては、AdaBoostによって強い重みがつけられます。

このような検出窓で画像中をスキャンして、その都度強い識別器で判別していくことで、オブジェクトの検出を行うことができます。

なお、Haar-Like特徴はちょっとした工夫で非常に早く計算することが可能です。このHaar-Like特徴を用いたことと、次に説明するAttentional Cascadeを用いたことにより、OpenCVのオブジェクト検出プログラムはリアルタイムに動作することができます。

Attentional Cascade

通常、検出窓で画像内をスキャンした場合、オブジェクトがみつかるよりもオブジェクト以外が見つかる方がはるかに確率が高いです。そのため、"いかに非オブジェクトの検出窓を拒否するか"というのが高速化の鍵になります。

そこでOpenCVのオブジェクト検出では、ここまでの説明で作成した強い識別器を複数連結(カスケード)することで、精度と速度を改善しています。

図6 Attentinal Cascade
図6 Attentinal Cascade

この図のように識別器を連結させたものを、Attentional Cascadeといいます。

画像上にある検出窓を設けた時、1つめの識別器で非オブジェクトと判断されたものは、それ以上の処理は行われずに終了します。もし、1つめでオブジェクトとして判断された場合は、2つめの識別器へ渡されます。同様に、2つめの識別器で非オブジェクトであると判断されたものは弾かれ、それ以外の画像が3つめの識別器へ渡されます。

つまり、上流の識別器では緩い判断基準で次々と疑わしい画像を排除していき、下流にいくほどオブジェクトの判断基準が厳しくなるように調整されています。

これらの識別器は、12万個の特徴パターンのうち、通常上流ほどわずかな数の特徴パターンを使用しているため、高速に非オブジェクト画像を弾くことできます。

以上が、OpenCVで用いられているオブジェクト検出の仕組みになります。

顔検出プログラム

というわけで、ようやく実際のオブジェクト検出プログラムの話に入ります。OpenCVにはあらかじめ、学習された「顔」情報が用意されているため、まずはこれを使用して顔検出プログラムを書いてみましょう。

下のプログラムで使用されている、学習済み顔画像⁠haarcascade_frontalface_default.xml⁠は、OpenCVインストールディレクトリ下の⁠data\haarcascades⁠にありますので、コピーをするかプログラム中でパスを指定するようにしてください。

顔検出プログラム
int main(int argc, char* argv[])
{
  /* 画像のロード */
  char imgfile[] = "office.jpg";
  IplImage* image = cvLoadImage( imgfile, 1 );

  /* 正面顔検出器のロード */
  CvHaarClassifierCascade* cascade = (CvHaarClassifierCascade*)cvLoad( "haarcascade_frontalface_default.xml" );

  CvMemStorage* storage = cvCreateMemStorage(0);
  CvSeq* faces;
  int i;

  /* 顔検出 */
  faces = cvHaarDetectObjects( image, cascade, storage );

  /* 顔領域の描画 */
  for( i = 0; i < faces->total; i++ )
  {
    /* extract the rectanlges only */
    CvRect face_rect = *(CvRect*)cvGetSeqElem( faces, i );
      cvRectangle( image, cvPoint(face_rect.x,face_rect.y),
      cvPoint((face_rect.x+face_rect.width),
        (face_rect.y+face_rect.height)),
        CV_RGB(255,0,0), 3 );
  }

  /* 画像の表示 */
  cvReleaseMemStorage( &storage );
  cvNamedWindow( "face_detect", 0 );
  cvShowImage( "face_detect", image );
  cvWaitKey(0);
  cvReleaseHaarClassifierCascade( &cascade );
  cvReleaseImage( &image );

  return 0;
}

重要なポイントだけ解説致します。まず、

CvHaarClassifierCascade* cascade = (CvHaarClassifierCascade*)cvLoad( "haarcascade_frontalface_default.xml" );

で、正面顔を学習させたカスケード識別器とそのパラメータをXMLファイルから読み込みます。⁠haarcascade_frontalface_default.xml⁠が学習済みの顔データになります。OpenCVで用意されている構造体などの情報は、cvLoadやcvSaveによって簡単にファイルへの読み込み/書き込みが可能です。

次にcvHaarDetectObjects()関数を使って画像から検出し、facesにというCvSeq構造体ポインタへ検出した顔の位置情報を渡します。

最後に、cvGetSeqElem関数で、facesから一つ一つ顔領域を矩形データとして取得し、それをcvRectangle()で描画します。

実行の結果、次のような結果が出れば成功です。

図7 顔検出結果
図7 顔検出結果

終わりに

いかがだったでしょうか? 今回は、OpenCVで用いられているオブジェクト検出アルゴリズムと、実装の仕方について解説しました。画像認識が具体的にどのような流れで行われているのかのイメージを掴んでいただけたら幸いです。

次回は、いよいよ最終回。自分たちでコンピュータに対してオブジェクトを学習させることで、オリジナル検出器を作ってみましょう。

おすすめ記事

記事・ニュース一覧