2015.10 .14.
石立 喬
OpenCVとVisual C++による画像処理と認識(24)
----- reprojectImageTo3D関数とprojectPoints関数を使う -----
視差マップ(disparity map)から三次元物体の座標を得るのにreprojectImageTo3D関数を使用できる。しかし、自然画像からは、適切な視差マップを求め難いので、人為的で理想的な視差マップを作成して、この関数の動作を確認した。得られた三次元座標データから、二次元射影画像を求めることも試みた。
3D←→2D変換を確認し、応用への道を探る
◎OpenCVの関数の問題点
二次元情報から三次元座標を推定するには、triangulatePoints関数とreprojectImageTo3D関数がある。前者は、SIFTやORBで求める特徴点群(point
cloud)を入力とするもので、適切な位置で正しくマッチングした点群が得られないと、うまく行かない。
後者は、StereoBMやStereoSGBMで求める視差マップ(disparity map)を入力とするもので、これも適切な個所での高精度のマッチングが要求される。
結局、いずれの方法でも、テクスチャの無い面や、繰り返しパターンのある面では、うまく行かず、応用範囲が限られる。また、点群や視差マップには色情報が含まれないので、三次元座標への変換が行われても、画像としての復元が困難である。
これらの問題点を克服する方法を求めて、まずは、単純な視差マップを人為的に作成し、これから深度マップや射影画像を作成して、より有効な応用へのヒントを探ってみた。
◎reprojectImageTo3D関数を使ってみる
これは2D→3D変換に用いられ、視差マップ(disparity map)があれば、元の三次元座標を復元できる。三次元座標のうち、特に奥行きの距離(Z座標)に注目したものが深度マップ(depth
map)で、まずは、人為的に作成した視差マップを用いて、視差マップ→深度マップ変換を試してみた。その結果、trianagulatepoints関数で求めたものと同じになることを確認した。
これには、Qマトリックスが必要であるが、直接設定したものと、stereoRectify関数で得たものとが一致することも確認した。
◎projectPoints関数
これは3D→2D変換に用いられ、回転ベクトル(rotation vector)と並進ベクトル(translation
vector)を与えると、三次元座標を、任意の視点からの射影座標に変換できる。
前記のreprojectImageTo3D関数で得た三次元座標を入力として、視点を移動させた二次元画像を再現した。再現に当たっては、深度マップの深度値に色を対応させ、見やすくした。
確認のためのプログラム
使用したプログラムは、下記から成る。
1)視差データを作成する(図1)。
半径100ピクセルの円の範囲を視差10とし、その手前に、次第に半径の小さな円(次第に視差が大きくなる)を重ねる。
2)視差データをそのまま画像化して表示する(図1、結果は図5左)。
視差値が10~50なので、暗い画像になる。
3)視差データを分かり易く表示する(図1、結果は図5右)。
視差値を4倍して、白黒を反転させる。
4)reprojectImageTo3D関数に必要なQマトリックスを求める(図2)。
stereoRectify関数を用いたが、数値を直接入れて設定する場合と同じであった。
5)reprojectImegeTo3D関数を実行し、深度マップを表示する(図3、結果は図6)。
得られた_3dImageは、x、y、zの3チャンネルから成っているので、これをvector<Point3f>のimagePointsに変換する。
z成分は、深度マップ用のデータdepth_dataとする。
6)projectPoints関数を用いて、imagePointsを二次元画像に射影して、表示する(図4、結果は図7)。
深度値に応じて色付けする。
使用したOpenCV関数の説明
いずれもcalib3d/Camera Calibration and 3D Reconstructionにある
◎stereoRectify関数
カメラマトリックスや歪係数など二台のカメラの条件を与えて、変換に必要な各種マトリックスを計算する関数で、ここでは、参考までに、Qマトリックスを求めるために使用した。Qマトリックスは、直接設定しても良い。引数は、
cameraMatrix1 --- #1のカメラマトリックス(入力)
distCoeffs1 ----- #1のカメラの歪係数ベクトル(入力)
cameraMatrix2 --- #2のカメラマトリックス(入力)
distCoeffs2 ----- #2のカメラの歪係数ベクトル(入力)
imageSize ------- ステレオキャリブレーションの画像サイズ(入力)
R --------------- #1と#2のカメラ相互の回転マトリックス(入力)
T --------------- 同上の並進ベクトル(入力)
R1 -------------- #1カメラの回転マトリックス(出力)
R2 -------------- #2カメラの回転マトリックス(出力)
P1 -------------- #1カメラの新しい座標系の射影マトリックス(出力)
P2 -------------- #2カメラの新しい座標系の射影マトリックス(出力)
Q --------------- 視差から深度への変換マトリックス(perspective transformation
matrix、出力)
flags = CALIB_ZERO_DISPARITY ---- 各カメラのレンズ中心を同一座標とする。デフォルトで良い
alpha = -1 ---------------------- 補正後の画像の拡大縮小を自動にする。デフォルトで良い
newImageSize = Size() ----------- 最初のサイズと同じ。デフォルトで良い
validPixROI1 = 0 ---------------- ROIを表示しない、デフォルトで良い
validPixROI2 = 0 ---------------- 同上
で、戻り値はない。
実際に使用してみたところ、下記のような結果が得られた。P2の要素(0, 3)の-320は、baselineとfの積に負号を付けたものである。

◎reprojectImageTo3D関数
視差マップ画像から、三次元座標データを求める関数で、引数は、
disparity ------ CV_8UC1またはCV_32Fなどの視差マップ(入力)
_3dImage ------- disparityと同じサイズのCV_32FC3(出力)
Q -------------- 4 x 4のQマトリックス(入力)
handleMissingValues = false ------ 視差データの無い部分を無視する。デフォルトで良い
ddepth = -1 ---------------------- -1は出力がCV_32F、デフォルトで良い
で、戻り値はない。
この関数は、下記の式を計算するものである。入力は二次元画像の座標x、yとその位置での視差disp(x,y)であり、それにQマトリックスを掛けて、三次元座標X,Y,Zを求める。ただし、Qマトリックスで、cxはカメラ画像の中心のX座標、cyは同じくY座標、fはカメラレンズの焦点距離、blは二つのカメラ間の基線長(base
line length)である。

この式を計算すると、下記の通りになり、三角測量(triangulation)そのものである。
図1 プログラムの最初の部分で視差マップを作成する
図2 Qマトリックスを設定する二通りの方法(結果は同じ)
図3 reprojectImageTo3Dで深度マップを作成して表示する
図4 projectPoints関数で射影する
得られた結果
視差マップを表示するに当たって、視差をそのまま表示したもの、すなわち濃度d
= densityで画像化したものを図5の左に示す。d = 0を背景に、外側から内側に向かって、d
= 10, 20, 30, 40, 50となっていて、暗い画像である。これを見やすくするために、白黒を反転し、濃度d
= 255 - disparity * 4としたものが図5の右である。
reprojectImageTo3D関数で視差マップを深度マップに変換したのが図6で、無限遠(実際はz
> 100)は濃度d = 255に、その他はd = z * 6となっている。ただし、zは深度値で、基線長baselineの倍数で示し、単位はない。
disparityとzの関係は、
disparity(pixels) z(ratio)
------------- ---------
10 32
20 16
30 10.666667
40 8
50 6.4
となっていて、f = disparity * z の関係がある。
図5の右の視差マップと、図6の深度マップは似ているが、視差マップが、中心に向かって一様に濃度が減少している(暗くなっている)のに対し、深度マップは中心に向かって急に濃度が減り(暗くなり)、次第にその傾斜が緩やかになっている。
図7は、projectPoints関数を用いて、三次元座標値を二次元画像に射影した一例である。reprojectImageTo3D関数で得た_3dImageを、図3の64行でobjectPoints化して用いた。視点をbaselineの1.5倍だけ右にずらしてあり、手前の円に隠れて視差データが存在しない部分は着色されていない。
図5 視差値をそのまま画像化(左)すると暗いが、白黒を反転して差を拡大(右)すると見易い
図6 深度値の表示方法にもよるが、深度マップは視差マップに似ている
図7 射影結果には、深度が不明な隙間ができている
結 論
人為的に作成した、同心円状の理想的な視差マップを用いると、reprojectImageTo3D関数が期待通りに三次元座標に変換してくれた。この三次元座標に対して、視点を移動して、projectPoints関数で二次元画像化すると、これも納得できる結果になった。
reprojectImageTo3D関数の正常な動作が確認できたので、あとは、自然画像から、いかにして正確な視差マップを得るかが重要な課題になる。すなわち、StereoSGBMなどで視差マップを生成する際に、いかに適切なマッピングが得られるか、そのためにどのような原画像が必要か、が重要である。
色情報の付加についても、工夫が必要になる。
「Visual C++の勉強部屋」(目次)へ