Pynote

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

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

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

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


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

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

pynote.hatenablog.com

手順

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

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

今回はチェスボードを撮影した以下の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) となっている。
交点数は 6 \times 9 = 54 であったので、この配列は [[[1個目の交点の xy 座標], [2個目の交点の xy 座標], ...] を表している。

print("corners shape", img_points[0].shape)  # corners shape (54, 1, 2)

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

3次元上の座標軸をどのように設定するかは自由に決めてよい。
通常はキャリブレーションボード上が xy 平面とし (つまり、z = 0)、どれかの交点に原点をとる。
このように座標軸を決めることで、チェスボードの行、列方向の交点数は既知なので、検出した画像座標上の点に対応する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)