OpenCVのcalibrateCamera関数を使ったカメラキャリブレーション

概要

この記事ではカメラの内部パラメータ(カメラの物理特性やレンズの光学特性)を画像から取得する方法について紹介します。カメラキャリブレーションは、画像から3D情報を取得するための重要なステップです。OpenCVのcalibrateCamera関数は、このプロセスを実行するための主要なツールとして利用されています。物体の3D座標とその2D画像上の対応点を使用して、カメラの内部パラメータ(焦点距離、光学中心など)と歪み係数を推定し、ロボットビジョン、AR/VR、ドローンのナビゲーション、3Dモデリングなど、3D空間の情報が必要なアプリケーションで広く使用されています。

キャリブレーション画像

出力結果

OpenCVのcalibrateCamera関数を使ったカメラキャリブレーション
カメラ行列 : [[2.22316748e+03 0.00000000e+00 9.53407289e+02] [0.00000000e+00 2.17954342e+03 5.27455394e+02] [0.00000000e+00 0.00000000e+00 1.00000000e+00]] 歪み : [[-6.93341740e-01 1.68611317e+00 -3.47860617e-03 2.49888722e-03 -3.58595488e+00]] 回転ベクトル : (array([[-0.05670753], [ 0.11145731], [-0.05411702]]), array([[ 0.0001874 ], [ 0.02157255], [-0.05575256]]), array([[-0.00751345], [-0.02596556], [-0.02433715]]), array([[-0.2511571 ], [-0.04236791], [-0.02396725]]), array([[ 0.13185458], [-0.0098366 ], [ 0.00658697]])) 平行移動ベクトル : (array([[-3.22927557], [-3.41872428], [30.86729749]]), array([[-4.84822483], [-4.91507281], [26.52208749]]), array([[-3.88182503], [-4.87841127], [27.45531436]]), array([[-3.94270144], [-2.71272356], [29.21697193]]), array([[-4.15810071], [-5.61976001], [31.35248365]]))

プログラム全体

#解説1 import cv2 import numpy as np import matplotlib.pyplot as plt #解説2 # 終了基準 criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001) # 物体点を準備する、例えば (0,0,0), (1,0,0), (2,0,0) ....,(9,12,0) objp = np.zeros((9*12,3), np.float32) objp[:,:2] = np.mgrid[0:12,0:9].T.reshape(-1,2) # 全ての画像から物体点と画像点を保存するための配列 objpoints = [] # 実世界空間での3D点 imgpoints = [] # 画像平面での2D点 #解説3 images = ['cal1.bmp', 'cal2.bmp', 'cal3.bmp', 'cal4.bmp', 'cal5.bmp'] for fname in images: img = cv2.imread(fname) if img is None: print(f'画像の読み込みに失敗しました {fname}') continue gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) #解説4 # チェスボードのコーナーを見つける ret, corners = cv2.findChessboardCorners(gray, (12,9), None) # 見つかった場合、物体点と画像点(これらを精緻化した後)を追加する if ret == True: objpoints.append(objp) corners2 = cv2.cornerSubPix(gray,corners, (11,11), (-1,-1), criteria) imgpoints.append(corners2) # コーナーを描画して表示する img = cv2.drawChessboardCorners(img, (12,9), corners2, ret) #解説5 plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)) plt.show() else: print(f'チェスボードのコーナーが見つかりませんでした {fname}') if len(objpoints) > 0 and len(imgpoints) > 0: ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None) print('カメラ行列 : \n') print(mtx) print('歪み : \n') print(dist) print('回転ベクトル : \n') print(rvecs) print('平行移動ベクトル : \n') print(tvecs) else: print('画像点が不足しているため、カメラのキャリブレーションに失敗しました')

解説

解説1:モジュールのインポート

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

この部分では、プログラムで使用するモジュールをインポートしています。

cv2 OpenCVライブラリ。画像処理やコンピュータビジョンのタスクを行うための関数が含まれています。
numpy 数値計算ライブラリ。配列や行列の操作を効率的に行うための関数が含まれています。
matplotlib.pyplot データの可視化ライブラリ。画像を表示するために使用します。

これらのモジュールは、プログラムの機能を実現するために必要な機能を提供します。例えば、画像を読み込んだり、画像に対して処理を行ったり、結果を表示したりするためにこれらのモジュールを使用します。

解説2: 初期設定

# 終了基準 criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001) # 物体点を準備する、例えば (0,0,0), (1,0,0), (2,0,0) ....,(9,12,0) objp = np.zeros((9*12,3), np.float32) objp[:,:2] = np.mgrid[0:12,0:9].T.reshape(-1,2) # 全ての画像から物体点と画像点を保存するための配列 objpoints = [] # 実世界空間での3D点 imgpoints = [] # 画像平面での2D点

この部分では、カメラキャリブレーションに必要な初期設定を行っています。

criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001) コーナー検出の精度をどれくらい正確に調整するかを決める基準です。ここでは、30回調整を試みたら終わり、または調整が十分小さくなったら(0.001以下になったら)終わり、というルールを設定しています。
objp = np.zeros((9*12,3), np.float32)
objp[:,:2] = np.mgrid[0:12,0:9].T.reshape(-1,2)
チェスボードの各コーナーの3D座標を表す配列を指定しています。撮影しているチェスボードは平らなので、高さ(Z座標)が0としています。ここでは、12x9の大きさのチェスボードを想定して、各コーナーの位置を定義しています。
objpointsimgpoints カメラが見たときのチェスボードのコーナーの位置を保存するための配列を定義しています。objpointsは実際のコーナーの位置(3D)を、imgpointsはカメラが見たときのコーナーの位置(2D)を保存します。これらの情報を使って、カメラの「目の見え方」を調整します。

これらの設定は、カメラキャリブレーションのために必要なパラメータを定義するために必要です。

解説3: 画像の読み込みと前処理

images = ['cal1.bmp', 'cal2.bmp', 'cal3.bmp', 'cal4.bmp', 'cal5.bmp'] for fname in images: img = cv2.imread(fname) if img is None: print(f'画像の読み込みに失敗しました {fname}') continue gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

この部分では、カメラキャリブレーションに使用する画像を読み込み、前処理を行っています。

images キャリブレーションに使用する画像ファイルのリストを指定しています。
cv2.imread for fname in imagesで、imagesの中にある画像の名前を一つずつ取り出して、その都度以下の処理を行っています。ここでfnameは取り出した画像の名前を一時的に保存しており次の行で使用しています。img = cv2.imread(fname)では、fnameで指定した名前の画像を読み込んで、imgに結果を返しています。if img is Noneで、もしimgが空(画像が読み込めなかった)なら、エラーメッセージを表示して、次の画像の読み込んでいます。
cv2.cvtColor 画像の色空間を変換する関数です。ここでは、画像をグレースケールに変換しています。モノクロに変換する理由は、色情報がなくても画像の形状を認識でき、計算が早くなるからです。

解説4: チェスボードのコーナーの検出と保存

# チェスボードのコーナーを見つける ret, corners = cv2.findChessboardCorners(gray, (12,9), None) # 見つかった場合、物体点と画像点(これらを精緻化した後)を追加する if ret == True: objpoints.append(objp) corners2 = cv2.cornerSubPix(gray,corners, (11,11), (-1,-1), criteria) imgpoints.append(corners2)

この部分では、チェスボードのコーナーを検出し、それらの座標を保存しています。

ret, corners = cv2.findChessboardCorners(gray, (12,9), None) 画像から12x9のマスのチェスボードのコーナーを検出する関数です。チェスボードのコーナーが見つかった場合、retはコーナーが見つかったかどうかを示すブール値(見つかったらTrue、見つからなかったらFalse)で、cornersはコーナーが見つかったコーナーの位置情報が入ります。
objpoints.append(objp) objpに入っている物体点をobjpointsというリストに追加しています。これは、後でカメラのキャリブレーションを行うために必要となります。
corners2 = cv2.cornerSubPix(gray,corners, (11,11), (-1,-1), criteria) gray画像のcornersの位置を11x11ピクセルの検索窓でサブピクセル精度に詳しく(精緻に)計算し、ゼロゾーン(計算から除外するエリア)は指定せず、指定した終了条件criteria(最大30回の反復計算を行い、または指定した精度(この場合は0.001))に達するまで計算を行っています。言い換えると、見つけたコーナーの位置をさらに詳しく計算し直しています。これにより、コーナーの位置をより正確に知ることができます。
imgpoints.append(corners2) 前の行で精緻に計算し直したコーナーの位置をimgpointsというリストに追加しています。これも、後でカメラのキャリブレーションを行うために必要な情報です。

これらの操作は、カメラキャリブレーションのために必要な画像点と物体点を取得するために必要です。

解説5: コーナーの描画とカメラキャリブレーション

# コーナーを描画して表示する img = cv2.draw ChessboardCorners(img, (12,9), corners2, ret) plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)) plt.show() else: print(f'チェスボードのコーナーが見つかりませんでした {fname}') if len(objpoints) > 0 and len(imgpoints) > 0: ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None) print('カメラ行列 : \n') print(mtx) print('歪み : \n') print(dist) print('回転ベクトル : \n') print(rvecs) print('平行移動ベクトル : \n') print(tvecs) else: print('画像点が不足しているため、カメラのキャリブレーションに失敗しました')

ここでは、チェスボードのコーナーを見つけて、それを画像に描画し、その後カメラのキャリブレーションを行っています。それぞれの部分について説明します。

cv2.drawChessboardCorners(img, (12,9), corners2, ret) この行は、見つけたチェスボードのコーナー(corners2)を画像(img)に描画しています。(12,9)はチェスボードのコーナーの数を表しています。
plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))plt.show() これらの行は、描画した画像を表示しています。cv2.cvtColor(img, cv2.COLOR_BGR2RGB)は、OpenCVがBGRカラーフォーマットを使用するのに対し、matplotlibがRGBフォーマットを使用するため、色の順序を変更しています。
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None) この行は、カメラのキャリブレーションを行っています。objpointsimgpointsは、それぞれ3Dの物体点と2Dの画像点を表しています。これらの点を使って、カメラの内部パラメータ(mtx:カメラ行列、dist:歪み係数)と外部パラメータ(rvecs:回転ベクトル、tvecs:平行移動ベクトル)を計算します。
print('カメラ行列 : \n')print(mtx)など これらの行は、計算したカメラのパラメータを表示しています。
else この行は、画像点が不足している場合(つまり、チェスボードのコーナーが十分に見つからなかった場合)にエラーメッセージを表示します。

これらの操作は、検出したコーナーを視覚的に確認し、カメラキャリブレーションを行うために必要です。カメラキャリブレーションにより、カメラの内部パラメータと歪み係数を求めることができます。これらのパラメータは、画像から3D情報を取得するために必要です。

補足:使用ハードウェアについて

この記事ではThe imaging Source社の下記のカメラを使用しています。

カメラ DFK33UX264
レンズ TBL8C 5MP
TheImagingSource社ではモノクロカメラを提供しています。モノクロカメラを使用すると、エッジが鮮明な画像を直接取得できます。これにより、モノクロへの変換処理が不要となり、画像処理がより効率的になります。その結果、カメラのキャリブレーションもスムーズに行うことが可能となります。