2015. 2. 13.
石立 喬

OpenCVとVisual C++による画像処理と認識(11)

----- ORBを用いて特徴点のマッチングを行う -----

 特許の制約がない高速のORBクラスを用いて、特徴点のマッチングを行った。まず最初に、単純な幾何学的図形に対してマッチングを実施して優れた性能を確認し、次いで、道路標識の認識の一助として使えないかを試してみた。ORBをデフォルト条件で使うと期待通りの結果が得られないので、デフォルトと詳細設定の場合との違いを調べた。

特徴点のマッチング
 特徴点のマッチングは、下記の順に行われる。
 1)特徴点検出(Feature Detectorを使う) ----------- 「画像処理と認識(10)」ですでに述べた
 2)特徴記述(Descriptor Extractorを使う) ---------- 特徴点に対して特徴量記述子(descroptor)を計算する
 3)特徴点マッチング(Descriptor Matcherを使う) ---- 記述子を比較して近いものからマッチングさせる
 これらの作業は、複数の画像上の物体を対応付けることであり、物体の追跡や認識に役立つ。

特徴記述とは
 特徴点検出によって特徴点がどこにあるかが分かると、次に、それらを調べて特徴を詳しく記述し、マッチングの判断材料にする。記述した結果は記述子(descriptor)と呼ばれ、記述子を計算することを抽出する(extract)と呼ぶ。具体的には、ORBの場合、特徴点の周辺に31x31のピクセルパッチを設け、その中でさらに小さい5x5の領域を二か所選んで平均輝度情報の大小関係を集め、それをランダムに繰り返して、距離計算が容易なバイナリ―コードの32バイトで表現する。このパッチサイズが、デフォルトとして31になっているため、画像の周辺近くの(31ピクセル以内の)特徴点検出・記述ができない欠点がある。ここでは、あえてこの値を小さく設定して使用した部分があるが、特に問題は生じなかった。

プログラムと得られた結果
  ここでは、プログラムを書き換えたり、追加したりして、色々な実験を行ったので、その推移を以下に示す。それぞれの図に示した部分的なプログラムは、つなぎ合わせて使用できる。
◎二つの同サイズの画像内にある各図形間のマッチングをとる
 1)最初に、detectorやextractorをデフォルトのままとし、同画像のマッチングについて調べた。
      プログラム ---- 図1  結果 ---- 図2
 2)各画像の周辺部のコーナー検出ができなかったので、「画像処理と認識(10)」に倣ってdetectorを設定し、extractorも、それに合わせた。
      プログラム ---- 図3  結果 ---- 図4
 3)同じ画像では正しくマッチングがとれたので、回転画像、鏡像画像、ランダムに配置換えした画像で調べた。
      結果 ---- 図5、図6、図7
 4)ランダム画像では、一部に偽マッチングが見られたので、マッチング距離にしきい値を与えてスクリーニングした。
      プログラム ---- 図8  結果 ---- 図9
◎小さなクエリ画像が、大きなサイズの訓練画像中のどの図形と一致しているかを求める
 5)画像から、三角形を取り出し、それをクエリ画像とし、従来の画像を訓練画像としてマッチングを取った。
      プログラム ---- 図10  結果 ---- 図11
 6)実際の道路標識を前処理した結果を用いて、マッチングを試みた。
      結果 ---- 図12
6)マッチングを一層明確に表示するように、マッチング点を赤丸で表示させた。
      プログラム ---- 図13  結果 ---- 図14

使用したOpenCV関数の説明
◎OrbDescriptorExtractor
 ORBクラスは、特徴点検出器(keypoint detector)と特徴量記述子抽出器(descriptor extracter)の両者を兼ね備えている。メソッドdetect()を使えば検出器になり、メソッドcalculete()を使えば抽出器として使える。しかし、これらを区別できるように、OrbFeatureDetectorとOrbDescriptorExtractorがtypedefで別名宣言されているので、ここでは、これらの別名を用いて使い分けをする(実質的には、ORBを使うのと全く同じである)。
 したがって、OrbDescriptorExtractorのコンストラクタの引数は、OebFeatureDetectorと全く同じで、コンストラクタの引数は、

   OrbDescriptorExtractor extractor(最大検出数=500、ピラミッドレイヤー間の縮小比率=1.2f、ピラミッドレベル数=8、エッジしきい値=31、最初のレベル=0、WTA_K=2、スコアタイプ=0、パッチサイズ=31);

となり、すべての引数にデフォルトが設定されている。エッジしきい値は、画像の周辺から、この距離だけ内側でないと検出できないことを意味する。スコアタイプは、デフォルトの0がORB::HARRIS_SCOREで、1はORB::FAST_SCOREである。パッチサイズは、デフォルトの31が強く要求されている。
 OrbFeatureDetectorとしてdetector1を設定してあるとすると、

 OrbDescriptorExtractor extractor(detector1);

でdetector1の設定を引き継がせることができる。
◎DMatch構造体
 後述のBFMatcherによるマッチング結果のデータを入れる構造体で、パラメータは次の通り。
  int queryIdx ------ クエリ(質問)画像の特徴記述子番号
  int trainIdx ------- 訓練画像の特徴記述子番号
  int imgIdx -------- 訓練画像の番号(複数画像を使用する場合がある)
  float distance ----- マッチング距離(記述子間の特徴のHamming距離、異なったビットの数)
がある。
◎BFMatcherクラス
 ORBはバイナリ―コードを使用のため、FlannBasedMatcherは使用できず、マッチング検出器としてはこれのみとなる。
 コンストラクタは、

  BFMatcher matcher(ノルムタイプ=NORM_L2、クロスチェック=false);

である。ノルムタイプのNORM_L2はSIFT,、SURF用で、ORBを使い、デフォルトのWTA_K = 2を使う場合には、ノルムタイプをNORM_HAMMINGに設定する。クロスチェックをtrueにすると、反対方向からもマッチングを確認するので、不適切なマッチングを除外できる。
 あとは、

  matcher.match(クエリ画像の記述子、訓練画像の記述子、マッチング結果のデータ、マスク= Mat());

で、DMatch型のマッチング結果データを得る。
◎drawMatches関数
 DMatchで得たマッチング結果を用いて描画する。引数は下記の通り。

  drawMatches(画像1、画像1の特徴点、画像2、画像2の特徴点、マッチング結果のデータ、結果出力画像、マッチング点の丸と線の色=Scalar::all(-1)、マッチングしない点の丸の色=Scalar::all(-1)、描画法= 0);

 描画法には、
  DEFAULT = 0 ------------------- マッチングしない点もすべて描画
  DRAW_OVER_OUTIMG = 1 --------- 既存の画像上に描画する
  NOT_DRAW_SINGLE_POINTS = 2 --- マッチングしない点は描画しない
  DRAW_RICH_KEYPOINTS = 4 ------ キーポイントの強度、方向を詳しく描画する
があり、一般的にはデフォルトで十分である。

 図1は、必要最小限で最初に作成したプログラムで、すべてデフォルト条件で使用している。二つの画像は同じsample.pngを使用した。得られた結果を図2に示す。エッジしきい値が31のため、画像周辺部のコーナーが検出されてなく、コーナーがない筈の円にコーナーがある。これは、「画像処理と認識(10)」でも知られていたことである。


図1 最初に作成したdefault条件のプログラム



図2 デフォルト条件では特徴点検出が十分でない


 そこで、「画像処理と認識(10)」で使用した結果を参考に、detector1とdetector2を詳細設定してみたが、期待の結果が得られなかった。結局、extractorもデフォルトでは不都合であることが分かり、detector2の設定を引き継がせて、うまくいった。
 ORBをFeatureDetectorとして使用する場合には、第4引数のエッジしきい値と第8引数のパッチサイズを同じにするのが好ましいとされ、一方、DescriptorExtractorとして使用する場合は、第8引数は、ほぼ絶対的にデフォルトの31が要求されるので、第4引数は7に、第8引数は31にして苦渋の妥協を図った。
 のちに、サイズの小さいクエリ画像用にdetector1を書き換える予定があり、現在使っているサイズの訓練画像はdetector2をそのまま使用するので、extractorは、detector2の設定を引き継がせている。
 結果として、図3に示すように条件設定をすると、図4のようなほぼ満足できる結果が得られた。


図3 detectorとextractorをデフォルトでなく条件設定したプログラム



図4 同じ図形を並べてマッチングを取ったらうまく行った


 ほぼ良好に条件設定できたと判断したので、sample.pngを回転させたり、鏡像反転させたり、ランダムに配置したりしたものを、同じプログラムで試してみた。その結果を図5~図7に示す。ORBの記述子は、回転不変性(rotation invariant)が長所で、回転図形には非常に強い。鏡像画像、回転画像、ランダム画像も、回転画像としてマッチング判断したものを正しいとすると、優秀な結果が得られた。


図5 回転させた画像とマッチングを取った結果(非常に良好である)



図6 鏡像関係の画像とマッチングを取った結果(回転画像としての判断が多い)



図7 ランダムに並べ替えた画像とマッチングを取った結果


 ランダムの場合は、近似度の低い(Hamming距離の離れた)特徴点も含まれているのではないかと考え、DMatch構造体のdistanceパラメータにしきい値を与え、選択的に表示させるようにした。そのプログラムが図8で、結果を図9に示す。試行錯誤でしきい値を決めたが、小さくすると正しいマッチングが消え、大きくすると偽マッチングが増えるので、条件設定が難しく、顕著な改善は得られていない。


図8 distanceの小さいもの(よく似たもの)を抜き出すプログラム



図9 偽マッチングの数は減ったが、良好なマッチングも減った


 以上のプログラムは、同サイズの画像に描かれている図形間の比較を行ったものであるが、これからは、物体認識の手始めとして、図形の探索を試みる。
 従来使ってきた画像sample.pngを訓練画像(すでに何であるかが分かっている学習用画像)として使用し、別の小さい画像に図形を1種類だけ描いたものをクエリ画像(これは何かを問い合わせる未知画像)とする。OpenCVでは、drawMatches関数でマッチング結果を描画する場合、左側がクエリ画像、右側が訓練画像となっているので、sample.pngを訓練画像src_image2、sample画像から1個の三角形を取り出したtriangle.pngをクエリ画像src_image1とする。
 このため、図10のように、プログラムの最初の部分を書き換えた。detector1の最大検出数を10に引き下げ、ピラミッドレイヤー間比率を1.35fに上げ、第8引数を7に下げて、ようやくクエリ画像での正しいコーナー検出が得られた。


図10 クエリ画像のために変更したプログラム


 図11は得られた結果で、良好な結果と言いたいところであるが、ORBの回転不変性が災いして、右側の訓練図形の二つの三角形の両方にマッチングしてしまった。訓練画像の三角形が一個であればうまくマッチングが取れた筈である。


図11 訓練画像の二個の三角形にマッチングした


 最後に、同じプログラムを使って道路標識を試してみた結果が図12で、やや不満足な結果になった。赤で囲まれた「止まれ」の文字が、特徴として重要な役割を果たしたのは興味がある。


図12 「止まれ」のクエリ画像が「進入禁止」ともマッチングしてしまった


 マッチング点を線で結ぶのではなく、赤丸で表示すると分かり易いと考え、マッチング結果のデータmatchesのプロパティである訓練画像のマッチング番号trainIdxを使って、図13のプロプラムを追加したところ、図14の結果が得られた。


図13 マッチング点を描画するプログラム



図14 直線で邪魔されないで、マッチング点がはっきり示された


画像の周辺を広く取っての再試行

 ORBは、小さい画像が苦手のようである。ORBコンストラクタ第4引数のエッジしきい値は、画像周辺部(エッジ)の特徴点を検出できない範囲のサイズとなっていて、デフォルトでは31である。すなわち、「画像の周辺31ピクセルは特徴点を検出できません」と言っているのである。これに逆らって、これまで、プログラムを作成してきたので、ORBの性能を十分引き出せていないのではないかと、疑問になってきた。
 そこで、画像の周辺を広くした画像を用い、ORBの設定をデフォルトのままにして、同様のマッチン処理を試してみた。
 図15は、OrbFeatureDetectorをデフォルト設定で使用して特徴点を検出させた結果で、図2と比べて、すべてのコーナーが確実に検出されている。デフォルトなので、最大検出数が大きく、余分なコーナーも多く含まれている。
 図4~図7と同じマッチングテストの結果はほぼ同等で、特徴点の数が多い分だけ、すこし線が込み合っていた。


図15 周辺を広げた効果で、コーナーが完全に検出されている


 道路標識のマッチングでは、デフォルト設定ではあまり良い結果が得られなかった(図16参照)。訓練画像の特徴点が多すぎたのが主な原因である。やむを得ず、エッジしきい値とパッチサイズはデフォルト値のままとし、その他を微細に調節して図17の結果を得た。


図16 デフォルト設定では使えない



図17 細かく設定して少し良くなった


結 論
 OpenCVでの特徴点のマッチング性能を実感するために、同じ画像のマッチングを調べた。そのまま並べた場合、180°回転させた場合は、完全に正しくマッチングが得られた。鏡像画像、回転画像、ランダムに並べ替えた画像に対しては、回転画像としてのマッチングが多く、これらを正解とすると、十分良好な成績が得られた。クエリ画像を用意して、訓練画像とマッチングさせる試みでは、訓練画像に類似性の高い特徴点を持った図形がある場合は区別が難しく、比較的単純な三角形ではうまくいった。
 実際の道路標識である「止まれ」で試したところ、特徴点を絞り込み過ぎたせいもあり、あまり良い結果は得られなかった。ただしマッチング数を確率的に判断すれば、良いと言えるのかもしれない。
 ORBをデフォルト設定で使用するのには無理がある。最大検出数を減らして同一個所付近に複数の特徴点が検出されるのを防ぐ一方で、ピラミッドのレイヤー比を細かく調節して検出漏れを防いぐ必要がある。レイヤー数は4~5が良かった。小さい画像では、エッジしきい値を減らすのが絶対的に必要で、これに対する悪影響は特に感じられなかった。パッチサイズは、デフォルトの31のままがマッチング精度が良く、それ以下にすると性能が下がるが、致命的と言うほどではなかった。
 最後に一言付け加えると、特徴点が適切に検出されていれば、マッチングは大体うまくゆく。

参考文献
 ORBの特徴点記述子計算方法は、BRIEFに回転不変性を与えたものなので、BRIEFの文献が役に立つ。
 M.Calonder, V.Lepetit, C.Strecha and P.Fua, "BRIEF: Binary Robust Independent Elementary Features"(2010)
 http://cvlabwww.epfl.ch/~lepetit/papers/calonder_eccv10.pdf
 M.Calonder, V.Lepetit, M.Ozuysa and ,"BREIF: Computing a Local Binary Descriptor Very Fast"(2011)
 http://infoscience.eplf.ch/record/167678/files/top.pdf





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