OpenCV を使用して画像の歪みを解消する
このチュートリアルでは、OpenCV の undistort()
関数を使用して画像の歪みを元に戻す方法について説明します。
OpenCV を使用して画像の歪みを解消する
カメラで画像をキャプチャしているときに、一部の画像が歪んでしまいます。 画像に追加された歪みは、主に画像のキャプチャに使用されたカメラによるものです。
画像に存在する歪みは、放射状または接線方向の可能性があります。 放射状の歪みでは、画像内の直線が湾曲し、画像の角にある場合はさらに湾曲します。
線が画像の中央にある場合、その歪みは小さく、目立ちません。 たとえば、下の歪んだ画像を参照してください。
上の画像では、チェス盤の端が赤い直線で結ばれているため、画像に存在する歪みを簡単に確認できます。 上の画像のエッジラインが曲がっていて、画像の中心から離れるほどカーブが大きくなっていることがわかります。
タンジェンシャル ディストーションでは、イメージ プレーンとイメージを取得するために使用されるレンズが互いに平行ではありません。そのため、イメージに存在する一部の領域が実際のイメージよりも遠くに見えたり、一部の領域が近くに見えたりします。 . たとえば、接線方向の歪みを示す下の画像を参照してください。
上のイメージでは、赤いグリッドは出力イメージ プレーンを示し、青いグリッドは入力イメージ プレーンを示します。 接線方向の歪みは、写真の撮影に使用されるレンズの角度によって異なります。
レンズの角度が変われば歪みも変化し、レンズの角度が大きくなると歪みが大きくなります。 画像の歪みを元に戻すには、歪み係数と呼ばれる 5つのパラメーターを見つける必要があります。
また、レンズの焦点距離や光学中心など、画像のキャプチャに使用されたカメラに関する情報を見つける必要もあります。 焦点距離と光学中心の値を使用して、画像の歪みを解消するために使用されるカメラ マトリックスを作成できます。
同じカメラが撮影した場合、他の画像でカメラ行列を再利用できますが、カメラが変更された場合、カメラによって焦点距離と光学中心の値が変わる可能性があるため、行列を再度見つける必要があります。 カメラ行列は、以下に示す行列のような 3 行 3 列の行列になります。
上記のカメラ マトリックスでは、変数 fx
と fy
には焦点距離の値が含まれ、変数 cx
と cy
には画像のキャプチャに使用されるレンズの光学中心の値が含まれます。 焦点距離と光心値は、カメラ固有のパラメーターです。
また、3D 座標系でポイントを変換するために使用される回転ベクトルと変換ベクトルを示す、カメラの外部パラメーターを見つける必要があります。 歪みパラメータを見つけるには、歪んだ画像のサンプルを使用する必要があり、これらの画像に存在する特定の点とその位置を使用する必要があります。
正確なカメラ マトリックスが計算されるように、すべてのサンプル画像は同じカメラで取得する必要があり、そのカメラの歪んだ画像はカメラ マトリックスを使用して歪みを補正する必要があります。 たとえば、10 枚のチェス盤の画像を取得して、3D の実世界のポイントと、3D ポイントの位置を含むそれらの 2D 座標を抽出してみましょう。
これらのポイントは、2つの黒い正方形が互いに接触しているチェス盤から取得されます。 これらの点を使用して、サンプル画像のキャプチャに使用するカメラと、調整する画像を調整します。
3D 現実世界のポイントには、x
、y
、および z
の 3つの値が含まれます。 手順を簡単にするために、カメラから固定距離でサンプル画像を取得し、3D 実数ポイントの値 z
を削除します。
(0, 0)
、(1, 0)
などの 3D 現実世界のポイントで、画像に存在するすべてのポイントの位置を渡します。 これらの点は、チェス盤の画像内の四角の位置を表します。
チェスの画像の正方形のサイズがわかっている場合は、すべてのポイントを追加するのではなく、いくつかのポイントをスキップできます。 たとえば、正方形が 30 mm の場合、(0, 0)
、(30, 0)
などの点を追加できます。
チェス盤の四角の角を見つけなければならないことを知っておいてください。 cv2.findChessboardCorners()
を使用して、正方形の角を見つけることができます。
cv2.findChessboardCorners()
関数内で探しているパターンを 8 行 8 列のグリッドのように渡す必要があります。 通常、チェス盤には 8 x 8 の正方形がありますが、この例では 7 x 6 のグリッドを使用します。
この関数は、左から右、上から下の順に配置されたコーナー ポイントを返します。 この関数は、探しているパターンが得られた場合に true
になるブール値も返します。
使用したいサンプル画像には必要なパターンが含まれていない可能性があるため、必要なパターンを含む画像を使用してコーナー ポイントを見つける必要があります。
cv2.findChessboardCorners()
のブール出力を使用して、パターンが取得されたかどうかを確認できます。パターンが取得された場合はコーナー ポイントを保存し、見つからない場合はコーナーを保存しません。 ポイントして次の画像に移動します。
cv2.findChessboardCorners()
関数の詳細については、この リンク を確認してください。 コーナー ポイントの精度を上げるには、cv2.cornerSubPix()
関数を使用できます。
cv2.cornerSubPix()
関数の詳細については、この リンク を確認してください。 cv2.drawChessboardCorners()
関数を使用して、チェス盤の画像にコーナー ポイントを描画することもできます。
cv2.drawChessboardCorners()
関数の詳細については、この リンク を確認してください。 たとえば、チェス盤の 10 枚のサンプル画像を読み取り、3D 現実世界と 2D 画像の点を見つけて、画像上に描画してみましょう。
以下のコードを参照してください。
import numpy as np
import cv2
term_criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
obj_points = np.zeros((6 * 7, 3), np.float32)
obj_points[:, :2] = np.mgrid[0:7, 0:6].T.reshape(-1, 2)
real_points = []
img_points = []
chess_images = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
for name in chess_images:
chess_img = cv2.imread(str(name) + ".png")
chess_gray = cv2.cvtColor(chess_img, cv2.COLOR_BGR2GRAY)
ret, corners = cv2.findChessboardCorners(chess_gray, (7, 6), None)
if ret == True:
real_points.append(obj_points)
corners2 = cv2.cornerSubPix(
chess_gray, corners, (11, 11), (-1, -1), term_criteria
)
img_points.append(corners)
cv2.drawChessboardCorners(chess_img, (7, 6), corners2, ret)
cv2.imshow("img", chess_img)
cv2.waitKey(0)
cv2.destroyAllWindows()
出力:
上記のコードでは、numpy
ライブラリの zeros()
関数を使用してゼロの行列を作成し、mgrid()
関数を使用して 3D 実数として使用される行列に値を入れました。 ・ワールドポイント。
real_points
および img_points
変数は、すべてのサンプル画像の 3D 現実世界の点と 2D 画像の点を格納します。
for
ループを使用して、各画像を個別に処理しました。 imread()
関数を使用して各画像を読み取り、cv2.cvtColor()
関数を使用して各画像をグレースケールに変換します。
if
ステートメントを使用して、パターンが取得されたかどうかを確認し、取得された場合は、ポイントを real_points
および img_points
変数に格納します。
cv2.cornerSubPix()
関数を使用してコーナー ポイントの精度を高め、上で定義した終了基準をグレースケール イメージとそのコーナーと共に関数内に渡し、コーナー ポイントの精度を高めました。 append()
関数を使用して、コーナー ポイントを 2D イメージ ポイントに追加しました。
3D 現実世界のポイントと 2D 画像ポイントを使用して、cv2. calibrationCamera()
関数を使用して 5つの歪みパラメーターを見つけることができます。 cv2. calibrationCamera()
関数の詳細については、この リンク を確認してください。
cv2.getOptimalNewCameraMatrix()
関数を使用して、5つの歪みパラメーターと入力画像サイズから 2つのパラメーターを使用してカメラ マトリックスを見つけることができます。
cv2.getOptimalNewCameraMatrix()
関数は、画像のトリミングに使用できる関心領域も返します。 これで、歪みパラメーターとカメラ マトリックスを使用して、OpenCV の cv2.undistort()
関数を使用して画像の歪みを元に戻すことができます。
cv2.getOptimalNewCameraMatrix()
関数の詳細については、この リンク を確認してください。 たとえば、10 個の歪んだサンプル画像から画像を読み取り、cv2.undistort()
関数を使用して歪みを戻してみましょう。
以下のコードを参照してください。
import numpy as np
import cv2
term_criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
obj_points = np.zeros((6 * 7, 3), np.float32)
obj_points[:, :2] = np.mgrid[0:7, 0:6].T.reshape(-1, 2)
real_points = []
img_points = []
chess_images = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
for name in chess_images:
chess_img = cv2.imread(str(name) + ".png")
chess_gray = cv2.cvtColor(chess_img, cv2.COLOR_BGR2GRAY)
ret, corners = cv2.findChessboardCorners(chess_gray, (7, 6), None)
if ret == True:
real_points.append(obj_points)
corners2 = cv2.cornerSubPix(
chess_gray, corners, (11, 11), (-1, -1), term_criteria
)
img_points.append(corners)
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(
real_points, img_points, chess_gray.shape[::-1], None, None
)
img = cv2.imread("5.png")
h, w = img.shape[:2]
newcameramtx, roi = cv2.getOptimalNewCameraMatrix(mtx, dist, (w, h), 1, (w, h))
dst = cv2.undistort(img, mtx, dist, None, newcameramtx)
x, y, w, h = roi
dst = dst[y : y + h, x : x + w]
cv2.imshow("Undistorted Image", dst)
cv2.imshow("distorted image", img)
cv2.waitKey(0)
cv2.destroyAllWindows()
出力:
上記の出力では、左の画像が歪んだ画像で、右の画像が歪みのない画像です。 上記のコードでは、cv2.imshow()
関数を使用して画像を表示し、cv2.imwrite()
関数を使用して歪みのない画像を保存しました。
cv2.undistort()
関数の詳細については、この リンク を確認してください。 上記の例では、正方形のグリッドを使用してパターンを見つけましたが、グリッドが円形の場合は、cv2.findCirclesGrid()
関数を使用してパターンを見つけることができます。
cv2.findCirclesGrid()
関数の詳細については、この リンク を確認してください。