10. Stereo Rectification
Stereo Rectificationとは
Stereo Rectificationは日本語ではステレオ平行化処理と言います. 右カメラと左カメラで撮影された右画像と左画像から 右カメラで撮影された右画像(これは元の右画像と同じ)と 同じ右カメラ(左カメラではありません)を左に平行移動(角度は変えないで)して 撮影した仮想的な左画像を得る処理です. この仮想的な左画像は元の左画像を変換することで得られます. 対象の3次元的な情報が全て分かっていない限り 任意の位置と角度から撮影した画像が得られる訳はありませんから, ステレオ並行化処理が可能というのは面白いことです. ステレオ並行化処理はステレオビジョンになくてはならない処理であり, 一方,ステレオビジョンは対象の3次元的な情報を求めることが目的であるわけですから, ステレオ並行化処理が可能ということは非常に重要なことです.あとで分かりますが 正確に左に移動して撮影した左画像とするには右画像にも変換が必要です.
座標系の変換
2つの3次元座標系\(OXYZ\)と\(oxyz\)との間の変換を考えます. 2つの座標系は本来対等なのですが, ここでは\(OXYZ\)をワールド座標系またはグローバル座標系, \(oxyz\)をカメラに取り付けられたカメラ座標系としておきます. \(O\)と\(o\)は各々の原点を表しています. ベクトルは座標系に独立であるとして表現していきます.\(\vec{Oo}=\vec{t}\)と書くことにします.そうすると \(P\)を空間上の点として \begin{align*} \vec{OP}=\vec{Oo}+\vec{oP}=\vec{t}+\vec{oP}=\vec{t}+x\vec{e_1}+y\vec{e_2}+z\vec{e_3} \end{align*} と表すことができます.ここで\(\vec{e_1}\)と\(\vec{e_2}\)と\(\vec{e_3}\)は座標系\(oxyz\)の基底ベクトルだとすると, \(P\)点の座標系\(oxyz\)での座標は\((x, y, z)\)となります. 一方,\(P\)点の座標系\(OXYZ\)での座標は\((X, Y, Z)\)であるとします. ここで全てのベクトルを座標系\(OXYZ\)によって成分表示することにすると, \begin{align*} \vec{OP}=\begin{pmatrix} X \\ Y \\ Z \end{pmatrix} \end{align*} となります.また \begin{align*} \vec{t}=\begin{pmatrix} t_1 \\ t_2 \\ t_3 \end{pmatrix}, \vec{e_1}=\begin{pmatrix} e_{11} \\ e_{12} \\ e_{13} \end{pmatrix}, \vec{e_2}=\begin{pmatrix} e_{21} \\ e_{22} \\ e_{23} \end{pmatrix}, \vec{e_3}=\begin{pmatrix} e_{31} \\ e_{32} \\ e_{33} \end{pmatrix} \end{align*} であるとします.そうすると \begin{align*} \vec{OP}=\vec{t}+x\vec{e_1}+y\vec{e_2}+z\vec{e_3} \end{align*} は成分表示では, \begin{align*} \begin{pmatrix} X \\ Y \\ Z \end{pmatrix}= \begin{pmatrix} t_1 \\ t_2 \\ t_3 \end{pmatrix}+ \begin{pmatrix} e_{11} \\ e_{12} \\ e_{13} \end{pmatrix}x+ \begin{pmatrix} e_{21} \\ e_{22} \\ e_{23} \end{pmatrix}y+ \begin{pmatrix} e_{31} \\ e_{32} \\ e_{33} \end{pmatrix}z= \begin{pmatrix} e_{11} & e_{21} & e_{31} \\ e_{12} & e_{22} & e_{32} \\ e_{13} & e_{23} & e_{33} \end{pmatrix} \begin{pmatrix} x \\ y \\ z \end{pmatrix}+ \begin{pmatrix} t_1 \\ t_2 \\ t_3 \end{pmatrix} \end{align*} となります.新しく \begin{align*} X=\begin{pmatrix} X \\ Y \\ Z \end{pmatrix}, x=\begin{pmatrix} x \\ y \\ z \end{pmatrix}, R^{-1}=\begin{pmatrix} e_{11} & e_{21} & e_{31} \\ e_{12} & e_{22} & e_{32} \\ e_{13} & e_{23} & e_{33} \end{pmatrix}, t=\begin{pmatrix} t_1 \\ t_2 \\ t_3 \end{pmatrix} \end{align*} と書くことにして \begin{align*} \begin{pmatrix} X \\ Y \\ Z \end{pmatrix}= \begin{pmatrix} e_{11} & e_{21} & e_{31} \\ e_{12} & e_{22} & e_{32} \\ e_{13} & e_{23} & e_{33} \end{pmatrix} \begin{pmatrix} x \\ y \\ z \end{pmatrix}+ \begin{pmatrix} t_1 \\ t_2 \\ t_3 \end{pmatrix} \end{align*} を \begin{align*} X=R^{-1}x+t \end{align*} と表します.\(R\)を\(R^{-1}\)の逆行列だとして両辺に左から掛けると, \begin{align*} RX=x+Rt \end{align*} となりますが,\(T=-Rt\)と置いて \begin{align} x=RX+T \label{eq:1} \end{align} と表します. \(X\)は\(P\)点の座標系\(OXYZ\)での座標であり, \(x\)は\(P\)点の座標系\(oxyz\)での座標であることに注意して, 式\eqref{eq:1}は 「座標系\(OXYZ\)で\(X\)を\(R\)だけ回転させて\(T\)だけ平行移動した位置の座標系\(OXYZ\)での座標が座標系\(oxyz\)での\(P\)点の座標となる」 と読みます.これが座標の変換の式です.これを成分表示で書いた \begin{align*} \begin{pmatrix} x \\ y \\ z \end{pmatrix}= \begin{pmatrix} r_{11} & r_{12} & r_{13} \\ r_{21} & r_{22} & r_{23} \\ r_{31} & r_{32} & r_{33} \end{pmatrix} \begin{pmatrix} X \\ Y \\ Z \end{pmatrix}+ \begin{pmatrix} T_1 \\ T_2 \\ T_3 \end{pmatrix} \end{align*} は \begin{align*} \begin{pmatrix} x \\ y \\ z \end{pmatrix}= \begin{pmatrix} r_{11} & r_{12} & r_{13} & T_1 \\ r_{21} & r_{22} & r_{23} & T_2 \\ r_{31} & r_{32} & r_{33} & T_3 \end{pmatrix} \begin{pmatrix} X \\ Y \\ Z \\ 1 \end{pmatrix} \end{align*} と表現することができます.これを \begin{align} x=(R|T)\tilde{X} \label{eq:2} \end{align} と書きます.\(\tilde{X}\)は\(X\)に\(1\)を追加したものという意味で,同次座標表現と言います. 同次座標(斉次座標とも言う)は無限遠点を扱うためのものだそうです. 今回の話に無限遠点は出てきませんので\(\tilde{X}\)という書き方だけを使います. 同次座標を使うと座標系の変換が行列の積で表せるようになります.
座標系そのものの回転や平行移動がどうなっているかを考えたくなりますが, 回転行列や移動ベクトルがそもそも成分表示されていますのでけっこう面倒です. とりあえず座標系そのものの回転や平行移動は考えないことにします. また \begin{align*} \vec{e_1}=\begin{pmatrix} e_{11} \\ e_{12} \\ e_{13} \end{pmatrix}, \vec{e_2}=\begin{pmatrix} e_{21} \\ e_{22} \\ e_{23} \end{pmatrix}, \vec{e_3}=\begin{pmatrix} e_{31} \\ e_{32} \\ e_{33} \end{pmatrix} \end{align*} は正規直交ベクトルですから \begin{align*} \begin{pmatrix} (\vec{e_1})^T \\ (\vec{e_2})^T \\ (\vec{e_3})^T \end{pmatrix} \begin{pmatrix} \vec{e_1} & \vec{e_2} & \vec{e_3} \end{pmatrix} =\begin{pmatrix} e_{11} & e_{12} & e_{13} \\ e_{21} & e_{22} & e_{23} \\ e_{31} & e_{32} & e_{33} \end{pmatrix} \begin{pmatrix} e_{11} & e_{21} & e_{31} \\ e_{12} & e_{22} & e_{32} \\ e_{13} & e_{23} & e_{33} \end{pmatrix} =\begin{pmatrix} 1 & 0 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 1 \end{pmatrix} \end{align*} となります.すなわち \begin{align*} R=\begin{pmatrix} e_{11} & e_{12} & e_{13} \\ e_{21} & e_{22} & e_{23} \\ e_{31} & e_{32} & e_{33} \end{pmatrix} =(R^{-1})^T \end{align*} \begin{align*} R^{-1}=\begin{pmatrix} e_{11} & e_{21} & e_{31} \\ e_{12} & e_{22} & e_{32} \\ e_{13} & e_{23} & e_{33} \end{pmatrix} =R^T \end{align*} です.
カメラと透視変換
物体からの光が必ずピンホールを通過するピンホールカメラを考えます. このピンホールに3次元座標\(oxyz\)の原点\(o\)を合わせます. 次に\(f\)点\((0, 0, f)\)で\(oz\)軸と垂直に交わる平面を考えこれを撮像面と言うことにします. ただし\(f\)は正で,撮像面はピンホールと物体の間にあるものとします. \(f\)点は焦点とも言います. 実際のピンホールカメラでは撮像面はピンホールを挟んで物体とは反対側ですので これは式の見通しを良くするための仮想の配置です. \(f\)点を原点とする2次元のピクセル座標\(fuv\)を撮像面に置き, \(fu\)軸と\(ox\)軸が平行になるように, \(fv\)軸と\(oy\)軸が平行になるようにします. このように2次元のピクセル座標\(fuv\)が配置された3次元座標\(oxyz\)をカメラ座標と言います.
物体上の点\((x, y, z)\)から発せられた光が 撮像面の点\((u, v)\)を通過するとき, \(x:z=u:f\)の関係がありますから\(u=fx/z\)となります. ピクセル座標がピクセルを単位とするのであればピクセルサイズを\(p\)として\(u=fx/{pz}\)となります. 同様に\(v=fy/{pz}\)です.これらを \begin{align*} s \begin{pmatrix} u \\ v \\ 1 \end{pmatrix}= \begin{pmatrix} f/p & 0 & 0 \\ 0 & f/p & 0 \\ 0 & 0 & 1 \end{pmatrix} \begin{pmatrix} x \\ y \\ z \end{pmatrix} \end{align*} と書きます.これは \( su=fx/p, sv=fy/p, s=z \) ということですから,第3式\(s=v\)を使うと,第1式は\(u=fx/{pz}\),第2式は\(v=fy/{pz}\)となるからです. また \begin{align*} s \begin{pmatrix} u \\ v \\ 1 \end{pmatrix}= \begin{pmatrix} f_x/p & 0 & c_x \\ 0 & f_y/p & c_y \\ 0 & 0 & 1 \end{pmatrix} \begin{pmatrix} x \\ y \\ z \end{pmatrix} \end{align*} は \( su=f_xx/p+c_xz, sv=f_yy/p+c_yz, s=z \) ということですから,第3式\(s=v\)を使うと,第1式は\(u=f_xx/{pz}+c_x\),第2式は\(v=f_yy/{pz}+c_y\)を表します. \((c_x, c_y)\)は中心(\(oz\)軸と撮像面との交点)のピクセル座標です. これはピクセル座標の原点が\(oz\)軸からずれてしまった場合のためにあります. \(f_x/p\)と\(f_y/p\)を新たに\(f_x\)と\(f_y\)として \begin{align} s \begin{pmatrix} u \\ v \\ 1 \end{pmatrix}= \begin{pmatrix} f_x & 0 & c_x \\ 0 & f_y & c_y \\ 0 & 0 & 1 \end{pmatrix} \begin{pmatrix} x \\ y \\ z \end{pmatrix} \label{eq:3} \end{align} と表した場合,この\(f_x\)と\(f_y\)はピクセルサイズを単位とする焦点距離です. 焦点距離に\(f_x\)と\(f_y\)の2つがあるのは\(x\)方向と\(y\)方向で値が異なることがあるからです. またもともと\(c_x\)と\(c_y\)はピクセルサイズを単位とする中央位置です. このカメラ座標系から撮像面のピクセル座標系への変換を透視変換と言います. \begin{align*} \tilde{m}= \begin{pmatrix} u \\ v \\ 1 \end{pmatrix} \end{align*} は同次座標表現となっています.同次座標を使うと透視変換も行列の積で表現できてしまうわけです. 上のいくつかの式で出てきた\(s\)は任意のある実数で同次座標を使うと必要になります. 式\eqref{eq:3}は \begin{align} s\tilde{m}=Ax \label{eq:4} \end{align} と書くことができます.\(A\)はカメラ行列と言います. 前のパラグラフの式\eqref{eq:1}と合わせて \begin{align} s\tilde{m}=A(RX+T) \label{eq:5} \end{align} または前のパラグラフの式\eqref{eq:2}と合わせて \begin{align*} s\tilde{m}=A(R|T)\tilde{X} \end{align*} となります.これらの式はカメラの基本式と呼ばれます. カメラの基本式はグローバル座標系からピクセル座標系への変換, すなわち実世界がどのように撮影されるのかを表しています.
カメラの間の関係
ワールド座標系からカメラ座標系への変換を表す式\eqref{eq:1}を左カメラ用と右カメラ用の2つ用意すると \begin{align} x_l=R_lX+T_l \label{eq:6} \end{align} \begin{align} x_r=R_rX+T_r \label{eq:7} \end{align} となります.式\eqref{eq:6}の両辺に左からある\(R\)を掛けると \begin{align*} Rx_l=RR_lX+RT_l \end{align*} となります.この\(R\)が\(R_r=RR_l\)となる\(R\)であったとすると,このことと式\eqref{eq:7}を使って \begin{align*} Rx_l=R_rX+RT_l=x_r-T_r+RT_l \end{align*} \begin{align*} x_r=Rx_l+T_r-RT_l \end{align*} と変形できます.\(T_r-RT_l\)を新たに\(-T\)と置くと \begin{align} x_r=Rx_l-T \label{eq:8} \end{align} となります.これは \(x_l\)を\(R\)だけ回転させて\(-T\)だけ平行移動すれば\(x_r\)となるということを表していますが, そのように言えるためには \begin{align*} R_r=RR_l \end{align*} \begin{align*} T_r=RT_l-T \end{align*} の関係が必要ということです. ここで\(R\)や\(T\)はカメラに写っているものの回転や平行移動で カメラそのものの回転や平行移動は考えないことにしたということに注意しておく必要があります. さてこれらと\eqref{eq:8}は2つのカメラに写っているものが相対的にどのような位置関係にあるのかを表しています. あとで使うので\eqref{eq:8}を \begin{align} x_l=R^{-1}(x_r+T) \label{eq:9} \end{align} と変形しておきます.
ステレオ並行化処理
カメラの基本式\eqref{eq:5}を分離して書いた\eqref{eq:4}と\eqref{eq:1}を 左カメラ用 \begin{align} s_l\tilde{m_l}=A_lx_l \label{eq:10} \end{align} \begin{align*} x_l=R_lX+T_l \end{align*} と右カメラ用 \begin{align*} s_r\tilde{m_r}=A_rx_r \end{align*} \begin{align} x_r=R_rX+T_r \label{eq:11} \end{align} に用意します. 式\eqref{eq:10}の両辺に左から\(A_l^{-1}\)を掛けて \begin{align*} s_lA_l^{-1}\tilde{m_l}=x_l \end{align*} と変形して\eqref{eq:9}を使うと \begin{align*} s_lA_l^{-1}\tilde{m_l}=R^{-1}(x_r+T) \end{align*} となります.さらに\eqref{eq:11}を使うと \begin{align*} s_lA_l^{-1}\tilde{m_l}=R^{-1}(R_rX+T_r+T) \end{align*} と変形できます.両辺に左から\(R\)を掛けると \begin{align*} s_lRA_l^{-1}\tilde{m_l}=R_rX+T_r+T \end{align*} となります.さらに両辺に左から\(A_r\)を掛けると \begin{align} s_lA_rRA_l^{-1}\tilde{m_l}=A_r(R_rX+T_r+T) \label{eq:12} \end{align} となります. \(\tilde{m_l}\)を \begin{align} \tilde{\dot{m_l}}=A_rRA_l^{-1}\tilde{m_l} \label{eq:13} \end{align} によって変換した \(\tilde{\dot{m_l}}\)を使うと \begin{align*} s_l\tilde{\dot{m_l}}=A_r(R_rX+T_r+T) \end{align*} を得ることができます. これと右カメラの基本式 \begin{align} s_r\tilde{m_r}=A_r(R_rX+T_r) \label{eq:14} \end{align} を比べてみると式\eqref{eq:13}によって変換を受けた左ピクセル画像には 全く同じ右カメラのなかで対象が\(T\)だけ平行移動したときの投影画像が得られるということが分かります. さらに\(T\)を右カメラの\(ox\)軸方向の移動とするために \begin{align*} LT=c\begin{pmatrix} e_{r11} \\ e_{r12} \\ e_{r13} \end{pmatrix}=b \end{align*} となる回転行列\(L\)を考え\eqref{eq:12}と\eqref{eq:14}の両辺に左から \(A_rLA_r^{-1}\)を掛けると \begin{align*} s_lA_rLRA_l^{-1}\tilde{m_l}=A_r(LR_rX+LT_r+b) \end{align*} \begin{align*} s_rA_rLA_r^{-1}\tilde{m_r}=A_r(LR_rX+LT_r) \end{align*} となってめでたくステレオ並行化が完了します. ピクセル変換は両方の画像で行う必要がありますが \begin{align} \tilde{\ddot{m_l}}=A_rLRA_l^{-1}\tilde{m_l} \label{eq:15} \end{align} \begin{align} \tilde{\ddot{m_r}}=A_rLA_r^{-1}\tilde{m_r} \label{eq:16} \end{align} と変換した 右画像\(\tilde{\ddot{m_r}}\) と 左画像\(\tilde{\ddot{m_l}}\) は あるカメラの撮影画像(右画像)とそのカメラが 正確に\(ox\)軸方向に\(-c\)だけ並行移動して撮影した画像(左画像)となっています. ここで初めてカメラそのものの平行移動というものを考えましたが, 写っているものが\(ox\)軸方向に\(c\)だけ並行移動している状況は カメラが\(ox\)軸方向に\(-c\)だけ並行移動したために発生したと考えられるからです. また\(L\)には\(ox\)軸を回転軸とする任意に回転の分だけ自由度があることにも注意が必要です. 次に,この画像変換を行うOpenCVのinitUndistortRectifyMap関数について説明します.
initUndistortRectifyMap関数
OpenCVのinitUndistortRectifyMap関数の引数は
- InputArray cameraMatrix
- InputArray distCoeffs
- InputArray R
- InputArray newCameraMatrix
- Size size
- int m1type
- OutputArray map1
- OutputArray map2