2016. 9.17.
2017. 9.30. OpenCV3.3.0とVisual C++ 2017で確認済み、このままのプログラムで使用可
石立 喬

OpenCV3.1.0とVisual C++ 2015による画像処理と認識(11)

----- BRISK、ORB、KAZE、AKAZEを用いて特徴点のマッチングを採る -----

 「OpenCVとVisual C++による画像処理と認識(11)」では、ORBについてのみのマッチンを試みたが、ここでは特徴点検出・記述器(feature detector / descriptor)の種類を増やし、従来からあるBRISK、ORBクラスと、OpenCV3.0から追加されたKAZE、AKAZEクラスを用いて、特徴点のマッチングを採った。

2D Features Frameworkモジュールの構成が、OpenCV3.1.0で大きく変わった
 OpenCV2.4とOpenCV3.0はほとんど同じであったが、OpenCV3.1.0になって大きく変わった。すなわち、OpenCV2.4とOpenCV3.0では、
  features2d(2D Features Framework)モジュール構成が、
    Feature Detection and Description
    Common Interface of Feature Detectors
    Common Interface of Descriptor Extractors ---- DescriptorExtractorクラスをcreateし、記述子をcomputeする
    Common Interface of Generic Descriptor Matchers
    Drawing Function of Keypoints and Matches
    Object Categorization
となっていたが、OpenCV3.1.0では、
    Feature Detection and Description ---- 各特徴点検出クラスをcreateし、computeする(またはcreateAndCompute)
    Descriptor Matchers
    Drawing Function of Keypoints and Matches
    Object Categorization
のみとなって、Common Interface類が無くなり、DescriptorExtractorを使わないで記述子の計算ができるように簡単化された。
 使用できる特徴点検出・記述器は、標準モジュール(main module)使用の場合、
  OpenCV2.4 ------ SIFT、SURF、BRIEF、BRISK、ORB、FREAK
  OpenCV3.0 ------ BRISK、ORB
  OpenCV3.1.0 ----- BRISK、ORB、KAZE、AKAZE
となっている。OpenCV3.0になって、SIFT、SURFは拡張モジュール(extra module)に移された。

特徴点のマッチング
 特徴点のマッチングは、下記の順に行われる。
 1)特徴点検出・記述器で特徴点検出と同時に特徴量記述子(feature descriptor)を計算する ---- detectAndCompute関数を使う
 2)記述子マッチャ―(descriptor matcher)でマッチングをとる ---- match関数を使う
 3)必要に応じ、良好なマッチングを選び出す ----- radiusMatch関数が使えるが、簡単なコードで実装可能
 3)マッチングを描画する ------- drawMatches関数を使う

特徴点のマッチングに使用したプログラム(良好なマッチングのみでなく、すべてを描画)
 図1に、使用したプログラムを示す。このプログラムは、下記から成っている。
 1)二つの画像を読み込む。OpenCVでは、第一の画像をクエリ(query)画像、第二の画像を訓練(train)画像と呼ぶ。
      query ------ 第二の画像のどれに一致するか?問い合わせる意
      train ------- 訓練用で、何であるかすでに分かっている意
 2)特徴点検出・記述器を生成する。この際、「OpenCV3.1.0とVisual C++ 2015による画像処理と認識(10)」の詳細設定を使用
   具体的には、
      BRISK::create(120, 3, 0.6f)
      ORB::create(80, 2.25f, 4, 7)
      KAZE::create(false, false, 0.005f)
      AKAZE::create()
   を使用する。
 3)二画像について、特徴点keypointsと記述子descriptorを求める。
 4)BFMatcherクラスのmatcherを生成する。この際、使用する特徴点検出・記述器に応じて評価法(NORM_HAMMINGなど)を設定する。
 5)match関数(メソッド)でマッチング結果matchesを求める。
 6)drawMatches関数でマッチングを表示する。

使用したOpenCVクラス、関数などの説明
◎DMatch構造体
 後述のBFMatcherによるマッチング結果のデータを入れる構造体で、パラメータは次の通り。
  int queryIdx ------ クエリ(問合せ)画像の特徴記述子番号
  int trainIdx ------- 訓練画像の特徴記述子番号
  int imgIdx -------- 訓練画像の番号(複数画像を使用する場合に使用)
  float distance ----- マッチング距離(記述子間の特徴のHamming距離またはユークリッド二乗距離)
がある。
◎BFMatcherクラス
 コンストラクタは、
  BFMatcher(
    normType = MORM_L2 ----- int、ORB、BRISK、AKAZEには、normTypeとしてNORM_HAMMINGを使用する
    crossCheck = false ------- bool、trueにすると、両サイドから見て再近傍の点のみが得られる
  )
 match関数は、
  match(
    queryDescriptors ---- InputArray、クエリ画像のMat型の記述子descriptors
    trainDescriptors ----- InputArray、訓練画像のMat型の記述子descroptors
    matches ----------- vector<DMatch>&、DMatch構造体のベクトルとして得られる結果
    mask = noArray() ---- InputArray
  )
◎drawMatches関数
 DMatchで得たマッチング結果を用いて描画する。引数は下記の通り。
  drawMatches(
    img1 ---------- InputArray、第一画像(クエリ画像)
    keypoints1 ----- vector<KeyPoints>&、上記画像の特徴点keypoints1のポインタ
    img2 ---------- InputArray、第二画像(訓練画像)
    keypoints2 ----- vector<Keypoints>&、上記画像の特徴点keypoints2のポインタ
    matches1to2 --- vector<DMatch>&、マッチング結果のDMatchベクトルのポインタ
    outImg -------- InputOutputArray、描画された出力画像
    matchColor = Scalar::all(-1) ---- Scalar、デフォルトはマッチング点の丸と線の色を乱数の色で表示
    matchesMask = vector<char>() ------ vector<char>&、デフォルトは、すべてを描画
    flags = DrawMatchesFlags::DEFAULT ----- int、描画法、デフォルトは単純なキーポイント表示のみ
  )
 描画法flagsには、
  DEFAULT = 0 ------------------- マッチングしない点もすべて描画
  DRAW_OVER_OUTIMG = 1 --------- 既存の画像上に描画する
  NOT_DRAW_SINGLE_POINTS = 2 --- マッチングしない点は描画しない
  DRAW_RICH_KEYPOINTS = 4 ------ 特徴点の強度、方向を詳しく描画する
があり、一般的にはデフォルトを使用する。


図1 詳細設定の特徴点検出・記述器で同一画像のマッチングを採るプログラム(BRISKの例)


同一の画像のマッチングを採った結果
 各特徴点検出・記述器について、同一画像を用いたマッチング結果を図2に示す。図は、上から順に、BRISK、ORB、KAZE、AKAZEとなっている。
 この結果を見る限り、特徴点検出・記述器の違いによる優劣はつけ難く、検出された(AKAZEは上部の検出ができない)特徴点に対しては、良く一致している。ただし、図1のプログラムの22行において、第二引数をcrossCheck = falseにした場合は、一部に不一致が見られた。








図2 詳細設定による、同一画像でのマッチング結果


ランダムに配置を変えた画像とのマッチングを採った結果
 図1のプログラムの14行で、"sample.png"を"sample_rand.png"に書き換え、その他は変更しないで、ランダムに並べ替えた画像とのマッチングを採った結果を図3に示す。
 全てのマッチングを表示している(良好なマッチングのみでない)ので、間違ったマッチングが多い。これらから単純に特徴点検出・記述器の優劣を決めるのは難しく、回転に対する同一性を考えると、間違いとは言えないマッチングも多い。







図3 詳細設定による、ランダム画像とのマッチング結果


図形一個のクエリ画像と図形多数の訓練画像とのマッチングを採った結果
  従来使ってきた画像sample.pngを訓練画像とし、別の小さい画像に星形を描いたものをクエリ画像star.pngとする。OpenCVでは、drawMatches関数でマッチング結果を描画する場合、左側がクエリ画像、右側が訓練画像となるので、star.pngをimage_src1とし、ample.pngをimage_src2として読み込む。
 マッチング結果に特に制限を加えないと、近似度の低い(Hamming距離などの離れた)特徴点も含まれるので、DMatch構造体のdistanceパラメータにしきい値を与え、それ以下の特徴点のみを選択的に表示させるようにする。そのために追加したプログラムを図4に示す。
図5はその結果で、左はしきい値thresholdを用いない場合、右は、DMatcherのdistanceにしきい値を試行錯誤的に与えて、その値以下の距離を持つ特徴点のマッチングのみを表示したものである。しきい値を下げて厳しくすると、誤りが少なくなると同時に、正しいマッチングも除外されてしまう。
 正しいマッチングの数が多く得られたのはKAZE、ORBであった。


図4 良好なマッチングのみを選択的に描画するために追加したプログラム(AKAZEの例)






図5 星形のクエリ画像とのマッチング結果(右は良好マッチングのみ)


 なお、同様のことが、BFMatcherクラスのradiusMatch関数でも可能なので、図6のプログラムにより試みた。図4の自作プログラムに比べ、BRISKを除いて、しきい値が一桁小さい値となり、しきい値に対して結果が敏感となり、試行錯誤の調節に苦労した。その理由は、良く分からない。


図6 radiusMatch関数を使用しても類似の処理ができる(BRISKの例)


結 論
 OpenCV3.1.0の標準モジュールに備わっている特徴点検出・記述器BRISK、ORB、KAZE、AKAZEについて、同一画像、ランダムに並べ変えた画像を用いて、マッチングを採った。同一画像に対しては、いずれも正しい一致が得られた(crossCheckありの場合)。ランダム画像に対しても、回転画像へのマッチングを含めると、かなり良い結果が得られた。
 一個の図形をクエリ画像とし、多数の図形を訓練画像としてマッチングを採り、マッチング結果であるDMatch構造体のdistanceパラメータにしきい値を与えた場合と、そうでない場合を比較した。しきい値は必要で、特に、KAZE、ORBが良い成績を示した。
 特徴点検出・記述器の評価は難しい。ここで用いた画像サンプルとプログラムに限っての結果であることを付記する。






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