カメラキャリブレーションのABC: 知っておきたい基本
目次
対象読者
- 画像処理をこれから学ぶ学生の方
- カメラキャリブレーションについて理解が曖昧な人
- 概要を知っている必要がある人(例: 営業の方)
この記事の範囲
具体的な数学には触れません。カメラキャリブレーションの基本的な概念を理解することを目的としています。またカメラキャリブレーションをすることにより、何が得られるかにも触れます。
カメラキャリブレーション (camera calibration) とは
私達は3次元に住んでいますが、カメラから得られる画像は2次元です。
画像処理の目的の1つとして、カメラから得られる画像を3次元の情報に変換することがあります。
(ロボティクスだったり、姿勢推定だったり、ARだったり)
変換を行うためには、カメラの特性を正確に知る必要があります。このようなカメラの特性を正確に知るための手続きを「カメラキャリブレーション」といいます。
カメラキャリブレーションとは、カメラで撮影した画像を「正確に」解釈するための手続き
と考えてください。
カメラキャリブレーションに必要なもの
- OpenCVプログラム:キャリブレーションを行うためのプログラム。OpenCVにはキャリブレーションに関する便利な関数が多数用意されています。
- キャリブレーションパターンの紙:チェスボードのような特定のパターンが印刷された紙。このパターンをカメラで撮影し、その画像を解析することでキャリブレーションを行います。画像は最低10枚必要です。
OpenCVとは
OpenCVとは、画像処理や機械学習などの機能を提供する高度なライブラリで、非常に広く使われています。
カメラキャリブレーション用パターンのあれこれ
印刷するパターンには、色々なパターンがあります。
- チェスボード
- エグザンプルとしてもっとも一般的です。
- 回転不変であるためには、行数が偶数で列数が奇数であるか、その逆である必要があります。
- サークルグリッド
- より厳密にキャリブレーションを行うためには、サークルグリッドを使用することが推奨されます。
- ArUcoやChArUco
- 厳密にカメラキャリブレーションを行えます。
キャリブレーションを理解するための専門用語
キャリブレーションを理解するためには、以下の専門用語を知る必要があります。
- レンズの歪みとその補正:
- カメラのレンズは、中心部と端部で光の屈折率が異なるため、画像が歪むことがあります。この歪みを正すことを「歪みの補正」といいます。
- ノートPCのカメラや市販のWEBカメラは、だいたい広角気味に歪んでいます。
- ワールド座標系とカメラ座標系:
- ワールド座標系は、実際の物体の位置や姿勢を示す座標系です。一方、カメラ座標系は、カメラの位置や向きを基準とした座標系です。
- 内部パラメーター (Intrinsic Parameters):
- カメラの特性を示すパラメーター。焦点距離やセンサーの中心位置などがこれに該当します。
- 外部パラメーター (Extrinsic Parameters):
- カメラの位置や向きを示すパラメーター。ワールド座標系とカメラ座標系の関係を示すために使用されます。
歪みの補正について詳しく
カメラのレンズは球状であり、この形状によって画像の端部が歪むことがあります。(もともとその様に作られていたり、値段の関係でそうなることもある。)
レンズの歪曲収差と対応方法(5)から引用
OpenCVでは、これらの歪みを補正するためcv2.undistort()
関数を使用することで、歪みの補正を行うことができます。
ワールド座標系とカメラ座標系について詳しく
参考: Learn Camera Calibration in Python with OpenCV: Complete Step-by-Step Guide with Python Script
座標とは
物の位置を数値で表すための「基準」。地図なら、経度と緯度を指定すると、特定の地点が表現できますね。3次元空間なら、[x, y, z]のように表現できます。
ワールド座標系とカメラ座標系
カメラで物を撮影すると、3Dの実世界の情報が2Dの画像平面に投影されて画像が形成されます。
この2Dの画像から、物の「実際の位置や姿勢」を知りたいとき、どうすればいいでしょうか?
カメラ(レンズやセンサー位置)の特性(歪みなど)も分からない、対象物がカメラから何mm離れているかも分からない。この状態だと、お手上げです。
そこで、あらかじめ大きさや並び方が分かっている物体を撮影し、その画像からゴニョゴニョと計算をすることで、カメラの特性やカメラの位置や向きや物体との距離を求めます。
カメラの特性が「カメラ座標系」、物体の位置や向きが「ワールド座標系」です。
でも今知りたいのは「内部パラメーター」と「外部パラメーター」です。
これらのパラメーターがわかれば、カメラの特性やカメラの位置や向きや物体との距離がわかるからです。
この「ゴニョゴニョした計算」で、内部パラメーターと外部パラメーターを求めます。
「あらかじめ大きさや並び方が分かっている物体」にはいくつかあって、一番ポピュラーな例として「チェスボード」が用いられます。
ゴニョゴニョと計算する時に、チェスボードの「内角の数」をコード中に指定します。「内角の数」とは、実際にチェスボードのマス目の交差点(コーナー)の数のことで、7×10のマス目のチェスボードがある場合、内角(コーナー)の数は6×9になります。
マス目の大きさは関係ありません。すべて同じ大きさであれば問題ありません。
stackoverflowから引用
復習しましょう。「ゴニョゴニョ」の部分です。すこし専門的になるので、わからなくても大丈夫です。
(結局、欲しいのは外部・内部パラメーターなので。)
外部パラメーターと内部パラメーターは、既知の3Dワールド座標と2D画像座標の対応関係をもとに計算されます。具体的には以下の手順で行われます。
- 既知の3Dワールド座標の設定:
キャリブレーションパターン(例:チェスボード)の各マスの交点をもとに、各コーナーの3Dワールド座標を定義します。 - 2D画像座標の取得:
カメラでキャリブレーションパターンを撮影し、画像上での各コーナーの2D座標を検出します。 - 対応関係の利用:
既知の3Dワールド座標と検出された2D画像座標の対応関係を利用して、外部パラメーターと内部パラメーターを推定します。
この推定は、実際の2D画像座標と、外部・内部パラメーターを使用して計算される予測2D画像座標との差(誤差)を最小化することで求められます。
したがって、既知の情報(3Dワールド座標と2D画像座標の対応関係)と数学的な最適化手法を組み合わせることで、外部パラメーターと内部パラメーターを同時に推定できる、ということです。
内部パラメーターと外部パラメーターについて詳しく
求めたいものです。
内部パラメーターは、カメラ自体の特性を示すパラメーターです。これには、焦点距離やセンサーの中心位置などが含まれます。これらのパラメーターは、カメラのモデルやレンズの種類によって異なります。
(センサーが中心位置からずれてることはある。レンズの質もしかり。)
外部パラメーターは、カメラの位置や向きを示すパラメーターです。これには、カメラの位置や姿勢、ワールド座標系との関係などが含まれます。
OpenCVを使用したキャリブレーションの手順
- キャリブレーションパターンの撮影:
- キャリブレーションパターン(例:チェスボード)を印刷し、それをさまざまな角度や位置からカメラで撮影します。この際、パターンが画像全体にきれいに映るように注意します。
- コーナーの検出:
- 撮影した画像から、キャリブレーションパターンのコーナーの位置を検出します。OpenCVの
cv2.findChessboardCorners()
関数を使用することで、この検出を自動的に行うことができます。
- キャリブレーションの実行:
cv2.calibrateCamera()
関数を使用して、キャリブレーションを行います。この関数には、検出したコーナーの位置やワールド座標系の点を入力として与えることで、内部パラメーターや外部パラメーターを求めることができます。
- 歪みの補正:
- 得られたキャリブレーションパラメーターを使用して、撮影した画像の歪みを補正します。
cv2.undistort()
関数を使用することで、この補正を行うことができます。
- パラメーターの保存:
- キャリブレーションで得られたパラメーターを保存します。これにより、あとで同じカメラを使用する際に、再度キャリブレーションを行う必要がなくなります。
実装
各パラメーターを算出して保存する、かんたんな例
import sys
sys.path.append('/usr/lib/python3/dist-packages')
import glob
import cv2
import numpy as np
# チェスボードの内角の数
CHECKERBOARD = (6,9)
# 3Dのワールド座標系の点
objpoints = []
for i in range(CHECKERBOARD[1]):
for j in range(CHECKERBOARD[0]):
objpoints.append([j, i, 0])
objpoints = np.array(objpoints, dtype=np.float32)
# 2Dと3Dの点を保存するリスト
objpoints_list = [] # 3Dのワールド座標系の点
imgpoints_list = [] # 2Dの画像上の点
# 画像の読み込み
images = glob.glob('path/to/your/images/*.jpg') # 画像のパスを指定
for fname in images:
img = cv2.imread(fname)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, corners = cv2.findChessboardCorners(gray, CHECKERBOARD, None)
if ret:
objpoints_list.append(objpoints)
imgpoints_list.append(corners)
# キャリブレーション
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints_list, imgpoints_list, gray.shape[::-1], None, None)
# 結果の確認
img = cv2.imread(images[0])
h, w = img.shape[:2]
newcameramtx, roi = cv2.getOptimalNewCameraMatrix(mtx, dist, (w,h), 1, (w,h))
dst = cv2.undistort(img, mtx, dist, None, newcameramtx)
# パラメータの保存
np.savez('calib.npz', mtx=mtx, dist=dist, rvecs=rvecs, tvecs=tvecs)
print("キャリブレーションが完了しました。")
保存されたパラメーターから、歪みの補正を行うかんたんな例
import sys
sys.path.append('/usr/lib/python3/dist-packages')
import cv2
import numpy as np
# 保存したパラメーターの読み込み
with np.load('calib.npz') as data:
mtx = data['mtx']
dist = data['dist']
# 画像の読み込み
img = cv2.imread('path/to/your/image.jpg')
# 歪みの補正
dst = cv2.undistort(img, mtx, dist, None, mtx)
# 補正後の画像の表示
cv2.imshow('Undistorted Image', dst)
cv2.waitKey(0)
cv2.destroyAllWindows()
まとめ
もし使用するカメラのキャリブレーションをしない場合、そのカメラに映った物体の位置や姿勢を正確に知ることはできません。また、歪みの補正も行うことができません。
画像が歪んでしまっていては、不良品の検出や、ロボットの姿勢推定などができません。カメラキャリブレーションは、画像処理を行う上で、とても重要な手順です。
ただし、すべてのカメラに対してキャリブレーションを行う必要はありません。
前回の記事「頭部姿勢推定を簡易実装する」では、「キャリブレーションを行わずに、頭部姿勢推定をする」ことがテーマでした。現場では、キャリブレーションできないこともままあります。
どうしてもキャリブレーションが必要なシーンで、なぜ、どうしてこの作業が必要なのか、説明できるようになるといいですね。
以上です。ありがとうございました。