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++の勉強部屋」(目次)へ