2015. 8.28.
石立 喬

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

----- ホモグラフィー変換やStitcherを用いて二枚の画像を合成する -----

 三次元物体の再構築には、多数の画像の変形と貼り合わせが欠かせない。ここで有用なのがホモグラフィー変換である。この予備知識を得るために、二枚の画像をパノラマ画像に合成るる簡単な例で、ホモグラフィー変換を試みた。
 一方、パノラマ画像の合成に便利なStitchクラスがOpenCVに備わっているので、これも使用して、ホモグラフィー変換による結果と比較した。

パノラマ画像の合成方法
 比較的視野の狭いカメラで撮影した画像を合成して、広範囲を一枚で表す画像を作成したい場合がある。この処理は、一般にパノラマ化と呼ばれるが、画像処理では、寄せ集めを意味するモザイク合成(image mosaicing)、縫い合わせを意味する画像連結(image stitching)などと呼ばれる。
 三次元情報の取得を目的とする場合と異なり、ここでは、カメラ位置を固定して、向きのみを変えて撮影した複数の画像を使用する。
 OpenCVを用いたパノラマ画像の合成には、下記の二通りの手法がある。パノラマ化のみを目的とすると、2)が簡単で便利であるが、三次元物体の再構築の手段としてフレキシブルに応用するには、1)が欠かせない。
 1)finfHomographyでホモグラフィー行列を求め、warpPerspectiveで一方の画像を変形し、他方の画像と合成する。
 2)Stitcherクラスのインスタンスを用い、複数の画像を入力として与え、一挙に合成する。

ホモグラフィー変換(homography transform、平面射影変換)
 ホモ(homo-)は、「同じ」の意味で、ホモグラフィー変換は、ある画像に写っているのと同じ内容を他の画像に変形して移し替えることを意味する。似たものに、画像の回転、平行移動、拡大縮小を行うアフィン変換(affine transform)があるが、ホモグラフィー変換は、それらに加えて、座標位置に応じて拡大縮小の比率を変え、台形状の変換が可能である。

ホモグラフィー行列(homography matrix)
 ホモグラフィー変換は、ホモグラフィー行列Hによって定義できる。x1、y1を原画像上の座標点、x2、y2をホモブラフィー変換後の画像上の座標点とすると、

   

の関係があり、h11、h12、h21、h22は、座標位置に不変の固定倍率の拡大縮小を含む回転に、h13、h23は平行移動に用いられる。h31h32は、座標位置に応じて拡大縮小の倍率が変わる台形状の変換効果があり、h33は1である。sは定数の係数である。

ホモグラフィー変換を用いて、二枚の画像を合成する
 このプログラムは、下記から成る(ただし、1)~3)は図1、4)~6)は図2、7)~11)は図3を参照)。
 1)原画像を読み込み、表示する。右画像をsrc_image1(query画像)、左画像をsrc_image2(train画像)とする。
    これは、左画像の既知(train)のどの部分が、右画像に一致するかを探す(query)ことを意味する。
 2)OrbFeatureDetectorで、特徴点keypointsを検出する。
 3)OrbDescriptorExtractorで、keypointsの特徴量の記述子descriptorsを抽出する。
 4)BFMatcherでマッチングを採り、結果をベクトルmatchesに入れる。
 5)matchesを順次読み取り、パラメータのdistanceが一定値(試行錯誤で25と決定)未満を良好なマッチングと判定し、
    ・そのmatchesをgood_matchesにコピーする
    ・そのmatchesのqueryIdxパラメータが指し示すkeypoints1を、good_keypoints1にコピーする
    ・そのmatchesのtrainIdxパラメータが指し示すkeypoints2を、good_keypoints2にコピーする
 6)good_matcesにしたがって、keypoints1とkeypoints2を使って、マッチングを描画する(結果を図5に示す)。
    good_matchesの内容は、matchesの内容を引き継いでいるので、keypoints1,2を使用する。
    左右が逆になっているが、これが正しい配置である。
 7)good_keypoints1,2はKeyPoint型のベクトルなので、findHomography関数用に、Point2f型ベクトルに変換する。
 8)findHompgraphy関数を実行して、ホモグラフィー行列homographyを求める。
 9)warpPerspective関数で、src_image1を射影変換して、Sizeで指定したdst_imageに描画する。
 10)dst_imageの、Rectで指定した部分に、src_image2をコピーする。
 11)dst_imageを表示する。

使用したOpenCV関数の説明
◎DMatch構造体(features2d/2D Features Framework/Common Interfaces of Descriptor Matchersにある)
 マッチング結果を格納する構造体で、パラメータには下記がある。
  queryIdx ---------------- query画像の特徴点の番号、ここでは右画像に対応する。
  trainIdx ----------------- train画像の特徴点の番号、ここでは左画像に対応する。
  imgIdx ------------------ train画像の番号
  distance ---------------- float型ではあるが、ORBの結果は整数で得られる。
◎BFMatcherクラス(同上)
 DescriptorMatcherを継承し、BruteForceアルゴリズムを使用する。コンストラクタは、
  normType = NORM_L2 ------ マッチングの度合いを測る基準で、以下の種類ほかがある。
        NORM_L1 = 2 ------------ SIFT、SURF用
        NORM_L2 = 4 ------------ デフォルト、同上
        NORM_HAMMING = 6 ----- ORB、BRISK、BRIEF用
  crossCheck = false --------- クロスチェックの有無
である。
 メソッドには、match()があり、引数は、
  queryDescriptors ------- query画像の特徴点の記述子
  trainDescriptors -------- train画像の特徴点の記述子
  matches -------------- DMatche型のベクトル
  mask = Mat() ---------- 一般には必要ない
を用いる。
◎KeyPoint::convert関数(OpenCV3.0.0-devの資料以外には説明がないが、Ver.2.4.10.0でも使える)
 KeyPoint型のベクトルをPoint2f型のベクトルに変換したり、またはその逆を行ったりする関数で、ここで使用する例のように、KeyPoint → Point2fの場合には、引数は、
  keypoints ------- 入力として与えるKeypoint型のベクトル
  points2f -------- 出力として受け取るPoint2f型のベクトル
  keypointsindex = vector<int>()
である。
 これは、KeyPointのパラメータptのみを取り出し、Point2f型にする
  for(auto it = keypoints.begin(); it != keypoints.end(); ++it)
      points2f.push_back(it -> pt);
と同じ作用をする。
◎findHomography関数(calib3d/Camera Calibration and 3D Reconstruction.にある)
 二つの画像の特徴点からホモグラフィー行列を求める関数で、引数は、
  srcPoints ------------------ Point2f型の原画像の特徴点のベクトル
  dstPoints ------------------ Point2f型の変換後画像の特徴点のベクトル
  method = 0 ------ 算出方法で、デフォルトは、すべての点を使用した単純な最小二乗法による。他に、下記がある。
        CV_RANSAC ----------- RANSAC法
        CV_LMEDS ------------ Least-median法
  ransacReprojThreshold = 3 ---- RANSACの時のみ使用、はずれ点と見なすエラーの最大許容値、1~10が良いとされる。
  mask = noArray() ----  一般的には使用しない
から成り、戻り値はMat型のホモグラフィー行列である。
◎warpPerspective関数(imgproc/Geometric Image Transformationsにある)
 入力原画像を、ホモグラフィー行列に応じて変換し、新しい画像を出力する関数で、引数は、
  src ------------ 入力原画像
  dst ------------ 原画像と同タイプで、サイズはdsizeで指定する、変換後出力画像
  M ------------- 3 x 3 の変換マトリックス(ここではホモグラフィー行列を与える)
  dsize ---------- 出力画像のサイズ
  flags = INTER_LINEAR ---------------- 補間方法を指定する。デフォルト以外にINTER_NEARESTがある
  borderMode = BORDER_CONSTANT ----- 変換画像が存在しない周辺の処理、外にBORDER_REPLICATEがある。
  borderValue = Scalar(0) --------------- BORDER_CONSTANTの場合の周辺の色、デフォルトは黒
であり、戻り値はない。in-placeでは使用できないので、dstにsrcを兼用させることはできない。


図1 原画像を読み込み、マッチングの準備をする



図2 マッチングを採り、良好なものを選ぶ



図3 ホモグラフィー行列を求め、画像変換して合成する


ホモグラフィー変換を用いた合成結果
 図4は、参考のために原画像をそのまま表示したもので、パノラマ合成時に右側にくる画像を#1画像、左を#2画像とする。
 図5は、良好な特徴点のマッチング結果を示したもので、並びの順序は左から、#1画像、#2画像となっている。この画面を見ながら、はずれ点(outlier)がなくなるように、図2の42行にあるしきい値を決めた。
 図6は、念のためにホモグラフィー行列homographyを出力させた結果で、納得できる結果が得られている。これが適切でないと思われた場合は、前述のしきい値をさらに下げ(厳しくする)て様子を見る必要がある。
 図7は最終的なパノラマ合成結果で、dst_imageのサイズを原画像を横に二つ並べた大きさにしたため、右側の画像の上下が一部切れている。


 図4 使用した原画像



図5 マッチングをとり、良好なマッチング点のみを選んだところ



図6 得られたホモグラフィー行列を表示した結果



図7 ホモグラフィー変換による合成結果


Stitcherクラスを用いてパノラマ画像を合成する
 Stitcherクラス(stitching/High Level Functionalityにある)がOpenCVに用意されており、これを使用すると、簡単に複数個の画像をパノラマ画像に合成できる。
 Stitcherクラスを使用するには、opencv2/stitching/stitching.hppをインクルードする必要があり、合わせて、opencv_stitching2410d.libも、「追加の依存ファイル」で設定しておく。
 使用したプログラムを図8に示し、図9はその結果である。
 プログラムは下記から成る。
 1)原画像を読み込む。左右どちらの画像をsrc_image1にしても、結果は同じである(左右を気にすることがない)。
 2)複数(ここでは二枚)の画像をベクトル化してsrc_imagesを用意する。
 3)createDefault()で、Stitcherクラスのインスタンスsttを生成する。
    引数なしのデフォルトでは、GPUを使用しない。
 4)stt.stitch関数で、src_imagesを合成し、得られた合成画像dst_imageを描画する。


図8 Stitcherを使用したプログラム


Stitcherクラスを用いた合成結果
 図9は得られたパノラマ合成結果で、#1画像、#2画像のいずれも切れることのないように外枠が決められているので、#2画像(右)はいっぱいに、#1画像(左)は、それに合わせて縮小されている。継ぎ目が目立たたなく、きれいに合成されている。処理時間は、やや長く感じられた。


図9 Stitcherによる合成結果


結 論
 OpenCVに備わっているfindHomography関数を用いてのホモグラフィー行列の推定は、満足な結果が得られ、これを用いて作成したパノラマ画像も、期待通りであった。ただし、事前に、マッチング点の選別を厳しくしておく必要はあった。
 Stitchクラスを用いたパノラマ画像の合成は、容易に良好な結果が得られた。ただし、処理時間は、やや長めであった。



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