😸

Raspberry Pi 5に接続した魚眼レンズカメラの画像の歪みを校正する

に公開

以前の記事では、Raspberry Pi 5にRaspberry Pi用魚眼レンズカメラモジュール(8MP / IMX219)を接続して、色ムラ等の補正をして撮影をしました。

このままでは以下のように歪んだ形の画像が撮影されます。

本記事ではOpenCVのパッケージを使って歪んだ形の画像の歪みを校正します。

校正用画像の撮影

校正用のパターン画像を印刷して、10~20枚ほど撮影します。この時、以下の点について意識して様々な条件で撮影します。(今回はpng画像で撮影しました)

  • 様々なパターン画像までの距離や傾ける角度で撮影する
  • 画像の端までパターンの内部の交点が来ている画像を撮影する

以下に撮影例を示します。



画像から校正用データを生成

以下のプログラムを実行します。普通のカメラの歪みの校正データ用の関数ではなく、魚眼レンズ用のcv2.fisheye.calibrate()を呼び出しています。

import cv2
import numpy as np
import glob

# チェスボードのサイズ (内部コーナーの数)
CHECKERBOARD = (10, 7)  # 例: 横に6個、縦に9個の内部コーナー

# チェスボードの各マスのサイズ (mm)
SQUARE_SIZE = 25.0  # 例: 25mm

# 画像のパス
image_dir = "./"  # 撮影したチェスボード画像のディレクトリ
images = glob.glob(image_dir + "*.png")  # または "*.jpg" など

# 物体座標と画像座標を格納するリスト
objpoints = []  # 3D空間内のチェスボードコーナーの座標
imgpoints = []  # 画像内のチェスボードコーナーのピクセル座標

# チェスボードの物体座標を生成
objp = np.zeros((1, CHECKERBOARD[0] * CHECKERBOARD[1], 3), np.float32)
objp[0, :, :2] = np.mgrid[0:CHECKERBOARD[0], 0:CHECKERBOARD[1]].T.reshape(-1, 2)
objp *= SQUARE_SIZE  # マスのサイズを考慮

# 各画像に対して処理
for fname in images:
    img = cv2.imread(fname)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    h, w = gray.shape[:2]

    # チェスボードのコーナーを検出
    ret, corners = cv2.findChessboardCorners(gray, CHECKERBOARD, None)

    if ret:
        objpoints.append(objp)

        # コーナーの精度を向上させる
        criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.1)
        corners2 = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria)
        imgpoints.append(corners2)

        # コーナーを描画 (デバッグ用)
        cv2.drawChessboardCorners(img, CHECKERBOARD, corners2, ret)
        cv2.imshow("img", img)
        cv2.waitKey(1)  # 少し待つ

cv2.destroyAllWindows()

# 魚眼レンズのキャリブレーション
K = np.eye(3)  # 初期カメラ行列
D = np.zeros((4, 1))  # 初期歪み係数
rvecs = [np.zeros((1, 1, 3), dtype=np.float64) for i in range(len(objpoints))]
tvecs = [np.zeros((1, 1, 3), dtype=np.float64) for i in range(len(objpoints))]

rms, K, D, rvecs, tvecs = cv2.fisheye.calibrate(
    objpoints,
    imgpoints,
    (w, h),
    K,
    D,
    rvecs,
    tvecs,
    flags=cv2.fisheye.CALIB_RECOMPUTE_EXTRINSIC + cv2.fisheye.CALIB_CHECK_COND + cv2.fisheye.CALIB_FIX_SKEW,
    criteria=(cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 1e-6)
)

print("RMS:", rms)
print("K:\n", K)
print("D:\n", D)

# キャリブレーションデータを保存 (オプション)
np.savez("fisheye_calibration.npz", K=K, D=D, DIM=(w, h))
print("Calibration data saved to fisheye_calibration.npz")

校正用データで画像を補正

import cv2
import numpy as np
import sys
def undistort_fisheye_image(input_image_path, output_image_path, K, D, DIM):
    """
    魚眼レンズ画像を指定されたパラメータで補正します。

    Args:
        input_image_path (str): 歪んだ魚眼レンズ画像のパス
        output_image_path (str): 補正後の画像の保存パス
        K (numpy.ndarray): カメラ行列 (3x3)
        D (numpy.ndarray): 歪み係数 (4x1 または 8x1)
        DIM (tuple): 歪んだ画像の寸法 (幅, 高さ)
    """

    img = cv2.imread(input_image_path)
    if img is None:
        print(f"Error: Could not read image from {input_image_path}")
        return

    # オプション: より詳細なマッピングのための新しいカメラ行列の計算
    # balance=1.0 で画像の全体を表示、balance=0.0 で中心部分のみを表示
    new_K = cv2.fisheye.estimateNewCameraMatrixForUndistortRectify(K, D, DIM, np.eye(3), balance=0.0)
    map1, map2 = cv2.fisheye.initUndistortRectifyMap(K, D, np.eye(3), new_K, DIM, cv2.CV_16SC2)
    undistorted_img = cv2.remap(img, map1, map2, interpolation=cv2.INTER_LINEAR, borderMode=cv2.BORDER_CONSTANT)

    cv2.imwrite(output_image_path, undistorted_img)
    print(f"Undistorted image saved to {output_image_path}")

# キャリブレーションデータの読み込み
calibration_data = np.load("fisheye_calibration.npz")  # キャリブレーションデータを保存したファイル名
K = calibration_data["K"]
D = calibration_data["D"]
DIM = calibration_data["DIM"]

args = sys.argv
if len(args) != 2:
    print("Usage: python undistort_image.py <input_image_path>")
    sys.exit(1)

input_image_path = args[1]
output_image_path = input_image_path.replace(".png", "_undistorted.png")

undistort_fisheye_image(input_image_path, output_image_path, K, D, DIM)

# 補足: 画像の表示
# 補正結果をすぐに確認したい場合は、以下のコードを追加できます。
# undistorted_img = cv2.imread(output_image_path)
# cv2.imshow("Undistorted Image", undistorted_img)
# cv2.waitKey(0)
# cv2.destroyAllWindows()

以下のように補正されます。



GitHubで編集を提案

Discussion