![]() |
OpenCV 5.0.0
Open Source Computer Vision
|
このセクションでは、次のことを学ぶ
ピンホールカメラの中には、画像に大きな歪みをもたらすものがある。主な歪みには2種類あり、放射状歪み (radial distortion) と接線方向歪み (tangential distortion) である。
放射状歪みは、直線が曲がって見える原因となる。放射状歪みは、点が画像の中心から離れるほど大きくなる。例えば、下に示す画像ではチェスボードの2つの辺が赤い線で示されている。しかし、チェスボードの境界が直線になっておらず、赤い線と一致していないことがわかる。本来は直線であるべき線がすべて外側に膨らんでいる。詳細は Distortion (optics) を参照のこと。
以降のセクションでは、いくつかの新しい引数を導入する。詳細は カメラキャリブレーションと3次元再構成 を参照のこと。
放射状歪みは次のように表せる。
\[x_{distorted} = x( 1 + k_1 r^2 + k_2 r^4 + k_3 r^6) \\ y_{distorted} = y( 1 + k_1 r^2 + k_2 r^4 + k_3 r^6)\]
同様に、接線方向歪みは、撮影レンズが結像面に対して完全に平行に配置されていないために生じる。そのため、画像内の一部の領域が予想より近くに見えることがある。接線方向歪みの量は次のように表せる。
\[x_{distorted} = x + [ 2p_1xy + p_2(r^2+2x^2)] \\ y_{distorted} = y + [ p_1(r^2+ 2y^2)+ 2p_2xy]\]
要するに、歪み係数 (distortion coefficients) と呼ばれる次の5つの引数を求める必要がある。
\[Distortion \; coefficients=(k_1 \hspace{10pt} k_2 \hspace{10pt} p_1 \hspace{10pt} p_2 \hspace{10pt} k_3)\]
これに加えて、カメラの内部引数や外部引数といった他の情報も必要となる。内部引数 (Intrinsic parameters) はカメラ固有のものである。これには焦点距離 ( \(f_x,f_y\)) や光学中心 ( \(c_x, c_y\)) などの情報が含まれる。焦点距離と光学中心を使ってカメラ行列を作成でき、これは特定のカメラのレンズによる歪みを除去するために使える。カメラ行列は特定のカメラに固有のものなので、いったん計算すれば、同じカメラで撮影した他の画像にも再利用できる。これは3x3行列として表される。
\[camera \; matrix = \left [ \begin{matrix} f_x & 0 & c_x \\ 0 & f_y & c_y \\ 0 & 0 & 1 \end{matrix} \right ]\]
外部引数 (Extrinsic parameters) は、3D点の座標を座標系へ変換する回転ベクトルおよび並進ベクトルに対応する。
ステレオの用途では、まずこれらの歪みを補正する必要がある。これらの引数を求めるには、よく定義されたパターン (例えばチェスボード) のサンプル画像をいくつか用意しなければならない。相対位置が既知の特定の点 (例えばチェスボードのマスの角) を見つける。これらの点の実世界空間における座標も画像中の座標もわかっているので、歪み係数を解くことができる。より良い結果を得るには、少なくとも10枚のテストパターンが必要である。
前述のとおり、カメラキャリブレーションには少なくとも10枚のテストパターンが必要である。OpenCV にはチェスボードの画像がいくつか付属しているので (samples/data/left01.jpg – left14.jpg を参照)、これらを利用する。チェスボードの画像を考える。カメラのキャリブレーションに必要な重要な入力データは、3Dの実世界点の集合と、それらの点に対応する画像中の2D座標である。2D画像点は画像から容易に求められるので問題ない。(これらの画像点は、チェスボードで2つの黒いマスが互いに接する位置である)
では実世界空間の3D点はどうだろうか。これらの画像は固定されたカメラから撮影され、チェスボードはさまざまな位置と向きに置かれている。したがって \((X,Y,Z)\) の値を知る必要がある。しかし単純化のために、チェスボードはXY平面上に静止していた (つまり常に Z=0) とし、カメラがそれに応じて移動したと考えることができる。この考え方により、X, Y の値だけを求めればよくなる。さて X, Y の値については、点の位置を表す (0,0), (1,0), (2,0), ... として単純に点を渡せばよい。この場合、得られる結果はチェスボードのマスのサイズを単位としたスケールになる。しかしマスのサイズ (例えば30 mm) がわかっていれば、値を (0,0), (30,0), (60,0), ... として渡せる。こうすると結果が mm 単位で得られる。(ここではこれらの画像を自分で撮影したわけではないのでマスのサイズがわからないため、マスのサイズを単位として渡す)
3D点は オブジェクト点 (object points) と呼ばれ、2D画像点は 画像点 (image points) と呼ばれる。
チェスボード内のパターンを見つけるには、関数 cv.findChessboardCorners() を使用できる。また、8x8 グリッドや 5x5 グリッドなど、どのようなパターンを探すのかを渡す必要がある。この例では 7x6 グリッドを使用する。(通常、チェスボードは 8x8 のマスと 7x7 の内部コーナーを持つ)。この関数はコーナー点と retval を返し、パターンが得られた場合 retval は True になる。これらのコーナーは(左から右、上から下の)順序で配置される。
コーナーを見つけたら、cv.cornerSubPix() を使ってその精度を高めることができる。また、cv.drawChessboardCorners() を使ってパターンを描画することもできる。これらのステップはすべて以下のコードに含まれている:
パターンを描画した画像の一例を下に示す。
オブジェクト点と画像点が揃ったので、キャリブレーションを実行する準備ができた。関数 cv.calibrateCamera() を使用できる。この関数はカメラ行列、歪み係数、回転ベクトル、並進ベクトルなどを返す。
これで、画像を取得して歪みを補正できる。OpenCV にはこれを行うための2つの方法がある。ただしまず、自由スケーリング引数に基づいて cv.getOptimalNewCameraMatrix() を使ってカメラ行列を調整できる。スケーリング引数 alpha=0 の場合、不要なピクセルが最小限になった歪み補正画像を返す。そのため画像のコーナー部分のピクセルが除去されることもある。alpha=1 の場合、すべてのピクセルが保持され、余分な黒い領域が加わる。この関数はまた、結果をクロップするために使用できる画像 ROI も返す。
そこで新しい画像 (ここでは left12.jpg。これは本章の最初の画像である) を取得する。
これが最も簡単な方法である。関数を呼び出し、上で得られた ROI を使って結果をクロップするだけでよい。
この方法は少しだけ難しい。まず、歪んだ画像から歪み補正画像へのマッピング関数を求める。次に remap 関数を使う。
それでも、両方の方法で同じ結果が得られる。下の結果を参照のこと。
結果を見ると、すべてのエッジが直線になっていることがわかる。
これで、今後の利用のためにカメラ行列と歪み係数を NumPy の書き込み関数 (np.savez, np.savetxt など) を使って保存できる。
再投影誤差は、求めた引数がどれだけ正確かをよく示す指標となる。再投影誤差がゼロに近いほど、求めた引数はより正確である。内部行列、歪み行列、回転行列、並進行列が与えられたら、まず cv.projectPoints() を使ってオブジェクト点を画像点に変換しなければならない。次に、この変換で得た結果とコーナー検出アルゴリズムとの間のノルムを計算できる。RMSE(二乗平均平方根誤差)を求めるには、すべての点と画像にわたって二乗誤差を平均し、その平方根を取る。