2015. 7. 4.
2015. 8.18. good_keypointsの求め方に誤りがあったので訂正、結果画像も訂正
2015. 9.25. 基礎行列が正しく得られなかった原因を推定して、「結論」に追記
石立 喬

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

----- 二枚のカメラ画像から基礎行列を求めるのは難しい -----

 二つの視点から撮影した二次元画像の間には、基礎行列(fundamental matrix)で表される関係がある。二つのカメラ画像から基礎行列を求める関数がOpenCVに備わっているので、これを使ってみたが、意外に難しいことが分かった。

エピポーラー幾何(epipolar geograpy)
 基礎行列は、エピポーラー幾何(学)と言う数学的体系の中で論じられる。エピポーラー幾何は、二つのカメラで同じ三次元物体を異なる視点から撮影する際の諸関係の枠組みで、以下の要素からなる。
◎基礎行列(fundamental matrix)
 エピポーラー幾何を代数学的に表現したもので、エピポーラー方程式により求める。二つのカメラ間の相対的な関係を定義し、総合的な環境のキャリブレーションとも言える。カメラ間の相対的な回転・並進ベクトルと、二つのカメラの内部パラメータが含まれる。理論的には明確に定義されているが、実際の画像から求めようとすると、非常に困難である。、
 実際には満たすことが困難な、下記条件を満足しないと、僅かなデータの違いが結果を大きく変化させ、不安定になる。
 1)画像の解像度が十分あること
 2)マッチング点が、画像内で広く分布していること
 3)マッチング点の深度が広く分布していること
 4)マッチング点が同一直線上にないこと
 5)マッチング点が同一平面にないこと
 6)マッチングのはずれ点(outliers)が少ないこと
 7)ノイズが少ないこと
 基礎行列を求めるには、findFundamentalMat()関数を用いる。基本行列(essential matrix)との違いは、回転・並進ベクトルの基本行列に、さらにカメラ内部情報が加わったことである。基礎行列は、画像上のピクセル座標で表されたマッチング点を対象とするが、基本行列は、カメラの方角から見た空間座標を対象とする。
◎エピポーラー線(epipolar lines)
 これを描くと、視覚的に、基礎行列の意味を確認出来る。#1のカメラで撮った画像に写っている三次元物体上の一点が、#2のカメラで撮った画像に写っている筈の点の軌跡であり、#1カメラの視線を#2のカメラ画像上に示したものである。
 三次元物体のある点の第一のカメラ画像への射影点をP(x1,y1)とすると、下式のように、これに基礎行列Fをかけることによって、第二のカメラ画像上のエピポーラー線を求めることができる。
 
       

   上式の左辺は、エピポーラー線の式の係数であり、下式(二つ示してあるが、同じ意味)により、エピポーラー線の式が得られる。

 

 エピポーラー線は、基礎行列から、computeCorrespondEpiline()関数を用いて求める。
◎エピポール(epipole)
 すべてのエピポーラー線は、必ず一点で交わる。これは、他方のカメラの中心がここにあることを意味する。一般には、画像の外にあって見えない。もし画像の中にあれば、他方のカメラが写り込んでいるはずであり、その中心位置である。この点をエピポール(epipole)と呼ぶ。実際の画像で基礎行列を計算し、エピポール線を引き、エピポールを求めると、他方のカメラが画像の範囲外にあるにも関わらず、画像内に現れることが多い。これは、計算誤差によるもので、計算の不安定さが分かる。

基礎行列の求め方
 マッチングのとれた特徴点のデータから計算し、大別して、3種類の方法がある。
 1)線形推定法 ------- 簡単で高速であるが、ノイズやはずれ点(outliers)に弱い。OpenCVの7POINT、8POINTが相当する。
 2)繰り返し推定法 ---- 誤差を計算しながら、誤差が最小になる結果を求める。これも、ノイズやはずれ点に弱い。
 3)ローバスト推定法 -- OpenCVのRANSACとLMEDSが相当する。RANSACは、RANdom SAmple Consensusの意味で、ランダムにマッチング点を抜き出し、性能の最も良いものを選ぶので、はずれ点の影響を避けることができる。LMEDSは、Least MEDian Squareの意味で、これも、複数の組み合わせの中から最も良いものを選ぶ。

二つの視点の画像から基礎行列を求め、エピポーラー線を描画する
 プログラムは、下記から成っている。
 1)画像を読み込む。カラーとグレイスケールのいずれで読み込んでも大差はなかったので、カラーにした(図1)。
 2)#1のカメラ画像の特徴点をkeypoints1、#2のカメラのものをkeypoints2として用意する(図1)。
 3)これらの特徴点は、ORBのFeatureDetector関数を用いて取得する(図1)。
 4)これらの特徴点のマッチングを取るために、ORBのDEescriptorExtractor関数で、記述子descriptor1とdescriptor2を得る(図1)。
 5)BFMatcherクラスで、descriptor1とdescriptor2のマッチングを取り、結果をmatchesに入れる(図1)。
 6)matchesを調べて、distanceが一定以下のmatchesをgood_matchesとして選別し、その時のkeypoints1とkeypoints2を、good_keypoints1とgood_keypoints2に入れる(図2)。
 7)drawMatches関数でマッチング結果を描画する。matchesをgood_matchesに変更したが、その内容は古いkeypointsの番号を指し示したままであるので、good_keypointsではなく、keipointsを使用する(図2)。
 8)findFundamentalMat関数で、基礎行列を求める(図3)。
 9)computeCorrespondEpilines関数で#1画像のエピポーラー線を求めて描画する(図3)。
 10)同様に、#2画像のエピポーラー線を求めて描画する(図4)。

使用したOpenCV関数の説明
 他稿で、すでに紹介したものも、念のために一部含めてある。
◎DMatch構造体(features2d/Common Interfaces of Descriptor Matchersにある)
 keypoint descriptorのマッチング状態を表す構造体で、パラメータには、
  queryIdx --- points1のdescriptor1の番号
  trainIdx ---- points2のdescriptor2の番号
  imgIdx ----- 画像の番号
  distance --- descriptor1とdescriptor2の距離
があり、メソッドにmatch1がある。
◎BFMatcherクラス(features2d/Common Interfaces of Descriptor Matchersにある)
 DescriptorMatcherを継承し、descriptor2とdescriptor2のマッチングを取る。コンストラクタの引数には、
  normType = NORM_L2
  crossCheck = false
があるが、ORBに対しては、デフォルトではなく、normType = NORM_HAMMINGに設定して使用する。crossCheck = trueにすると、はずれ点を除去するのに役立つ。
◎drawMatches関数(features2d/Drawing Function of Keypoints and Matchesにある)
 keypointのマッチングを直線で結ぶ関数で、引数は、
  img1 --------- 第1の画像
  keypoints1 ---- 第1の画像の特徴点 vector<KeyPoint>型
  img2 --------- 第2の画像
  keypoints2 ---- 第2の画像の特徴点 vector<KeyPoint>型
  matches1to2 -- マッチングデータ vector<DMatch>型
  outimg ------- 出力画像
 この他にもデフォルト設定済みの引数があるが、一般にはデフォルトで良い。
 
◎findFundamentalMat関数(calib3d/Camera Calibration and 3D Reconstructionにある)
 二つの画像のマッチング点と計算手法を与えて、基礎行列を得る関数で、引数は、
  points1 --------------- #1の画像のマッチング点、vector<Point2f>型の入力配列
  points2 --------------- #2の画像のマッチング点、vector<Point2f>型の入力配列
  method = FM_RANSAC--- 基礎行列を計算するアルゴリズム
  param1 = 3 ------------ RANSACに使用するパラメータで、エピポーラー 線との最大許容ピクセル数
  param2 = 0.99 ---------- RANSACとLMEDSに使用するパラメータで、結果が正しい確率
  mask = noArray() ------- 一般的にはデフォルトで良い
であり、戻り値はMat型の基礎行列である。
 使用できるアルゴリズムには下記がある。
  FM_7POINT = 1 -------------- マッチング点が7個に限る
  FM_8POINT = 2 -------------- マッチング点が8個以上
  FM_LMEDS = LMEDS = 4 ------- マッチング点が十分多いことが望ましい
  FM_RANSAC = RANSAC = 8 ---- マッチング点が十分多いことが望ましい
◎computeCorrespondEpilines関数(calib3d/Camera Calibration and 3D Reconstructionにある)
 注目点と基礎行列を与えて、相手方のカメラのエピポーラー線を得る関数で、引数は、
  points ------- 入力として与える、vector<Point2f>型の配列
  whichImage -- どちらの画像のpointsであるかを指定する(1または2)
  F ----------- 基礎行列を与える
  lines -------- vector<float>型の出力。a = lines[0]、b = lines[1]、c = lines[2]により、線の式ax + by + c = 0が得られる
であり、戻り値はない。


図1 二枚の画像を読み込んで、マッチングを取る



図2 良好なマッチングのみを選んで結果を描画する



図3 基礎行列を計算し、#1画像のエピポーラー線を描く



図4 #1画像のエピポーラー線を描く


プログラムの実行結果
 基礎行列を求めるには、特徴点のマッチングが良く取れていることが望ましいので、BFMatcherクラスのコンストラクタで、デフォルトのcrossCheck = falseの場合と、crossCheck = trueの場合を比較した。
 図5と図6は、二つの場合についてマッチングを描画したもので、差はほとんど見られない。これらは、いずれも、distance < 20の良好なマッチング点のみを選んで線で結んだものである。


図5 crossCheck = false(デフォルト)でのマッチング結果



図6 crossCheck = trueでのマッチング結果


 図7は、crossCheck = false(デフォルト)の場合の、計算アルゴリズムによる違いを調べたもので、8POINT法よりもRANSAC法やLMEDS法が良いという結果は得られていない。
 図8は、crossCheck = trueの場合で、やはり、8POINT法よりもRANSAC法やLMEDS法が良いという結果はない。
 興味があるのは、図5と図6で、crossCheckの有無に殆ど違いが見られないにもかかわらず、図6と図7ではエピポールの位置に違いがあることである。このことは、基礎行列の計算が、僅かのデータの違いで敏感に影響を受けることを意味する。


図7 crossCheck = falseでの結果、上から8POINT、RANSAC、LMEDS



図8 crossCheck = trueでの結果、上から8POINT、RANSAC、LMEDS


結 論
 OpenCVに備わっている関数を使用した限りでは、得られた基礎行列は、僅かの条件で変動が激しく、どれが正しいかが分からない。基礎行列から計算したエピポーラー線を描き、エピポールを求めても、納得できる結果は得られなかった。
 基礎行列は、二つのカメラ画像間のピクセル座標について関連付けるもので、二つのカメラの内部パラメータであるカメラマトリックス(焦点距離、中心点)と歪係数マトリックスおよび視点マトリックス(並進ベクトル、回転ベクトル)を総合的に含むものである。これらを、不確実で限られた点の集まりから一挙に求めようとするところに無理があるようである。カメラ固有のパラメータを別途求め、視点マトリックスも距離や角度を実測して与えると良いのかも知れない。
 二つのカメラ画像から計算できる、明確な数学的根拠があるために、理論が先走ってしまった気がする。
(追記)
 「OpenCVとVisual C++による画像処理と認識(23)」で検証した結果、ここで取り上げた例は、1)マッチング結果で得られた特徴点の数が限度ぎりぎり程度に少ない。2)二か所のカメラ位置の相互距離に比して、対象物の距離が遠すぎる。3)誤差の影響を受けやすい遠距離の特徴点が含まれている。などが原因と考えられる。


参考文献
 Q.T.Luong and O.D.Faugeras, "The Fundamental Matrix: Theory, Algorithms and Stability Analysis"(1995)
 http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.31.761&rep=rep1&type=pdf

 Z.Zhang, "Determining the Epipolar Geometry and Its Uncertainity: A Review"(1998)
 http://research.microsoft.com/en-us/um/people/zhang/Papers/IJCV-Review.pdf

 C.L.Feng and Y.S.Hung, "A Robust Method for Estimating the Fundamental Matrix"(2003)
 http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.91.4672&rep=rep1&type=pdf

 J.F.Huang, S.H.Lai and C.M.Cheng, "Robust Fundamental Matrix Estimation with Accurate Outlier Detection"(2007)
 http://www.iis.sinica.edu.tw/page/jise/2007/200707_16.pdf


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