蟹者

@kanimono

python+opencvでカメラキャリブレーション→ステレオマッチング→奥行き計測をする方法

今回でほぼパーペキにわかったのでまとめ

環境:python 2.7.6 OpenCV2.4.11

 
キャリブレーションをせずともステレオマッチングと、視差マップをもとにした奥行き計測はできる↓

 

russeng.hatenablog.jp

 

 


①事前
・左右カメラで同一のチェスボードを撮影した画像を数10枚程度(最低10枚くらい、確か)用意する(Zhangの方法)
・↑と同様のカメラの位置関係で撮影したステレオマッチング用の画像(奥行計測したい画像)を用意する


②cv2.findChessboardCorners(image, patternSize[, corners[, flags]]) → retval, corners
「チェスボードの交点を見つける」
・cornersには交点の画像内ピクセル座標が戻り値として与えられる
・撮影したキャリブレーション用画像すべてに対して行う
・cornersは、左右カメラ独立に取り出せるよう処理しておくのがよい


③cv2.calibrateCamera(objectPoints, imagePoints, imageSize[, cameraMatrix[, distCoeffs[, rvecs[, tvecs[,
flags[, criteria]]]]]]) → retval, cameraMatrix, distCoeffs, rvecs, tvecs
「カメラのキャリブレーション(カメラ行列と歪み係数を求める)」
・objectPointsには、キャリブレーションパターン内の座標値(パターン内の平面座標したがってz=0)
・imagePointsには、パターンを撮影した画像内の交点座標(先のcorners)を与える
・チェスボードはカメラに向かって水平にしなくてもよい
・1個のカメラのみにおける外部パラメータ(回転行列・並進ベクトル)も算出できるが、とりあえず使わない
・この関数を左右のカメラそれぞれにおいて独立に適用する


④cv2.stereoCalibrate(objectPoints, imagePoints1, imagePoints2, imageSize[, cameraMatrix1[, distCoeffs1[,
cameraMatrix2[, distCoeffs2[, R[, T[, E[, F[, criteria[, flags]]]]]]]]]]) → retval, cameraMatrix1,
distCoeffs1, cameraMatrix2, distCoeffs2, R, T, E, F
「撮影システムのキャリブレーション(左右のカメラを含めた外部パラメータを求める)」
・cameraMatrix1(,2)とdistCoeffs1(,2)には、先で求めたカメラ行列と歪み係数を入れる
・同様にcv2.findChessboardCornersで求めたピクセル座標と、パターン内の交点座標を入力する
・RとTは「左右のカメラ間の」回転行列と並進ベクトルであり、1で入力した方のカメラのカメラ座標系をそのままワールド座標系と考えて計算している
・カメラ行列、歪み係数も再度最適化(誤差を最小化するよう補正)される。
 つまり入力する必要は必ずしもないが、安定した解を素早く求めるために、先のcv2.calibrateCameraで求めておくことが推奨される。
    この"二度手間"の最適化を行わない条件で関数を動かすこともできる(flagsで制御)


⑤cv2.stereoRectify(cameraMatrix1, distCoeffs1, cameraMatrix2, distCoeffs2, imageSize, R, T[, R1[, R2[, P1[,
P2[, Q[, flags[, alpha[, newImageSize]]]]]]]]) → R1, R2, P1, P2, Q, validPixROI1, validPixRPI2
「それぞれのカメラの平行化のための回転行列・射影行列およびQを求める」
・前述のRとTを入力
・Qは視差-奥行のマッピング行列(ステレオマッチング後の3D座標の取得に必要)
・上記の3つの関数はこのドキュメント*1にリファレンスが載っている。以下に紹介する関数も特記しない限り載っている。この関数はなぜかpython2系の情報について書かれてないが、存在はする。


そもそも平行化とは?
ステレオマッチングとは、2つの画像間の対応点を探索し、対応点どうしのピクセル距離(視差)を求める作業である。その目的は、次式により、画像の取得過程(3次元→2次元)で失われた奥行情報を計算することにある。
    z=bf/(u-u')
ところでこの式は、以下のワールド座標-画像内座標の変換式を、左右のカメラが画像のx軸に平行であり、y方向・Z方向の位置は同一である(=平行ステレオ)と単純に仮定した場合に得られる変換式である。この仮定は、回転行列と並進ベクトルを用いて以下のように定義する。

しかし実際には、カメラのわずかな回転などにより、完全に平行にカメラを配置できるわけではない。
したがって平行化の操作とは、まるで"上記の仮定が成立する場合に得られた画像である"かのように、実際の画像を修正する作業である。
平行化の作業は、エピポーラ線を求めやすくもする、つまりステレオマッチングにおける対応点探索において解を求めやすくなる(書いてて思ったが、そっちの方の目的がメインじゃないのか?)


⑥cv2.initUndistortRectifyMap(cameraMatrix, distCoeffs, R, newCameraMatrix, size, m1type[, map1[, map2]]) →
map1, map2
「平行化と歪み補正を同時に行う"マップ"を求める」
・マップとは、OpenCVにおける画像の幾何変換のための位置指定情報を乗せた行列(関数Remapで使用する)
・左右のカメラ画像についてそれぞれ行う
・関数Rectifyで求めたRをRに、P1(P2)の左側の3×3行列をnewCameraMatrixに入力する
・sizeには歪み補正後の画像サイズを入力する
・cameraMatrixとdistCoeffsにはstereoCalibrate関数で求めたものを入力する


⑦cv2.remap(src, map1, map2, interpolation[, dst[, borderMode[, borderValue]]]) → dst
「マップを用いて画像を変換」
・initUndistort関数で求めたmap1,map2を入力する
・interpolationは補間方法の指定
・srcには変換したい元画像を入力し、distには変換後の画像が出力される
 (ここではステレオマッチングを行う用のペア画像を変換する)


マッチング関数(StereoBM関数、StereoSGBM関数等)を用い、remapで変換した画像に対しステレオマッチング
 →視差画像の取得


⑨cv2.reprojectImageTo3D(disparity, Q[, _3dImage[, handleMissingValues[, ddepth]]]) → _3dImage
「画像から3次元座標を得る」
・disparityにはマッチング関数で得た視差画像を、QにはstereoRectify関数で求めたものを入力する
・_3dImageは、dispartyと同じサイズでかつ3チャンネル(3次元座標)の構造体が入力されている


 
おわり
・CやC++でもほぼ同様の名前の関数を適用する(たぶん)
・コードはいずれまた