OpenCV를 사용하여 이미지 왜곡 제거

Ammar Ali 2023년6월21일
OpenCV를 사용하여 이미지 왜곡 제거

이 튜토리얼에서는 OpenCV의 undistort() 기능을 사용하여 이미지를 왜곡 해제하는 방법에 대해 설명합니다.

OpenCV를 사용하여 이미지 왜곡 제거

카메라로 이미지를 캡처하는 동안 일부 이미지가 왜곡됩니다. 이미지에 추가된 왜곡은 대부분 이미지를 캡처하는 데 사용된 카메라 때문입니다.

이미지에 존재하는 왜곡은 방사형 또는 접선형일 수 있습니다. 방사형 왜곡에서는 이미지의 직선이 구부러지고 이미지의 모서리에 있는 경우 선이 더 구부러집니다.

선이 이미지 중앙에 있으면 왜곡이 작아 눈에 띄지 않습니다. 예를 들어 아래 왜곡된 이미지를 참조하십시오.

체스 판

위의 이미지에서 체스 판의 가장자리는 빨간색 직선으로 연결되어 이미지에 왜곡이 있음을 쉽게 확인할 수 있습니다. 위의 이미지에서 가장자리 선이 구부러져 있고 이미지 중앙에서 멀어질수록 더 구부러진 것을 볼 수 있습니다.

접선 왜곡에서는 이미지를 촬영하는 데 사용되는 이미지 평면과 렌즈가 서로 평행하지 않기 때문에 이미지에 존재하는 일부 영역은 더 멀리 보이고 일부 영역은 실제 이미지에서 보는 것보다 더 가깝게 보입니다. . 예를 들어 접선 왜곡을 보여주는 아래 이미지를 참조하십시오.

접선 왜곡

위 이미지에서 빨간색 그리드는 출력 이미지 평면을 나타내고 파란색 그리드는 입력 이미지 평면을 나타냅니다. 접선 왜곡은 사진을 찍는 데 사용되는 렌즈의 각도에 따라 다릅니다.

렌즈 각도가 변경되면 왜곡이 변경되고 렌즈 각도가 증가하면 왜곡이 증가합니다. 이미지를 왜곡하지 않으려면 왜곡 계수로 알려진 5개의 매개변수를 찾아야 합니다.

또한 렌즈의 초점 거리 및 광학 중심과 같이 이미지를 캡처하는 데 사용되는 카메라에 대한 정보를 찾아야 합니다. 초점 거리와 광학 중심 값을 사용하여 이미지 왜곡을 제거하는 데 사용할 카메라 매트릭스를 만들 수 있습니다.

같은 카메라로 촬영하면 다른 영상에 카메라 매트릭스를 재사용할 수 있지만, 카메라가 바뀌면 카메라에 따라 초점 거리와 광학 중심 값이 달라질 수 있기 때문에 매트릭스를 다시 찾아야 합니다. 카메라 매트릭스는 아래 표시된 매트릭스와 같은 3x3 매트릭스가 됩니다.

카메라 매트릭스

위의 카메라 매트릭스에서 변수 fxfy는 초점 거리 값을 포함하고 변수 cxcy는 이미지를 캡처하는 데 사용되는 렌즈의 광학 중심 값을 포함합니다. 초점 거리와 광학 중심 값은 카메라의 고유 매개변수입니다.

또한 3D 좌표계에서 점을 변환하는 데 사용되는 회전 및 변환 벡터를 보여주는 카메라의 외부 매개변수를 찾아야 합니다. 왜곡 매개변수를 찾으려면 일부 샘플 왜곡 이미지를 사용해야 하며 이러한 이미지에 있는 특정 지점과 해당 위치를 사용해야 합니다.

모든 샘플 이미지는 정확한 카메라 매트릭스를 계산하기 위해 동일한 카메라로 촬영해야 하며, 해당 카메라의 왜곡된 이미지는 카메라 매트릭스를 사용하여 왜곡되지 않아야 합니다. 예를 들어, 10개의 체스 판 이미지를 가지고 3D 실세계 포인트와 3D 포인트의 위치를 포함하는 2D 좌표를 추출해 봅시다.

이 포인트는 두 개의 검은색 사각형이 서로 닿는 체스판에서 가져옵니다. 이 포인트를 사용하여 샘플 이미지를 캡처하는 데 사용되는 카메라와 보정하려는 이미지를 보정합니다.

3D 실제 포인트에는 세 가지 값 x, yz가 포함됩니다. 절차를 단순화하기 위해 카메라에서 고정된 거리에서 샘플 이미지를 가져와 3D 실제 단어 포인트의 z 값을 제거합니다.

이제 (0, 0), (1, 0) 등과 같은 3D 실세계 포인트에서 이미지에 있는 모든 포인트의 위치를 전달합니다. 이 점은 체스판 이미지에서 사각형의 위치를 나타냅니다.

체스 이미지에서 사각형의 크기를 알고 있으면 모든 포인트를 추가하는 대신 일부 포인트를 건너뛸 수 있습니다. 예를 들어 정사각형이 30밀리미터인 경우 (0, 0), (30, 0) 등과 같은 점을 추가할 수 있습니다.

우리는 체스판에서 사각형의 모서리를 찾아야 합니다. cv2.findChessboardCorners()를 사용하여 사각형의 모서리를 찾을 수 있습니다.

찾고자 하는 패턴을 cv2.findChessboardCorners() 함수 내부에 8x8 그리드처럼 전달해야 합니다. 체스판은 일반적으로 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() 함수를 사용하여 0의 행렬을 만든 다음 mgrid() 함수를 사용하여 3D 실수로 사용할 행렬에 값을 입력했습니다. -세계 포인트.

real_pointsimg_points 변수는 모든 샘플 이미지의 3D 실제 포인트와 2D 이미지 포인트를 저장합니다.

각 이미지를 개별적으로 처리하기 위해 for 루프를 사용했습니다. imread() 기능을 사용하여 각 이미지를 읽은 다음 cv2.cvtColor() 기능을 사용하여 각 이미지를 회색조로 변환합니다.

우리는 if문을 사용하여 패턴을 얻었는지 여부를 확인하고 패턴을 얻었으면 real_pointsimg_points 변수에 포인트를 저장합니다.

cv2.cornerSubPix() 함수를 사용하여 코너 포인트의 정확도를 높이고 위에서 정의한 종료 기준을 그레이스케일 이미지 및 코너와 함께 함수 내부에 전달하여 코너 포인트의 정확도를 높였습니다. append() 함수를 사용하여 2D 이미지 포인트에 꼭지점을 추가했습니다.

cv2.calibrateCamera() 함수를 사용하여 3D 실세계 포인트와 2D 이미지 포인트를 사용하여 5개의 왜곡 매개변수를 찾을 수 있습니다. cv2.calibrateCamera() 기능에 대한 자세한 내용은 이 링크를 확인하십시오.

cv2.getOptimalNewCameraMatrix() 함수를 사용하여 카메라 매트릭스를 찾기 위해 5개의 왜곡 매개변수와 입력 이미지 크기의 두 매개변수를 사용할 수 있습니다.

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() 기능에 대한 자세한 내용은 이 링크를 확인하십시오.

작가: Ammar Ali
Ammar Ali avatar Ammar Ali avatar

Hello! I am Ammar Ali, a programmer here to learn from experience, people, and docs, and create interesting and useful programming content. I mostly create content about Python, Matlab, and Microcontrollers like Arduino and PIC.

LinkedIn Facebook