ソースに絡まるエスカルゴ

貧弱プログラマの外部記憶装置です。

【python/OpenCV】カメラの歪み補正を行う方法

 カメラのレンズの種類によっては画像自体に歪みが発生する場合があります。(広角レンズなど)

 そのカメラの歪みをpythonOpenCVを使って補正する方法を説明していきます。

 基本的には参考資料にあるページ様のソースコードそのままなので、詳しい内容はそちらを参照してください。

 では始めます。


1:チェスボードの準備
 カメラの歪み補正にはチェスボードを用います。ネットで画像検索すれば色々見つかりますが、自分が適当に作成したものがあるので面倒な方は以下のpdfファイルをダウンロードして印刷して使ってください。

 また、あとで使用するのでマス目の1辺の長さも定規で計測しておくとよいです。


2:補正したいカメラを使ってチェスボードを撮影する
 チェスボードの準備ができたら、そのチェスボードを机なりに置いた状態で補正したいカメラを使って様々角度からチェスボードを撮影します。

 撮影する時は以前記事で紹介した画像キャプチャ用のソースコードを使うと楽かと思います。

 以下が実際に撮影した写真の例です。このように様々な角度からチェスボードを撮影します。
f:id:rikoubou:20190326133857p:plain
f:id:rikoubou:20190326140115p:plain
f:id:rikoubou:20190326135818p:plain

 歪みを補正する精度を高めるためにはチェスボードが写っている画像が20枚ほど必要になるので、頑張って撮影してください。


3:補正用内部パラメータの計算
 チェスボードの画像を20枚ほど撮影できたら、それらの画像を使って補正用のパラメータを計算していきます。
 ソースコードの中身はほとんど以下の参考ページ様からのコピペです。

・calcCamera.py

#
# カメラの歪みを戻すための値を計算する
#
import numpy as np
import cv2
import glob
from time import sleep
from datetime import datetime

TMP_FOLDER_PATH = "./tmp/"
MTX_PATH = TMP_FOLDER_PATH + "mtx.csv"
DIST_PATH = TMP_FOLDER_PATH + "dist.csv"

# メイン関数
def main():
    calcCamera() # カメラの歪みを計算

# カメラの歪みを計算する関数
def calcCamera():
    square_size = 20.0      # 正方形のサイズ(mm)
    pattern_size = (9, 6)  # 模様のサイズ
    pattern_points = np.zeros( (np.prod(pattern_size), 3), np.float32 ) #チェスボード(X,Y,Z)座標の指定 (Z=0)
    pattern_points[:,:2] = np.indices(pattern_size).T.reshape(-1, 2)
    pattern_points *= square_size
    obj_points = []
    img_points = []
 
    for fn in glob.glob("./img/*"):
        # 画像の取得
        im = cv2.imread(fn,0)
        print ("loading..." + fn)
        # チェスボードのコーナーを検出
        found, corner = cv2.findChessboardCorners(im, pattern_size)
        # コーナーがあれば
        if found:
            term = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_COUNT, 30, 0.1)
            corners2 = cv2.cornerSubPix(im, corner, (5,5), (-1,-1), term)
            # マークをつけて画像保存
            img = cv2.drawChessboardCorners(im, pattern_size, corners2, found)
            saveImgByTime(TMP_FOLDER_PATH, img)
            sleep(1)
        # コーナーがない場合のエラー処理
        if not found:
            print ('chessboard not found')
            continue
        img_points.append(corner.reshape(-1, 2))   #appendメソッド:リストの最後に因数のオブジェクトを追加
        obj_points.append(pattern_points)
 
    # 内部パラメータを計算
    rms, K, d, r, t = cv2.calibrateCamera(obj_points, img_points, (im.shape[1],im.shape[0]), None, None)

    # 計算結果を表示
    print ("RMS = ", rms)
    print ("K = \n", K)
    print ("d = ", d.ravel())

    # ファイル保存
    saveCalibrationFile(K, d, MTX_PATH, DIST_PATH)

# キャリブレーションCSVファイルを上書き保存する関数
def saveCalibrationFile(mtx, dist, mtx_path, dist_path):
    np.savetxt(mtx_path, mtx, delimiter =',',fmt="%0.14f")   #カメラ行列の保存
    np.savetxt(dist_path, dist, delimiter =',',fmt="%0.14f") #歪み係数の保存

# 画像を時刻で保存する関数
def saveImgByTime(dirPath, img):
    # 時刻を取得
    date = datetime.now().strftime("%Y%m%d_%H%M%S")
    path = dirPath + date + ".png"
    cv2.imwrite(path, img) # ファイル保存
    print("saved: ", path)

if __name__ == '__main__':
    main()

 calcCamera関数の「square_size = 20.0 # 正方形のサイズ(mm)」のところは1で使用したチェスボードのマス1辺の長さの値に書き換えてください。

 プログラムを少し説明すると、チェスボードの角を検出し、その角からカメラの内部パラメータ(レンズの歪みなど)を計算しています。

 使い方としては上記のプログラムを「calcCamera.py」として保存し、同じ階層に「img」フォルダと「tmp」フォルダを作成します。そしてimgフォルダ内に2で撮影した画像を全て入れ、calcCamera.pyを実行します。
 「tmp」フォルダに角を検出した結果の各白黒画像とカメラの内部パラメータを保存した「mtx.csv」と「dist.csv」が保存されます。


4:内部パラメータを使って画像を補正
 内部パラメータを保存した「mtx.csv」と「dist.csv」を使って画像を補正していきます。

・calibrate.py

#
# カメラの歪みを補正する
#
import numpy as np
import cv2
import glob
from time import sleep
from datetime import datetime

TMP_FOLDER_PATH = "./tmp/"
MTX_PATH = TMP_FOLDER_PATH + "mtx.csv"
DIST_PATH = TMP_FOLDER_PATH + "dist.csv"
SAVE_FOLDER_PATH = "./result/"

# メイン関数
def main():
    calibrateImage() # 画像の歪みを補正

# カメラの歪みをCSVファイルを元に補正する関数
def calibrateImage():
    mtx, dist = loadCalibrationFile(MTX_PATH, DIST_PATH)

    for fn in glob.glob("./img/*"):
        img = cv2.imread(fn)
        resultImg = cv2.undistort(img, mtx, dist, None) # 内部パラメータを元に画像補正
        saveImgByTime(SAVE_FOLDER_PATH, resultImg)
        sleep(1)

# キャリブレーションCSVファイルを読み込む関数
def loadCalibrationFile(mtx_path, dist_path):
    try:
        mtx = np.loadtxt(mtx_path, delimiter=',')
        dist = np.loadtxt(dist_path, delimiter=',')
    except Exception as e:
        raise e
    return mtx, dist

# 画像を時刻で保存する関数
def saveImgByTime(dirPath, img):
    # 時刻を取得
    date = datetime.now().strftime("%Y%m%d_%H%M%S")
    path = dirPath + date + ".png"
    cv2.imwrite(path, img) # ファイル保存
    print("saved: ", path)

if __name__ == '__main__':
    main()

 上記をコピペして「calibrate.py」という名前でcalcCamera.pyと同じ階層に保存します。また同じ階層に「result」フォルダも作成します。
 その状態で実行すると、tmpフォルダの中にあるmtx.csvとdist.csvを読み込み、その値を使ってimgフォルダの中にある画像を補正したものがresultフォルダの中に保存されます。

 ちなみに自分がやった感じだと以下のようになりました。

  • 元画像

f:id:rikoubou:20190326142524p:plain

  • 補正後

f:id:rikoubou:20190326142549p:plain


 以上がpythonOpenCVを使ってカメラの歪み補正を行う方法です。

 あくまでOpenCVがカメラの歪みを推定しているだけなので完璧な補正というわけにはいきませんが、ぱっと見で違和感ない程度には補正されているのがわかるかと思います。それと補正時に元画像の一部がカットされてしまうので注意が必要です。

 画像解析などをする際には歪みがあると何かと面倒だったりする場合もあるので、この方法を使うと何かといいかもしれません。


 またコピペしたりフォルダを作成したりするのが面倒な人もいるかと思うので、3と4のソースコードを1つにまとめた「calibrateCamera.py」やその他諸々をまとめたzipファイルを以下に公開しておきます。必要な方はご自由にどうぞ。


・参考資料