Python OpenCV 広角レンズ画像の歪み補正 カメラキャリブレーション

スポンサーリンク
Python/DeepLearning

みなさん、こんにちは!

みやしんです。

なんと、月間10,000PV & Twitterフォロワー数1000人を突破しました!!

多くの方が記事を参考にしてくださり、本当にありがとうございます!

これからも良い記事をお届けできるよう頑張りますので応援よろしくお願いします!^^

みやしん
みやしん

良かったらTwitterもフォローしてにゃ!!!

↓Twitterへのリンク

さてさて、今回はOpenCVを使って、画像の歪み補正をしてみましたのでご紹介します!

OpenCVはサンプルデータが落ちていて、とっても簡単にできちゃいます。

では、早速やっていきましょう!


スポンサーリンク

この記事で出来ること

OpenCVを使った画像の歪補正方法の導入部分をご紹介します。

例えば、広角レンズで撮影した画像や動画はそのままだと歪んで見えています。こんな感じです。

左側が歪んだままの写真、右側が歪みを補正した後の写真です。

元画像では街灯や建物が傾いて映っていましたが、右側の写真(歪補正後)では真っすぐに直っているのが分かりますね!

Pythonなどで画像処理を行う場合には、事前に画像の歪み除去が必要になる場合も多いですので、自分で出来るようになると便利かもしれませんね!

サンプルデータのダウンロード_Github Learn OpenCV

OpenCV の活用方法を紹介している有益なGithub のサイトがあります。Github Learn OpenCVです。

sub directory ごとにそれぞれ1テーマのスクリプトやサンプルデータが入っています。

Github Learn OpenCV

GitHub - spmallick/learnopencv: Learn OpenCV : C++ and Python Examples
Learn OpenCV : C++ and Python Examples. Contribute to spmallick/learnopencv development by creating an account on GitHub...

↓こんな感じにGithubに繋がりますので、赤枠の部分からダウンロードしてください。

こんな感じでダウンロードできますので解凍してください。

解凍すると大量のフォルダーが入っていますが、「CameraCalibration」のフォルダーを使います。

フォルダー内を見てみますと、.pyファイルが2つ入っています。

今回は「cameraCalibrationWithUndistortion.py」を使って行きます。

また、更にimagesのフォルダを開けると、今回使うサンプルデータが入っています。

チェスボード柄の画像がたくさん入っていますが、これでカメラキャリブレーションを行います。

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

広角カメラ等で写真を撮ってみると画像が歪みます。主な歪としては放射状歪みと接線歪みがあります。放射状歪は画像の中心から離れるほど歪は大きくなります。例えば以下の画像を見てください。チェスボードのエッジに沿って赤線を引いていますが、少しだけチェスボードが湾曲しているのが分かると思います。

<補足>

この放射上歪みは以下の式で補正します。

また、もう一つの接線歪は、レンズと画像平面が完璧に平行になっていないことが原因で生じます。そのため、ある領域が期待しているより近くにあるように見えてしまいます。接線歪みは下記の式で補正します。

まとめると、以下の式となり5つのレンズ歪みパラメータを推定する必要があります。

また、カメラの内部パラメータや外部パラメータの情報も必要になります。

内部パラメータ:焦点距離(fx, fy)、光学中心(Cx, Cy)

内部パラメータはカメラ行列とも呼ばれており、カメラ固有の値になります。

内部パラメータは 3 × 3 の行列として以下のように表されます。

外部パラメータは、世界座標をカメラ座標に変換する行列です。つまり、カメラから見てどの位置にあるか?といったカメラ基準の座標に変換するためのパラメータです。

これらのパラメータを推定するためにチェスボードのような、歪がなかったらどのように見えるか分かっているパターンを何枚か撮影します。チェスボードでは四角形ナー検出などで検出し、本来の位置からのズレ量などを用いて数学的な処理を行い歪みパラメータを推定します。これがカメラキャリブレーションの概要です。少なくとも10枚程度の画像が必要になります。

OpenCVをインストール

OpenCVをインストールしていない人は下記のコマンドでインストールしましょう。

pipを使うの方

> pip install opencv-python

Anacondaを使う方

> conda install -c conda-forge opencv

とりあえず、直ぐにやってみたい方

フォルダ内の「cameraCalibrationWithUndistortion.py」を実行しちゃってください。

> python cameraCalibrationWithUndistortion.py

Pythonスクリプト

スクリプトはこんな感じです。

import cv2
import numpy as np
import os
import glob

# Defining the dimensions of checkerboard
CHECKERBOARD = (6,9)
# cv2.TERM_CRITERIA_EPS:指定された精度(epsilon)に到達したら繰り返し計算を終了する
# cv2.TERM_CRITERIA_MAX_ITER:指定された繰り返し回数(max_iter)に到達したら繰り返し計算を終了する
# cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER : 上記のどちらかの条件が満たされた時に繰り返し計算を終了する
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)

# Creating vector to store vectors of 3D points for each checkerboard image
objpoints = []
# Creating vector to store vectors of 2D points for each checkerboard image
imgpoints = [] 


# Defining the world coordinates for 3D points
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)
prev_img_shape = None

# Extracting path of individual image stored in a given directory
images = glob.glob('./images/*.jpg')
for fname in images:
    img = cv2.imread(fname)
    gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
    # Find the chess board corners
    # If desired number of corners are found in the image then ret = true
    ret, corners = cv2.findChessboardCorners(gray, CHECKERBOARD, cv2.CALIB_CB_ADAPTIVE_THRESH+
    	cv2.CALIB_CB_FAST_CHECK+cv2.CALIB_CB_NORMALIZE_IMAGE)
    
    """
    If desired number of corner are detected,
    we refine the pixel coordinates and display 
    them on the images of checker board
    """
    if ret == True:
        objpoints.append(objp)
        # refining pixel coordinates for given 2d points.
        corners2 = cv2.cornerSubPix(gray,corners,(11,11),(-1,-1),criteria)
        
        imgpoints.append(corners2)

        # Draw and display the corners
        img = cv2.drawChessboardCorners(img, CHECKERBOARD, corners2,ret)
    
    cv2.imshow('img',img)
    cv2.waitKey(0)

cv2.destroyAllWindows()

h,w = img.shape[:2]

"""
Performing camera calibration by 
passing the value of known 3D points (objpoints)
and corresponding pixel coordinates of the 
detected corners (imgpoints)
"""
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1],None,None)

print("Camera matrix : \n")
print(mtx)
print("dist : \n")
print(dist)
print("rvecs : \n")
print(rvecs)
print("tvecs : \n")
print(tvecs)

# Using the derived camera parameters to undistort the image

img = cv2.imread(images[0])
# Refining the camera matrix using parameters obtained by calibration
# ROI:Region Of Interest(対象領域)
newcameramtx, roi = cv2.getOptimalNewCameraMatrix(mtx, dist, (w,h), 1, (w,h))

# Method 1 to undistort the image
dst = cv2.undistort(img, mtx, dist, None, newcameramtx)

# undistort関数と同じ結果が返されるので、今回はコメントアウト(initUndistortRectifyMap()関数)
# Method 2 to undistort the image
# mapx,mapy=cv2.initUndistortRectifyMap(mtx,dist,None,newcameramtx,(w,h),5)
# dst = cv2.remap(img,mapx,mapy,cv2.INTER_LINEAR)

# Displaying the undistorted image
cv2.imshow("undistorted image",dst)
cv2.waitKey(0)

imagesフォルダー内に入っているチェスボード画像を使ってカメラキャリブレーションをしていきます。この画像は固定してあるカメラの前でチェスボードの位置や傾きを様々に変えて撮影をしたものです。

それでは、スクリプト内の解説を進めていきます。

cv2.findChessboardCorners()関数

画像中からチェスボード上の講師点を検出する関数です。8×8など、格子のパターンを指定する必要があります。今回のチェスボードの格子数は6×9のチェスボードを使っていますね。

関数からは2つの出力が返されます。

1つ目は、出力のフラグです。指定したパターンが画像中から検出されたときTrueが返されます。スクリプト上ではretに格納しています。全ての画像でパターンが検出される訳ではありません。

2つ目は、検出された制御点の座標が出力されます。スクリプト上ではcornersに格納しています。

チェスボードではなく、格子状に並んだ円のパターンを使うこともでき、その場合はcv2.findCirclesGrid()関数を使ってパターンを検出します。こちらの方がより少ない画像枚数でも精度が得られるそうです。

cv2.cornerSubPix()関数

コーナーを検出した後に、この関数を使うと検出精度を向上することができます。

cv2.drawChessboardCorners()関数

この関数を使えば、検出した制御点の位置を画像上に表示することができます。

こんな感じです。

cv2.calibrateCamera()関数

カメラキャリブレーションを行う関数です。

返り値は、カメラ行列(内部パラメータ)、レンズ歪みパラメータ、回転・並進ベクトル等になります。

上記のスクリプトでは下記のように返り値を受け取ります。

  1. ret : 処理が上手くできた時にtrueを格納
  2. mtx : カメラ行列(内部パラメータ)
  3. rvecs : 回転ベクトル
  4. tvecs : 並進ベクトル
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1],None,None)

cv2.getOptimalNewCameraMatrix()関数

カメラ行列(内部パラメータ)を改善する関数です。

newcameramtx, roi = cv2.getOptimalNewCameraMatrix(mtx, dist, (w,h), 1, (w,h))

第4引数に「1」を入れていますが、この場合はすべての元画像ピクセルを保持します。補正後の画像のエッジ部分を黒色で埋めます。「0」にすると目的に合うピクセルだけを取り出します。

また、roiは Region Of Interestの略で対象領域を返します。

cv2.undistort()関数

歪み補正を行う関数です。下記ではdstに格納していますのでcv2.imshow()で表示できます。

# undistort
dst = cv2.undistort(img, mtx, dist, None, newcameramtx)

# Displaying the undistorted image
cv2.imshow("undistorted image",dst)
cv2.waitKey(0)

歪み補正後の画像

こんな感じです!

結構面白いですね^^!

追伸>そもそも歪んだ画像が取れるような広角レンズなんて持ってないよ! という方におススメのスマホに付けられる便利ツールがありますので気になる方は試してみてくださいね!

続いては、自前のスマホカメラの広角レンズでカメラキャリブレーションを実施してみました!

下記の記事にまとめていますので、こちらもご参考にどうぞ!

Python/DeepLearning
スポンサーリンク
記事が良かったらSNSでシェアを宜しくお願いします!
みやしんをフォローすると役立つ情報がいっぱいにゃ!
スポンサーリンク

コメント

  1. rain より:

    大変参加になる記事ありがとうございます!一点質問させてください。
    今カメラキャリブについて色々調べているのですが、この手のプログラムは大抵チェスボードのマス目のサイズや画像サイズを入力する必要があると思うのですが、このプログラムはどうなっているのでしょうか?

    自分で撮影した1920×1080の画像を使うととんでもない失敗の仕方をします。どうぞよろしくお願いします。

    • rain より:

      すいません、追加させてください。
      プログラム内で画像サイズ(1920×1080)を読み込んでいるのはわかったのですが、失敗する理由がますます分からないかなりました。。
      ちなみに、使用しているチェスボードのマス目は20mmです。

      • みやしん より:

        返信が遅くなりごめんなさい。ご連絡に気づくのが遅れてしまいました。ご連絡くださりありがとうございます。失敗とはどのような感じでしょうか?画面がぐにゃっとすごく曲がっている感じでしょうか?

タイトルとURLをコピーしました