2015. 9.11.
石立 喬

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

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

 左右二枚のステレオ画像から、triangulatePoints関数で、三次元物体の座標を獲得することができる。一般の撮影画像の特徴点の代わりに、既知の座標を射影した画像を使用して、triangulatePoints関数の使い方と性能を確認した。ところが、OpenCVの仕様通りに引数を与えると、異常な結果が得られることが分かり、別な方法で、うまくいった。
 射影には、projectPoints関数を用いた。

調査の内容
◎目的
 ここでの主目的は、triangulatePoints関数の使い方と動作の確認である。実際にカメラで撮影した画像で使用したところ、不自然な結果になったので、その原因を探ることにした。
◎方法
 理想的に設定したカメラ条件および配置と、正六面体の8個の頂点を特徴点とする三次元点群を用いて、理想的なL画像(左のカメラで撮影した画像)とR画像(右のカメラで撮影した画像)を用意しておく。
 これらの画像に対して、triangulatePoits関数を使用し、得られた三次元座標が元の座標に合っているかどうかを確認する。
◎カメラの条件と配置
 カメラでの撮影画像を320ピクセルX240ピクセルとし、焦点距離を320ピクセルとした。これは、35mmフィルム換算で、焦点距離35mmの準広角レンズを用いた場合に相当する。カメラは理想的な特性を持ち、歪はないものとする。
 左右二台のカメラは同じ高さで、光軸は平行とする。カメラ間の距離である基線長(base line length)は300とした。これは絶対的な長さではなく、300mmと考えても、300cmと考えても良い。triangulatePoints関数で求まる結果は、これを基準として、比例関係で得られる。
 カメラの外部パラメータである回転ベクトルは、すべての方向に0とし、並進ベクトルでは、L画像カメラについては(150, 0, 800)、R画像カメラについては(-150, 0, 800)とした。150は基線長の半分で、二台のカメラの中間の前方800の位置に正六面体の中心があることになる。

三次元点群をL画像とR画像に射影する(準備作業)
 このプログラムは、下記から成る(ただし、1)〜2)は図1、3)〜7)は図2、8)は図3)。
 1)三次元点群の座標位置を8個設定する。
 2)カメラ位置とカメラ関連マトリックスを設定する。
 3)回転ベクトルを設定する(L画像、R画像共通)。
     回転はないものとする。
 4)L画像用とR画像用の並進ベクトルを設定する。
     L画像に対しては、カメラよりhbl(基線長の半分 = 150)だけ右方に点群の中心があるとする。
     R画像に対しては、カメラより-hblだけ左方に点群の中心があるとする。
 5)projectPointsを用いて、三次元点群をL画像に射影する。
 6)L画像への射影結果を印字する。
 7)L画像へに射影結果を表示する。
 8)上記の、5)〜7)をR画像についても同様に実施する。

使用したOpenCV関数の説明
◎projectPoints関数
 「・・・による画像処理と認識(18)」にあるので、省略する。


図1 L画像とR画像を作成する、プログラムの最初の部分



図2 回転・並進ベクトルを設定し、L画像に対して射影して、結果を表示する



図3 R画像に対して射影し、結果を表示する


得られた結果
 図4は、L画像上の座標位置imagePointsLとR画像上のimagePointsRを表示させた結果で、float型の値を持っている。図5は、これらを画像上に描画したもので、「L画像」は、中心点から150だけ左に寄った視点、「R画像」は中心点から150だけ右に寄った視点からの画像となっていて、設定通り正しく射影されている。


図4 各画像上のピクセル値



図5 各画像に対する射影結果を描画した


L画像とR画像から三次元点群の座標を求める(triangulatePoints関数の確認、これがこの資料の主目的)
 このプログラムは、下記から成る(図6参照)。
 1)左右のカメラの射影行列を設定する。
     hblは、すでに設定済みのもの(150)を使う。
 2)triangulationPoints関数を用いて、三次元座標を得る。
   8個の座標点があるので、それぞれに対して、下記を実行する。
    ・準備段階で得られたimagePoiuntsLベクトルからひとつづつ取り出し、Mat型に変換する。
        Point2f型のまま与えると、エラーになる。
    ・imagePointsRに対しても同様とする
    ・これらをtriangulatePoints関数の引数として与え、Mat型のpoints4Dを得る。
    ・points4Dを、vector<Point3f>型のpoints3D_resultに変換する。
        4要素から成るホモジーニアス座標を、reshapeで並びを変え、convertPointsFromHomogeneous関数を使う。

使用したOpenCV関数の説明
◎triangulatePoints関数(calib3d/Camera Calibration and 3D Reconstructionにある)
 二枚の画像上の対応する点群から、三角測量法により、三次元座標点を求める関数で、引数は、
  projMatr1 ------- 第1のカメラの射影行列(3 x 4)
  projMatr2 ------- 第2のカメラの射影行列(3 x 4)
  projPoints1 ----- 第1の画像の特徴点の配列(2 x N)またはvector<Point2f>型
  projPoints2 ----- 第2の画像の対応点の配列(2 x N)またはvector<Point2f>型
  point4D -------- 出力として得られるホモジーニアス座標(homogeneous coordinate)の点群配列(4 x N)
               Size(N, 4)で、チャンネル数1のMat型なので、下記のconvertPointsFromHomogeneous
               関数に与えるには、Size(N, 1)のチャンネル数4に変形(reshape)する必要がある。
で、戻り値はない。
 ただし、この動作には疑問があり、問題点を別途後述する。
◎convertPointsFromHomogeneous関数(資料の場所は同上)
 triangulatePoints関数は、結果をホモジーニアス座標で出力するので、一般のEuclid座標系に変換するために用いる関数で、引数は、
  src ------ n次元で表示した点のベクトル(入力)
  dst ------ (n -1)次元で表示した点のベクトル(出力)
である。戻り値はない。ホモジーニアス座標はn = 4であり、n = 3 のPoint3f型のベクトルに変換する。
◎Mat::reshapeメソッド(core/Basic Structuresにある)
 Matの構成を変更するのに用いるメソッドで、引数は、
  cn -------- 新たに設定するチャンネル数、0にすると変更なし
  rows = 0 --- 新たに設定するrowsの数、0にすると変更なし
で、戻り値はMat型の変換結果である。


図6 triangulatePoints関数を用いて、L画像とR画像から三次元点群座標を求める


得られた結果
 図7はtriangulatePoints関数によって、三次元座標を復元した結果である。 使用した特徴点群の座標は、図4に示すprojectPoints関数で作成したfloat型を、そのまま使用したため、triangulatePoints関数で得た結果の誤差は僅少であった。試みに、小数点以下を切り捨てたfloat型も用いてみたが、誤差は十分満足できる範囲であった。


図7 得られた結果


triangulatePoints関数の使い方で気になった点
 この関数は、左右二つのカメラの射影行列と、二つの画像の対応する特徴点の配列(2 x N)を入力として受け取ることになっており、N個の特徴点を一括して扱うはずである。
 ところが、これに従うと、プログラムは正常にビルドでき、実行時エラーも生じないにもかかわらず、不思議な結果が得られる。
 図8は、OpenCVの仕様に従って作成したプログラムで、結果は図9のようになる。座標点2個ごとに同じ結果になっており、ProjectMatrixLやProjectMatrixRで、hbl(基線長の半分)を変えても、距離(zの値)に反映されない。
 つまり、仕様通りに使用すると不可解な結果になったので、結局、図6の95行から101行のように、個別に計算して、ようやく、正常な結果を得た。


図8 仕様通りに使ったtriangulatePoints関数



図9 仕様通りの使い方での不思議な結果


結 論
 OpenCVに備わっているtriangulatePoints関数を、一般画像に応用したところ、不可解な結果になったので、理想的な条件でtriangulatePoints関数の動作を確認した。
 解説資料に従って、特徴点群をvector<Point2f>型で与えると、やはり不思議な結果になった。この原因はわからない。
 そこで、triangulatePoints関数を、一対の特徴点のみで使用するように変更すると、正常に動作することが分かった。ただし、Point2f型のままではエラーになるので、これをMat型に変換して使用した。
 triangulatePoints関数で得られる出力は、入力特徴点の数に関係なく(一対だけの場合はN = 1)、Size(N, 4)でチャンネル数1のMat型のホモジーニアス座標で得られる。これを、通常の三次元座標に変換するために、convertPointsFromHomogeneous関数を使用するには、Size(N, 1)でチャンネル数4のMat型に変形(reshape)しなければいけない。この辺が、面倒で、分かりにくい。
 使用した特徴点群の座標は、projectPoints関数で作成したfloat型を、そのまま使用したため、triangulatePoints関数で得た結果の誤差は僅少であった。試みに使用した、小数点以下を切り捨てたfloat型でも、誤差は大きくなかった。



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