カメラキャリブレーション (Camera Calibration, Camera Resectioning) を行うと、レンズの歪みを表現するパラメータや、カメラのワールド座標系での位置姿勢を推定できます。
チェスボードのようなキャリブレーション専用のボードが利用されます。
キャリブレーションで得られたパラメータを用いると、例えば歪みを補正することができます。
カメラキャリブレーションのおおまかな流れは以下のようになります。
キャリブレーションボードの画像をカメラで撮影
キャリブレーションで利用するのは距離センサで取得した情報ではなく、カメラで取得した平面画像です。カメラの位置を固定したまま、キャリブレーションボードを様々な位置に移動させて画像を撮影します。
キャリブレーションボードの各点の座標を取得
画像処理を行い、キャリブレーションボードの各点の、平面画像内での座標 を取得します。
連立方程式を立てる
画像処理で取得したキャリブレーションボードの各点の座標 と、ある座標系におけるキャリブレーションボードの各点の座標 は既知です。例えばローカル座標系におけるキャリブレーションボードの各点の座標は (0,0,0), (1,0,0), (2,0,0) ....,(6,5,0)
のようになります。
ローカル座標系からカメラの視点座標系への変換行列 T
と、カメラの視点座標系から平面画像に投影するための、カメラモデルを表現する行列 K
を用いると以下のような連立方程式が立てられます。キャリブレーションにおいて、K
と T
をそれぞれ内部パラメータ (Intrinsics) および外部パラメータ (Extrinsics) とよびます。
一般性を失うことなく、 座標が 0 となるような、キャリブレーションボードのローカル座標系を考えることができます。その場合は、外部パラメータの行列は少しだけ簡単になります。
K
はカメラの種類に応じて異なりますが、ピンホールカメラの場合は以下のようになることが知られています。
以下の図において、物体 の座標 はカメラの視点座標系における値です。ピンホール (真ん中に微小な穴が開いている架空の壁) は 平面の点 で表現されています。ピンホール から入った光は、撮像素子となる平面 の点 に到達します。分かりやすさのため、平面 を物体 と同じ側に仮想的に反転移動して考えています。
Camera Calibration and 3D Reconstruction
理想的なピンホールカメラであれば、ピンホールの光軸 は平面 の を通るようにできますが、実際には製造の過程で多少のずれが発生します。光軸が通る点の平面 における座標を とすることでずれを表現しています。このずれは、キャリブレーションで推定する、カメラの内部パラメータの一つです。
また、画像処理で得られる、画像平面内の座標 はピクセル座標です。撮像素子である平面 xy
におけるピクセルは正方形ではないことが一般的です。そのため、距離とピクセル座標の変換において、x 軸と y 軸で別々の値を用いる必要があります。三角形の相似を考えると、以下のような式が得られます。これを行列で表現すると のようになります。
レンズの歪み
一点しか光を通す穴がない理想的なピンホールを用いる場合は歪みは発生しませんが、実際には一点しか穴がないと露光時間中に十分な光量を得られないためレンズを用いる必要があります。レンズを用いると十分な光量を得られる一方、その代償として歪みが発生します。
OpenCV は特に影響の大きい「半径方向歪み」と「円周方向歪み」の二つを考慮した実装がなされています。
半径方向歪みはレンズの形状に起因する歪みで、レンズの中心から離れた場所を通過する光は中心付近を通過する光よりも大きく曲げられることによります。魚眼のようになります。補正のためのパラメータはテイラー級数になります。実際には最初の 2 項だけ考えていれば十分な場合が多く、OpenCV の calibrateCamera
が返す歪みパラメータの個数も最初の数個です。
円周方向歪みは、撮像素子がピンホールの平面 に対して平行になっていない、組立て工程に起因します。パラメータ の二つで表現できます。OpenCV の calibrateCamera
が返す歪みパラメータの一つです。
必要となるキャリブレーション画像の枚数
カメラキャリブレーションで推定する必要のあるパラメータは三種類です。
- 内部パラメータ 4つ (キャリブレーションボードの位置姿勢によらず一定)
- 外部パラメータ 回転 3つ と平行移動 3つ で合計 6つ (キャリブレーションボードの位置姿勢一つに対して 6 つ)
- 歪みパラメータ
透視変換を考えると、キャリブレーション画像一枚で利用可能な点は、例えば一番右上、右下、左上、左下の4つだけです。得られるパラメータは透視変換行列の 8 つです
- 歪みがない場合、キャリブレーション画像一枚で推定する必要のあるパラメータの個数は 10 です。8 つ求まりますが足りません。
- 歪みがない場合、キャリブレーション画像二枚で推定する必要のあるパラメータの個数は 4 + 6 + 6 で 16 です。透視変換行列二つで 16 となるため理想的には最低二枚のキャリブレーション画像が必要ということになります。
しかしながら、実際には歪みだけでなくノイズの影響があるため 10 枚程度は必要になります。
OpenCV で連立方程式を解く
OpenCV にはカメラキャリブレーションのための機能が実装されています。キャリブレーションで利用する画像のサンプルも OpenCV に含まれています。
$ ls samples/data/left*.jpg | head -2
samples/data/left01.jpg
samples/data/left02.jpg
cornerSubPix
は内部的に既に findChessboardCorners
で利用されてはいますが、更に精度を高めるために、事実上もう一度実行しておくとよいです。
sample.py
#!/usr/bin/python
# -*- coding: utf-8 -*-
import numpy as np
import cv2 as cv
import glob
# ワールド座標系におけるキャリブレーションボードの各点の座標
# (0,0,0), (1,0,0), (2,0,0) ....,(6,5,0)
objp = np.zeros((6*7,3), np.float32)
objp[:,:2] = np.mgrid[0:7,0:6].T.reshape(-1,2)
# キャリブレーションで利用する座標
objpoints = [] # 3d point in real world space
imgpoints = [] # 2d points in image plane.
# cornerSubPix の閾値
criteria = (cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER, 30, 0.001)
images = glob.glob('*.jpg')
for fname in images:
img = cv.imread(fname)
# グレースケールで利用
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
# キャリブレーションボード内の点の座標を取得
ret, corners = cv.findChessboardCorners(gray, (7,6), None)
# キャリブレーションのために結果を保存
if ret == True:
objpoints.append(objp)
# 座標の精度を上げる
corners2 = cv.cornerSubPix(gray, corners, (11,11), (-1,-1), criteria)
imgpoints.append(corners2)
# 確認のため画像を表示
cv.drawChessboardCorners(img, (7,6), corners2, ret)
cv.imshow('img', img)
cv.waitKey(0)
cv.destroyAllWindows()
imageSize = gray.shape[::-1]
import IPython
IPython.embed()
上記スクリプトで用意した imgpoints と objpoints による連立方程式を解くためには cv.calibrateCamera
を利用します。err
が小さい程よいキャリブレーション結果であると言えます。内部パラメータ (Intrinsics) KK
、 が格納されてる歪み係数 distCoeffs
、外部パラメータ (Extrinsics) rvecs
および tvecs
が得られます。外部パラメータは環境におけるカメラの位置姿勢の推定結果と考えることもできます。rvecs
はロドリゲス形式のベクトルです。
err, KK, distCoeffs, rvecs, tvecs = cv.calibrateCamera(objpoints, imgpoints, imageSize, None, None)
In [2]: err
Out[2]: 0.15536900918594446
In [3]: KK
Out[3]:
array([[534.07088626, 0. , 341.53407091],
[ 0. , 534.11914802, 232.94565231],
[ 0. , 0. , 1. ]])
In [4]: distCoeffs
Out[4]:
array([[-2.92971621e-01, 1.07706888e-01, 1.31038490e-03,
-3.11023081e-05, 4.34799129e-02]])
In [5]: rvecs[0]
Out[5]:
array([[-0.3784336 ],
[-0.18064237],
[-3.11615995]])
In [6]: tvecs[0]
Out[6]:
array([[ 2.82321765],
[ 2.22374307],
[10.95762955]])
歪み補正 rectification
歪み補正マップを計算する方法
得られたパラメータで歪み補正を行います。補正したい画像を読み込みます。
targetImage = cv.imread('left12.jpg')
targetImageSize = targetImage.shape[:2][::-1]
歪み係数を用いてカメラ行列を計算しなおします。補正した後の画像と補正前の画像のサイズは異なります。alpha に 1 を指定すると、補正後の画像において、補正前にはないピクセルは 0 で埋められます。
alpha = 1
newKK, roiSize = cv.getOptimalNewCameraMatrix(KK, distCoeffs, targetImageSize, alpha, targetImageSize)
元画像のピクセルの、新しい画像における X 座標の情報を持つ mapX
と、Y 座標の情報を持つ mapY
を計算します。歪み補正マップとよばれます。
mapX, mapY = cv.initUndistortRectifyMap(KK, distCoeffs, None, newKK, targetImageSize, cv.CV_32FC1)
歪み補正を行います。
undistortedImage = cv.remap(targetImage, mapX, mapY, cv.INTER_LINEAR)
alpha を 1 にしたことによる 0 で埋められた箇所を取り除いてみます。
x, y, w, h = roiSize
undistortedImage2 = undistortedImage[y:y+h, x:x+w]
以下のようになります。
cv.imshow('undistortedImage', undistortedImage)
cv.imshow('undistortedImage2', undistortedImage2)
cv.waitKey()
より簡単な cv.undistort の利用
歪みを補正する画像が一枚だけの場合は cv.undistort
を利用した方が簡単です。
targetImage = cv.imread('left12.jpg')
targetImageSize = targetImage.shape[:2][::-1]
alpha = 1
newKK, roiSize = cv.getOptimalNewCameraMatrix(KK, distCoeffs, targetImageSize, alpha, targetImageSize)
undistortedImage = cv.undistort(targetImage, KK, distCoeffs, None, newKK)
x, y, w, h = roiSize
undistortedImage2 = undistortedImage[y:y+h, x:x+w]
cv.imwrite('calibresult.png', undistortedImage2)
calibresult.png
関連記事
- 輪郭に関連した画像処理 (OpenCV3 C++)cv::Canny などで検出したエッジをもとに cv::findContours で輪郭を計算できます。輪郭に関連した処理の例を記載します。 輪郭の描画 #include <opencv2/opencv.hpp> #in
- Qt for Python (PySide2) の基本的な使い方QT を Python から利用するためのライブラリには PyQt や PySide 等が存在します。PySide は元々 QT4 向けのライブラリでしたが、QT5 に対応するために新たに PySide2 が開発されました。PySide2 は Qt for Python ともよばれています。 Q: PySide? Qt for Python? what is the name?A: The nam...
- OpenGL を Python3 から利用するための環境設定および簡単なサンプルコード (Linux)コンピュータグラフィックスのレンダリングライブラリの一つである OpenGL を Python3 から利用するための Linux 環境を準備して、設定例およびサンプルコードを記載します。特にここでは Debian9 を利用します。 Getting Started [Language bindings / Python](ht
- 立方体を回転させるサンプル (OpenGL、Python)立方体を二つ配置して回転させてみます。ライブラリを用いずに OpenGL API を直接利用します。 wget https://gist.githubusercontent.com/harubot/df886254396a449038ee542ed317f7b3/raw/92216e02d0210b9d81770562ddf7741339f1b286/opengl-setup2.py DISPLA...
- Open Dynamics Engine によるロボットの自己位置の推定 (Python)ロボットアプリケーションを開発する際に、ロボットの自己位置を推定する必要がある場合を考えます。ここでは Open Dynamics Engine を Python から利用した場合について、自己位置推定のサンプルを記載します。自己位置推定と環境の地図作成を同時に行う場合を SLAM (Simultaneous Localization and Mapping) とよびます。 検証に用いる車輪型ロボ...