どうもこんにちは!みやしんです。
今回は自分のスマホカメラに搭載されている広角レンズで撮影した写真の歪み補正を行ってみました!
PythonのOpenCVを使って補正しますが、導入部分を別の記事で詳しくご紹介していますので、気になる方はそちらもご確認頂けたらと思います。

早速やってみるにゃ!!
では、早速やっていきましょう!
管理人のみやしんが本を出版しました🤗✨Pythonをやってみたいけれど、作りたいものや目標はとくにない、そんな方でも挫折しないで楽しく安心して学習を進められる、とっても素敵な本です🤗


スマホカメラの紹介
身近なもので試してみたかったので、自分のスマホカメラの広角レンズをチョイスしてみました!
スマホはOPPO Reno5 Aを使っています。これです↓

カメラはレンズが縦に3つ並んでいます。
一番上(メインカメラ)が通常撮影で使用するレンズで6400万画素です。

え?6400万画素っ!?スマホにしてはすごいんじゃにゃいか?はじめて気づいたにゃ・・・笑
今回は、真ん中にある「超広角レンズ(800万画素)」を使って撮影し、歪み補正をしたいと思います。

ちなみに、
メインカメラと広角カメラで、同じ位置から撮影して写真を比較してみると、

広角カメラの方が大分広く映っています。
なるほど×2、では、次へ進んでいきます。
チェスボードでカメラキャリブレーション
チェスボードを撮影し、OpenCVを使ってカメラキャリブレーションを行います。
初めての方は、こちらの記事をご確認ください^^
チェスボードのダウンロード
探したらチェスボードがネット上にありましたので、これを使いました!
http://opencv.jp/sample/pics/chesspattern_7x10.pdf
7×10のチェスボードです。
印刷して部屋の壁に貼り付けて、色々な角度から合計40枚撮影しました。
撮影の時に手の影が入ってしまいましたが、そのまま続行しました。

画像サイズ調整
写真が800万画素でちょっと容量大きいかな?実践向きじゃないな~ と思いましたのでサイズを調整しました。
フォルダー構成

スクリプトはこちらになります。
Resize_img.py
# ライブラリをインポート
import cv2
import glob
# フォルダ内の画像を取得
images = glob.glob('*.jpg')
for filename in images:
# 指定したパスの画像を読込む
img = cv2.imread(filename)
# 高さと幅を定義する
height = img.shape[0]
width = img.shape[1]
# 画像をリサイズする。今回は各長さを6分の1にする。800万画素➔22万画素に縮小
img_resize = cv2.resize(img, (int(width/6), int(height/6)))
# リサイズ後の画像を保存する
cv2.imwrite('./img_resized/' + str(filename), img_resize)
800万画素→大体20万画素くらいに落としました。
解像度を落としても十分きれいです。

カメラキャリブレーション
撮影した40枚のチェスボード写真を使って、カメラキャリブレーションをしてみました。
ちょっと実験です。カメラキャリブレーションに使うチェスボード写真を1枚、5枚、10枚・・・・40枚まで枚数を変えて、各パラメータがどうなるか確認してみました。大体何枚くらい、チェスボード写真が必要になるのか現物確認してみます。
確認するパラメータは、
- 焦点距離 fx, fy
- 光学中心 Cx, Cy
- 歪係数 k1, k2, p1, p2, k3
です。結果はこちらです。


これを見ますと、10枚以下は値がかなりバラついていてダメそうですね。最低10枚以上必要と言われていますが確かにそうかもしれません。できれば20枚くらいあると安心かな~と思います。
ちなみに参考で800万画素の写真でもパラメータを出してみましたところ、20万画素とは全然違う値になりました。
上記で使用したスクリプトはこちらです。
フォルダー構成

cameraCalibrationWithUndistortion.py
import cv2
import numpy as np
import os
import glob
# Defining the dimensions of checkerboard
CHECKERBOARD = (7,10)
# 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('./img_resized/*.jpg')
for filepath in images:
img = cv2.imread(filepath)
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 the corners
img = cv2.drawChessboardCorners(img, CHECKERBOARD, corners2,ret)
# img_drawChessboardCornersフォルダにチェスボードのコーナー検出画像を保存
cv2.imwrite('./img_drawChessboardCorners/' + str(os.path.basename(filepath)), 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
for filepath in images:
img = cv2.imread(filepath)
# 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)
# 歪み補正した画像をimg_undistortフォルダへ保存
cv2.imwrite('./img_undistort/undistort_' + str(os.path.basename(filepath)), dst)
cv2.waitKey(0)
出力結果はこんな感じになります。(1枚抜粋)

画像の端を見ると歪み補正が掛かっていることが分かりますね!
風景写真を歪み補正してみた!
求まったカメラパラメータを使って、記事の最初に載せました建物が映っている写真を歪み補正してみました。40枚のチェスボードから算出したパラメータを使います。
| fx | 253.5233687 |
| fy | 255.634826 |
| Cx | 272.69949049 |
| Cy | 206.13581154 |
| k1 | 0.0264629 |
| k2 | -0.01938724 |
| p1 | 0.00176052 |
| p2 | 0.00183265 |
| k3 | 0.01337071 |
フォルダー構成

imageUndistortion.py
import cv2
import numpy as np
import os
import glob
# チェスボード画像から算出したカメラパラメータを設定
fx = 253.5233687
fy = 255.634826
Cx = 272.69949049
Cy = 206.13581154
mtx = np.array([[fx, 0, Cx],[0, fy, Cy],[0, 0, 1]])
# チェスボード画像から算出した歪係数を設定
k1 = 0.0264629
k2 = -0.01938724
p1 = 0.00176052
p2 = 0.00183265
k3 = 0.01337071
dist = np.array([[k1, k2, p1, p2, k3]])
# img_resizedフォルダー内のjpg画像を読み込む
images = glob.glob('*.jpg')
# Using the derived camera parameters to undistort the image
for filepath in images:
img = cv2.imread(filepath)
h,w = img.shape[:2]
# 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)
# 歪み補正した画像をimg_undistortフォルダへ保存
cv2.imwrite('./img_undistort/undistort_' + str(filepath), dst)
cv2.waitKey(0)
実行結果です。パっと見は分かりにくいですが、補正後の写真の角を見ると補正されていることが分かりますね!

歪みの大きい写真を使うともっと面白くなりそうですね!





コメント