Pynote

Python、機械学習、画像処理について

OpenCV - カメラキャリブレーションを行う方法

カメラキャリブレーション

内部パラメータ及び外部パラメータを推定することをカメラキャリブレーションという。


  • 内部パラメータ: カメラ固有のレンズの歪み等を表すパラメータ。
  • 外部パラメータ: カメラの位置、姿勢を表すパラメータ。

内部パラメータ及び外部パラメータについては以下の記事を参照されたい。

pynote.hatenablog.com

手順

1. キャリブレーション器具を様々な距離、角度から撮影する。

まず、チェスボードまたはサークルグリッドと呼ばれるキャリブレーション器具を様々な距離、角度から撮影する。

今回はチェスボードを撮影した以下の13枚の画像を使用する。
ダウンロード

import glob

import cv2
import matplotlib.pyplot as plt
import numpy as np

# チェスボードを撮影した画像を読み込む。
samples = []

for path in glob.glob('samples/*.jpg'):
    img = cv2.imread(path)
    samples.append(img)

print('number of samples:', len(samples))  # number of samples: 13
print('image shape:', samples[0].shape)  # image shape: (480, 640, 3)

plt.imshow(samples[0])
plt.show()

キャリブレーション器具でマーカー検出を行う。

画像処理により、画像に写っているキャリブレーション器具のマーカーを検出し、その画像上の位置を求める。
マーカーはチェスボードの場合は交点、サークルグリッドの場合は丸い点である。
今回はチェスボードなので、findChessBoardCorners() を使用する。
引数にチェスボードの交点数を指定する必要がある。(マス目の数ではなく、交点の数なので注意)


retval, corners = cv2.findChessboardCorners(image, patternSize[, corners[, flags]])
  • 引数
    • image: 画像
    • patternSize: チェスボードの交点数を (列、行) で指定する。
    • corners: 検出された交点の位置一覧 (引数経由で受け取る場合)
    • flags: フラグ
  • 返り値
    • retval: すべての交点の検出に成功した場合は True、そうでない場合は False
    • corners: 検出された交点の位置一覧
# チェスボードの設定
cols = 9  # 列方向の交点数
rows = 6  # 行方向の交点数

# チェスボードのマーカー検出を行う。
image_points = []
for img in samples:
    # 画像をグレースケールに変換する。
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    
    # チェスボードの交点を検出する。
    ret, corners = cv2.findChessboardCorners(img, (cols, rows))

    if ret:  # すべての交点の検出に成功
        image_points.append(corners)
    else:
        print('corners detection failed.')

findChessboardCorners() の返り値である corners の形状を見てみると、(54, 1, 2) となっている。
交点数は 6 \times 9 = 54 であったので、[[[1個目の交点の xy 座標], [2個目の交点の xy 座標], ...] を表している。
drawChessboardCorners() で検出結果を画像に描画できるので、その結果を表示してみる。

# 1枚目の交点検出結果を可視化する。
print('corners shape', image_points[0].shape)  # corners shape (54, 1, 2)

img = samples[0].copy()
cv2.drawChessboardCorners(img, (cols, rows), image_points[0], ret)
plt.imshow(img)
plt.show()


検出した画像座標上の点に対応する3次元上の点を作成する。

3次元上の座標軸をどのように設定するかは自由に決めてよい。
通常はキャリブレーションボード上が xy 平面とし、交点上に原点をとる。
このように座標軸を決めることで、チェスボードの行、列方向の交点数及び正方形のマス目の一辺の長さ (今回は 20 mm) は既知なので、検出した画像座標上の点に対応する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(image_points[0], world_points):
    print('image coordinate: {} <-> world coordinate: {}'.format(img_pt, world_pt))

# 画像の枚数個複製する。
object_points = [world_points] * len(samples)

パラメータ推定を実行する。

画像座標系の点と世界座標系の対応する点の一覧ができたので、calibrateCamera() でこれらを元にパラメータを推定する。

retval, cameraMatrix, distCoeffs, rvecs, tvecs = \
    cv.calibrateCamera(objectPoints, imagePoints, imageSize,
                       cameraMatrix, distCoeffs[, rvecs[, tvecs[, flags[, criteria]]]])
  • 引数
    • objectPoints: 世界座標系の点一覧
    • imagePoints: 画像座標系の点一覧
    • imageSize: 画像の大きさ
    • cameraMatrix: カメラ行列 (内部パラメータ, 引数経由で受け取る場合)
    • distCoeffs: 歪み係数 (内部パラメータ, 引数経由で受け取る場合)
    • rvecs: 回転ベクトル (外部パラメータ, 引数経由で受け取る場合)
    • tvecs: 平行移動成分 (外部パラメータ, 引数経由で受け取る場合)
    • 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, image_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)