2015. 9.25.
石立 喬

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

----- findFundamentalMat関数を詳しく調べる -----

 findFundamentalMat関数を、一般画像(人為的なグラフィックスでなく)で用いると、不可解な結果になることが多いので、理想的な条件で使用してみて、動作を確認した。特徴点群は、正六面体の頂点と辺の中点を用いた。頂点のみでは、特徴点の数N = 8となり、RANSACアルゴリズムで異常現象が多発したので、中点を加えて、N = 20で詳しく調べた。乱数による誤差を付加して、その影響も調べた。

調査の内容
◎目的
 ここでの主目的は、findFundamentalMat関数の動作の確認である。実際にカメラで撮影した画像では、不自然な結果になったので、その原因を探ることにした。
◎カメラの条件と配置
 カメラでの撮影画像を320ピクセルX240ピクセルとし、焦点距離を320ピクセルとした。これは、35mmフィルム換算で、焦点距離35mmの準広角レンズを用いた場合に相当する。カメラは理想的な特性を持ち、歪はないものとする。
 左右二台のカメラは同じ高さで、光軸は正六面体の中心に向いているものとする。
 カメラの外部パラメータである回転ベクトルは、Y軸を変化させて、カメラ位置の角度を設定する。並進ベクトルは、L画像とR画像に共通で、(0, 0, 800)とする。800は、カメラから点群の中心までの距離である。
◎検証1(誤差を加えない場合)
 理想的に設定したカメラ条件および配置と、正六面体の頂点や辺の中点を三次元座標点とする三次元点群を用いて、L画像(左のカメラで撮影した画像)とR画像(右のカメラで撮影した画像)へ射影して、理想的な特徴点を用意した。これらの画像に対して、findFundamentalMat関数を使用し、得られたエピポーラー線が適切かどうかを確認した。
 正六面体の8個の頂点のみの射影結果を特徴点として使用した場合には、多くの角度で異常な結果が得られたので、12個の辺の中点12個を三次元座標点として追加し、合計20点の三次元点群を用いた。この正六面体はスケルトン(骨格のみ)なので、隠蔽(occlusion)は起きない。
◎検証2(誤差を加えた場合)
 検証1で使用した20個の三次元座標点をL画像とR画像に射影した後に、それらの二次元座標値に誤差を付加し、影響を調べた。

二台のカメラの光軸が平行な場合(念のための確認、プログラムは省略、結果のみ)
 基礎行列を求める一般的な場合(各カメラの光軸が被写体を向いていて、カメラが同一平面上にない場合)の調査に先立って、「OpenCVとVisual C++による画像処理と認識(22)」で用いた、二つのカメラの光軸が平行な場合の射影画像を用いて、findFundamentalMat関数を試してみた。三次元点群座標の作成は、すでに前項で紹介したものと同じで、基礎行列を求める部分は、後に紹介するものと同じなので、ここでは、プログラムの紹介は省略する。
 結果は図1の通りで、7POINT、RANSAC、LMEDSは正常に同じ結果が得られ、RANSACの場合を代表的に示している。図2は(POINTを用いた場合で、水平直線ではなく、しかも点を通らない不思議な結果になっている。 8POINTについては、一部の資料にも否定的な情報があり、これは使えないと判断した。


図1 光軸が平行なので、エピポーラー線は水平になり、正常な結果が得られた



図2 8POINTを用いると、水平でなく、点群を通らない、不思議な結果になった


調査のためのプログラム
 基礎行列を求める、もっとも一般的な場合で、ここでは、正六面体の三次元座標点を用い、その正面から、左右に同じ角度だけ回転させた位置に二台のカメラを配置した。
 7POINTは一般的でなく、RANSACとLMEDSは同じ結果が得られたので、ここではRANSACに絞って調査した。同様の処理を繰り返して行うことが多かったので、それらの処理をメソッド化した。
 使用したプログラムは、下記から成る。
 1)XYZ各軸の回転角を与えて回転ベクトルrvecを返すメソッドを作成しておく(図3)。
      実際には、Y軸のみしか回転させない。
 2)射影された特徴点を描画するメソッドを用意する(図4)。
 3)エピポーラー線を引くメソッドを用意する(図5)。
 4)正六面体の三次元座標位置を(8+12)個設定する(図6)。
      8個のみを使用する場合には、残りの12個をコメントアウトしておく。
 5)理想的なカメラマトリックスを設定する(図7)。
 6)並進ベクトルで距離を固定的に設定する(L画像、R画像共通、図7)。
 7)回転ベクトルで角度thetaを変化させ、L画像用はY軸回転角をthetaに、R画像用は-thetaに設定する(図7)。
 8)projectPoints関数を用いて、三次元点群をL画像とR画像に射影する(図7)。
 9)L画像とR画像への射影結果(特徴点)に誤差を付加する(図8)。付加しないときは、dist(0.0, 0..0)にしておく。
 10)誤差を付加された特徴点を描画して表示する(図8)。
 11)L画像とR画像から、findFundamentalMatを用いて基礎行列を計算する(図9)。
 12)基礎行列を使って、各画像上の点に対応したエピポーラー線を他画像に描き、表示する(図9)。

使用したOpenCV関数の説明
 いずれもcalib3d/Camera Calibration and 3D Reconstructionにある
◎findFundamentalMat関数
 二つの画像の特徴点群とアルゴリズムを指定して基礎行列を求める関数で、引数は、
  points1 ----- 第一の画像の特徴点群でvector<Point2f>型
  points2 ----- 第二の画像の特徴点群でvector<Point2f>型
  method ----- 基礎行列を計算するアルゴリズム
              CV_FM_7POINT ------- N = 7
              CV_FM_8POINT ------- N >= 8
              CV_FM_RANSAC ------ N >=8
              CV_FM_LMEDS ------- N >= 8
  param1 = 3 --------- RANSACのみに使用する、一般的にはデフォルトで十分
  param2 = 0.99 ------- RANSACまたはLMEDSに使用する、一般的にはデフォルトで十分
  mask = noArray() ---- 一般的にはデフォルトで十分
で、戻り値は、3 x 3のMat型である。
 ただし、7POINTは実用的でなく、8POINTは動作が疑問で、RANSACとLMEDSは差異が認められず、N >= 15でないと使えない印象を受けた。
◎computeCorrespondentEpilines関数
 一方の画像の点に対応する他の画像上のエピラインを求める関数で、引数は、
  points ------- ある画像上の点群、vector<Point2f>型
  Image ------- 上記のある画像の番号(1または2)
  F ----------- 基礎行列
  lines -------- vector<Vec3f>型、一点当たり三要素(a,b,c)から成るデータのベクトル
で、戻り値はない。
 エピポーラー線は、ax + by + c = 0の形で表されているので、これを変形して、y = - (a/b x + c/b )とし、直線を描く。


図3 プログラムの最初の部分と回転ベクトルを設定するメソッド



図4 特徴点を描画するメソッド



図5 エピポーラー線を引くメソッド



図6 N=8の時は、下部をコメントアウトして、点群を設定する



図7 カメラを設定して、点群をL画像とR画像に射影する



図8 誤差を付加して結果を描画する(誤差なしは、120行でdist(0.0, 0.0)にする)



図9 L画像とR画像から基礎行列を求めて表示する


検証1の結果(RANSACはN = 8では使えないことが分かった)
 まず最初に、特徴点の数をNとして、N = 8で調べてみた。角度θ=0°(左右のカメラが同じ位置)とθ=45°では、基礎行列が0行列になってしまった。θ=0°で基礎行列が0になるのは理解でき、θ=45°では、特徴点が一直線上に並んで縮退(degenerate)が起き、特徴点の数が足りなくなるものと判断した。
 そこで、N = 20にすると、θ=45°でも基礎行列が得られ、正常にエピポーラー線を描くことができた。図9は、N = 8とN = 20の場合について、θ=45°で比較したものである。
 N = 8の場合には、θ=45°以外でも、多数の点で異常な特異点が見られた。θ=38°は異常な角度の一例で、図10に結果を示す。N = 20では、正常なエピポーラー線が得られた(基礎行列が正常)。
 以上の結果から、RANSACは、N = 8では使用できず、N = 20では正常に動作することが分かった。



図10 45°では縮退が生じるので、点の数が少ないと基礎行列が求まらない(上はN=8、下はN=20)




図11 38°の特異現象は、点数を増やすと無くなった(上はN=8、下はN=20)


検証2の結果(誤差があると、その大きさに応じて、小さな角度では使えないことが分かった)
 図8の120行に示すように、dist(-1.0, 1.0)と設定して、-1.0から1.0の範囲で乱数を発生させ、射影で得た各特徴点の座標に誤差として加算した。誤差の範囲を、-3.0〜3.0などに大きくした場合も調べたが、誤差の大きさに応じて許容角度が狭まる傾向を確認できた。
 図11は、e = 1.0(誤差が-1.0〜1.0の意)に固定し、角度をθ=25°から順次小さくしていった場合の結果で、最上段のθ=25°では、ほぼ正常にエピポーラー線が得られている(基礎行列が正常に求まっている)が、θが小さくなるにしたがって、エピポーラー線が不正確になっているのが分かる。
 図には示していないが、誤差がない場合には、これらの角度で、すべて正常にエピポーラー線が得られている(θ=2°以下では異常が発生)。
 つまり、角度が小さくなると、L画像とR画像とで、特徴点のズレが少なくなり、相対的に、誤差の影響が大きくなるものと思われる。基礎行列は、θ=25°以上くらいでないと正常に求めることができないとの結論になる。言い換えると、カメラ相互間の距離より短い距離の前方にある被写体しか、正常に基礎行列が得られないことになる。






図12 特徴点の座標値に誤差を加味すると、小さい角度では異常が出る 


より正しく基礎行列を求めるために
 以上の検証で分かったことは、下記の通りである。しかし、これでは、適用できるケースが非常に限定される。
1)隠蔽(occlusion)や縮退(degenerate)、はずれ点(outliers)等による特徴点の減少があっても、十分な数だけの特徴点を用意する。
  →多数の特徴点が得られ易いテクスチャのある対象物が必要。
2)精度の良い画像データを用意する。
  →高性能レンズ(または正確にキャリブレーションされたレンズ)、高画素数のカメラ、カメラ位置の正確な固定が必要。
3)カメラ相互間の距離と同程度より近い距離の前方にある被写体を用いる。
  →遠距離の特徴点を含ませないようにする。比較的近距離の物体にしか使用しない。

結 論
 OpenCVの公式資料によれば、特徴点の数をNとして、7POINTアルゴリズムはN = 7、8POINTはN = 8、RANSACおよびLMEDSはN >=8の場合に使うものとされている。つまり、RANSACはN = 8でも使えることになっているが、これは正しくない。N = 8の条件でカメラの視角を変えて行くと、特異点が続出した。特異点とは、その点では異常現象が発生するのに、その0.1°前後では異常が発生しないような、不連続性の箇所である。別の資料によれば、RANSACはN >= 15とあるが、今回の調査で、これが納得できることが分かった。
 理想的に作成した点群を用い、float型の精度の座標値をそのまま用いてfindFundamentalMat関数を用いた場合には、N = 20で十分満足できる基礎行列が得られた。 しかし、特徴点の座標値に乱数による誤差を付加すると、誤差の大きさに応じて、カメラ角度を大きくしないと(近距離の対象物でないと)正しい基礎行列が得られなかった。これは、理論的に十分理解できるところである。
 より正しく基礎行列を求めるためには、多くの確実な特徴点が求まり易く、かつ近距離の被写体に対し、高性能のカメラで、カメラを十分安定させて撮影する必要があるが、これは容易でなく、応用範囲が限定される。
 この検証は、スケルトン状態の正六面体の特徴点を用いたため、隠蔽(occlusion)の影響が含まれていない。これを考慮すると、もっと多数の特徴点が必要だったかもしれない。




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