OpenCV - カメラキャリブレーションを行う方法
概要
OpenCV でカメラキャリブレーションを行う方法について紹介する。
カメラキャリブレーション
内部パラメータ及び外部パラメータを推定することをカメラキャリブレーションという。
- 内部パラメータ: カメラ固有のレンズの歪み等を表すパラメータ。
- 外部パラメータ: カメラの位置、姿勢を表すパラメータ。
内部パラメータ及び外部パラメータについては以下の記事を参照されたい。
手順
キャリブレーション器具を様々な距離、角度から撮影する。
まず、チェスボードまたはサークルグリッドと呼ばれるキャリブレーション器具を様々な距離、角度から撮影する。
今回はチェスボードを撮影した以下の13枚の画像を使用する。
from pathlib import Path import cv2 import matplotlib.pyplot as plt import numpy as np # チェスボードを撮影した画像があるディレクトリ img_dir = Path("samples") # 画像を読み込む。 samples = [] for path in img_dir.glob("*.jpg"): img = cv2.imread(str(path)) samples.append(img) # 画像を確認する。 fig = plt.figure(figsize=(10, 14), facecolor="w") for i, img in enumerate(samples, 1): ax = fig.add_subplot(5, 3, i) ax.set_axis_off() ax.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)) plt.show()
キャリブレーション器具でマーカー検出を行う。
画像処理により、画像に写っているキャリブレーション器具のマーカーを検出し、その画像上の位置を求める。
マーカーはチェスボードの場合は交点、サークルグリッドの場合は丸い点である。
今回はチェスボードなので、cv2.findChessBoardCorners() を使用する。
引数にチェスボードの交点数を指定する必要がある。(マス目の数ではなく、交点の数なので注意)
retval, corners = cv2.findChessboardCorners(image, patternSize[, corners[, flags]])
- 引数
- image: 画像
- patternSize: チェスボードの交点数を (列、行) で指定する。
- flags: フラグ
- 返り値
- retval: すべての交点の検出に成功した場合は True、そうでない場合は False
- corners: 検出された交点の位置一覧
返り値の corners 及び retval を cv2.drawChessboardCorners() に渡すことで、検出結果を画像に描画できる。
# チェスボードの設定 cols = 9 # 列方向の交点数 rows = 6 # 行方向の交点数 fig = plt.figure(figsize=(10, 14), facecolor="w") # チェスボードのマーカー検出を行う。 img_points = [] for i, img in enumerate(samples, 1): # 画像をグレースケールに変換する。 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # チェスボードの交点を検出する。 ret, corners = cv2.findChessboardCorners(img, (cols, rows)) if ret: # すべての交点の検出に成功 img_points.append(corners) else: print("Failed to detect chessboard corners.") # 検出結果を描画する。 dst = cv2.drawChessboardCorners(img.copy(), (cols, rows), corners, ret) ax = fig.add_subplot(5, 3, i) ax.set_axis_off() ax.imshow(cv2.cvtColor(dst, cv2.COLOR_BGR2RGB)) plt.show()
返り値 corners の形状を見てみると、(54, 1, 2) となっている。
交点数は であったので、この配列は [[[1個目の交点の xy 座標], [2個目の交点の xy 座標], ...] を表している。
print("corners shape", img_points[0].shape) # corners shape (54, 1, 2)
検出した画像座標上の点に対応する3次元上の点を作成する。
3次元上の座標軸をどのように設定するかは自由に決めてよい。
通常はキャリブレーションボード上が 平面とし (つまり、)、どれかの交点に原点をとる。
このように座標軸を決めることで、チェスボードの行、列方向の交点数は既知なので、検出した画像座標上の点に対応する3次元上の点を作成できる。
# 検出した画像座標上の点に対応する3次元上の点を作成する。 world_points = np.zeros((rows * cols, 3), np.float32) world_points[:, :2] = np.mgrid[:cols, :rows].T.reshape(-1, 2) print("world_points shape:", world_points.shape) # world_points shape: (54, 3) for img_pt, world_pt in zip(img_points[0], world_points): print("image coordinate: {} <-> world coordinate: {}".format(img_pt, world_pt)) # 画像の枚数個複製する。 object_points = [world_points] * len(samples)
パラメータ推定を実行する。
画像座標系の点と世界座標系の対応する点の一覧ができたので、cv2.calibrateCamera() でこれらを元にパラメータを推定する。
retval, cameraMatrix, distCoeffs, rvecs, tvecs = \
cv2.calibrateCamera(objectPoints, imagePoints, imageSize,
cameraMatrix, distCoeffs[, rvecs[, tvecs[, flags[, criteria]]]])
- 引数
- objectPoints: 世界座標系の点一覧
- imagePoints: 画像座標系の点一覧
- imageSize: 画像の大きさ
- flags: フラグ
- criteria: 最適化の反復を終了する基準
- 返り値
- retval: 交点検出に成功した場合は True、そうでない場合は False
- cameraMatrix: カメラ行列 (内部パラメータ)
- distCoeffs: 歪み係数 (内部パラメータ)
- rvecs: 回転ベクトル (外部パラメータ)
- tvecs: 平行移動成分 (外部パラメータ)
h, w, c = samples[0].shape ret, camera_matrix, distortion, rvecs, tvecs = cv2.calibrateCamera( object_points, img_points, (w, h), None, None ) print("reprojection error:\n", ret) print("camera matrix:\n", camera_matrix) print("distortion:\n", distortion) print("rvecs:\n", rvecs[0].shape) print("tvecs:\n", tvecs[0].shape)
reprojection error: 0.3811715473367915 camera matrix: [[ 531.14749874 0. 341.82112771] [ 0. 531.4320961 235.03969878] [ 0. 0. 1. ]] distortion: [[ -2.74391331e-01 -3.07977600e-02 8.95663893e-04 -2.21525979e-04 2.76812366e-01]] rvecs: (3, 1) tvecs: (3, 1)
カメラパラメータを保存する。
カメラ行列、歪み係数はカメラ固有の内部パラメータであり、他の処理でも使うので、ファイルに保存しておく。
np.savez("camera-params.npz", camera_matrix=camera_matrix, distortion=distortion)