OpenCV - 영상의 기하학적 변환

5 분 소요

어파인 변환 (affine transformation)

영상의 기하학적 변환 (geometric transformation)은 영상을 구성하는 픽셀의 배치 구조를 변경하여 전체 영상의 모양을 바꾸는 작업이다. 영상의 밝기 및 명암비 조절, 필터링 등은 픽셀의 위치는 고정한 상태에서 픽셀 값을 변경했지만, 기하학적 변환은 픽셀 값은 그대로 유지하면서 위치를 변경한다.

그 중 어파인 변환은 영상의 평행 이동, 확대 및 축소, 회전 등의 조합으로 만들 수 있는 기하학적 변환을 통칭한다.

어파인 변환 행렬(affine transformation matrix)

영상에서 (x, y) 좌표의 픽셀을 결과 영상의 (x’, y’) 좌표로 변환하는 방법은 다음과 같다.

{ x’ = f₁(x, y), y’ = f₂(x, y) }

이 때 영상을 평행 이동시키거나, 회전 및 크기 변환을 할 수 있는 어파인 변환은 다음 수식으로 정의할 수 있다.

{ x’ = f₁(x, y) = ax + by + c , y’ = f₂(x, y) = dx + ey + f }

위 수식은 다시 행렬을 이용하여 다음과 같이 표현이 가능하다

[[x’], [y’]] = [[a, b], [d, e]] * [[x], [y]] + [[c], [f]]

그리고 이는 다시 아래와 같이 요약할 수 있다.

[[x’], [y’]] = [[a, b, c], [d, e, f]] * [[x], [y], [1]]

여기에서 여섯 개의 파라미터로 구성된 2x3 행렬 [[a, b, c], [d, e, f]]어파인 변환 행렬 이라고 부른다. 뒤에 나오는 변환들은 모두 어파인 변환 행렬이 무엇인지만 파악하면 실행이 가능해진다.

만약 어파인 변환 행렬을 모르는 상황에서는 입력 영상에서 세 점의 좌표와 변환을 통해 이 점들을 이동시킨 결과 영상의 세 점의 좌표를 통해 어파인 변환 행렬을 계산할 수 있다.

cv2.getAffineTransform(src, dst)

  • src: 입력 영상에서 세 점의 좌표
  • dst: 결과 영상에서 세 점의 좌표

그리고 어파인 변환 행렬을 가지고 있을 때 영상을 어파인 변환한 결과 영상을 생성하려면 warpAffine() 함수를 사용하면 된다.

cv2.warpAffine(src, M, dsize)

  • src: 변환할 영상
  • M: 어파인 변환 행렬, CV_32FC1 (32비트 실수) 또는 CV_64FC1(64비트 실수) 타입이어야 함
  • dsize: 결과 영상 크기 (가로, 세로)

두 함수를 같이 사용하여 다음과 같은 영상의 기하학적 변환이 가능하다.

import cv2
import numpy as np

img = cv2.imread('nano.jpg')
h, w, c = img.shape

pts1 = np.float32([[0,290],[360,0],[762,260]]) # 입력 영상의 세 점의 위치
pts2 = np.float32([[0,0],[762,0],[762,599]])  # 변환 후 결과 영상의 세 점이 될 위치

M = cv2.getAffineTransform(pts1, pts2)  # 어파인 변환 행렬 구함

dst = cv2.warpAffine(img, M, (w, h))

cv2.imshow('img', img)
cv2.imshow('dst', dst)
cv2.waitKey(0)
cv2.destroyAllWindows()

img

추가로, 어파인 변환 행렬을 사용해서 영상 전체를 어파인 변환하지 않고 일부 점들이 어느 위치로 이동하는지만 알고 싶다면 transform() 함수를 사용할 수 있다.

cv2.transform(src, dst, M)

  • src: 입력 영상의 점의 좌표
  • dst: 변환된 점의 좌표를 담을 결과 영상
  • M: 어파인 변환 행렬

이동변환 (translation transformation)

영상을 가로 또는 세로 방향으로 일정 크기만큼 이동시키는 연산. 시프트(shift) 연산이라고도 한다.

좌표를 x 방향으로 a만큼(x+a), y 방향으로 b만큼(y+b) 이동시키면 되며, 이를 어파인 변환 행렬로 표현하면 다음과 같고 이 M을 warpAffine() 함수에 사용하면 된다.

M = [[1, 0, a], [0, 1, b]]

img = cv2.imread('c.jpg')
h,w,c = img.shape

# 변환 행렬, X축으로 -100, Y축으로 -200 이동
M = np.float32([[1,0,-100],[0,1,-200]])

dst = cv2.warpAffine(img, M,(w, h))
cv2.imshow('Original', img)
cv2.imshow('Translation', dst)

cv2.waitKey(0)
cv2.destroyAllWindows()

img

전단변환 (shear transformation)

전단변환은 직사각형 형태의 영상을 한쪽 방향으로 밀어서 평행사변형 모양으로 변형되는 변환이며 층밀림 변환이라고도 한다.

y좌표가 증가함에 따라 영상을 가로 방향으로 조금씩 밀어서 만드는 전단변환 수식은 x’ = x + my, y’ = y 로, 어파인 변환 행렬은 M = [ [1, m, 0], [0, 1, 0] ] 이다.

x좌표가 증가함에 따라 영상을 세로 방향으로 조금씩 밀어서 만드는 전단변환 수식은 x’ = x, y’ = mx + y 로, 어파인 변환 행렬은 M = [ [1, 0, 0], [m, 1, 0] ] 이다.

img = cv2.imread('c.jpg')
h,w,c = img.shape

M = np.float32([[1,0.2,0],[0,1,0]])

dst = cv2.warpAffine(img, M,(w+100, h))
cv2.imshow('Original', img)
cv2.imshow('Translation', dst)

cv2.waitKey(0)
cv2.destroyAllWindows()

img

크기변환 (scale transformation)

크기변환은 영상의 전체적인 크기를 확대 또는 축소하는 변환이다. 가로로는 원래 너비 w에서 w’로 변환했을 때 변환 비율은 w’/w 로, 세로로도 마찬가지로 변환 비율은 h’/h 가 된다. 크기변환 결과를 계산하는 수식은 x’ = (w’/w)x, y’ = (h’/h)y 이며, 어파인 변환 행렬은 M = [ [w’/w, 0, 0], [0, h’/h, 0] ] 이 된다.

크기 변환의 경우 영상 처리에서 매우 빈번하게 사용되기 때문에 OpenCV 함수 resize() 가 제공된다.

cv2.resize(img, dsize, fx, fy, inerpolation)

  • img - 입력 영상

  • dsize - 결과 영상 크기, (가로, 세로)

  • fx - 가로 사이즈의 배수

  • fy - 세로 사이즈의 배수

  • interpolation - 보간법.

    결과 영상의 픽셀 값을 결정하기 위해 입력 영상에서 주변 픽셀 값을 이용하는 방식.

    • INTER_NEAREST: 최근방 이웃 보간법. 가장 빠르게 동작하지만 결과 영상 화질이 좋지 않다.
    • INTER_LINEAR: 양성형 보간법. 연산 속도가 빠르고 화질도 좋은 편이라서 널리 사용되며, resize() 함수의 기본값으로 지정되어 있다.
    • INTER_CUBIC: 3차 보간법
    • INTER_LANCZOS4: 란초스 보간법
      • INTER_LINEAR 보다 더 좋은 화질을 원할 경우 3차 보간법이나 란초스 보간법을 이용하는 것이 좋다. 하지만 그만큼 연산 속도가 느려진다.
    • INTER_AREA: 픽셀 영역 리샘플링. 영상 축소시 사용하면 무아레 현상이 적게 발생하고 화질면에서 유리하다.
img = cv2.imread('c.jpg')
h, w, c = img.shape

#cv2.INTER_AREA: 축소에 적합한 보간법
shrink = cv2.resize(img, None, fx=0.5, fy=0.5, interpolation=cv2.INTER_AREA)

#cv2.INTER_CUBIC: 3차 보간법. 고화질
zoom1 = cv2.resize(img, (w*2, h*2), interpolation=cv2.INTER_CUBIC)

# 배수 Size지정
zoom2 = cv2.resize(img, None, fx=2, fy=2, interpolation=cv2.INTER_CUBIC)

cv2.imshow('Origianl', img)
cv2.imshow('Shrink', shrink)
cv2.imshow('Zoom1', zoom1)
cv2.imshow('Zoom2', zoom2)

cv2.waitKey(0)
cv2.destroyAllWindows()

회전변환 (rotation transform)

특정 좌표를 기준으로 영상을 원하는 각도만큼 회전하는 변환. 회원 변환에 의해 입력 영상의 점 (x, y)가 이동하는 점의 좌표 (x’, y’)는 삼각함수를 이용하여 구할 수 있다.

x’ = cosθ·x + sinθ·y y’ = -sinθ·x + cosθ·y

다만 영상 회전의 사용 또한 빈번하기 때문에 회전변환 어판인 변환 행렬은 OpenCV 함수 getRotationMatrix2D()를 사용해서 구할 수 있다.

cv2.getRotationMatrix2D(center, angle, scale)

  • center - 이미지의 중심 좌표
  • angle - 회전 각도
  • scale - 이미지 변환 크기
img = cv2.imread('c.jpg')
h, w, c = img.shape

# 이미지의 중심점을 기준으로 45도 회전 하면서 0.5배 Scale
M= cv2.getRotationMatrix2D((w/2, h/2), 45, 0.5)

dst = cv2.warpAffine(img, M, (w, h))

cv2.imshow('Original', img)
cv2.imshow('Rotation', dst)

cv2.waitKey(0)
cv2.destroyAllWindows()

img

대칭변환 (symmetric transformation)

영상의 좌우 또는 상하를 뒤집는 형태의 변환. 대칭변환은 입력 영상과 같은 크기에 입력 영상의 픽셀과 결과 영상의 픽셀이 일대일로 대응되는 결과 영상을 생성하기 때문에 보간법이 필요하지 않다.

좌우 대칭 수식은 x’ = w-1-x, y’ = y 이고 어파인 변환 행렬은 M = [ [-1, 0, w-1], [0, 1, 0] ]

상하 대칭 수식은 x’ = x, y’ = h-1-y, 어파인 변환 행렬은 M = [ [1, 0, 0], [0, -1, h-1] ]

하지만 대신 flip() 함수를 사용 할 수 있다.

cv2.flip(img, mode)

  • mode -1은 좌우반전, 0은 상하반전
img = cv2.imread('c.jpg')

img1 = cv2.flip(img, 1)
img2 = cv2.flip(img, 0)

cv2.imshow('img', img)
cv2.imshow('img1', img1)
cv2.imshow('img2', img2)

cv2.waitKey(0)
cv2.destroyAllWindows()

img

투시변환 (perspective transformation)

어파인 변환과 유사하지만 자유도가 더 높은 변환 방법으로, 꼭지점 3개를 기준으로 하며 직선의 평행 관계가 유지되는 어파인 변환과 달리 꼭지점 4개를 기준으로 하며 결과 영상의 형태가 임의의 사각형으로 나타난다.

어파인 변환과 마찬가지로 투시 변환 행렬, cv2.getPerspectiveTransform()을 찾아 투시 변환 함수 cv2.warpPerspective 에 사용하는 식으로 활용이 가능하다.

import cv2
import numpy as np
from matplotlib import pyplot as plt

img = cv2.imread('perspective.jpg')
# [x,y] 좌표점을 4x2의 행렬로 작성
# 좌표점은 좌상->좌하->우상->우하
pts1 = np.float32([[200,600],[100, 800],[560, 600],[650,800]])

# 좌표의 이동점
pts2 = np.float32([[10,10],[10,1000],[1000,10],[1000,1000]])

# pts1의 좌표에 표시. perspective 변환 후 이동 점 확인.
cv2.circle(img, (200,600), 20, (255,0,0),-1)
cv2.circle(img, (100, 800), 20, (0,255,0),-1)
cv2.circle(img, (560, 600), 20, (0,0,255),-1)
cv2.circle(img, (650,800), 20, (0,0,0),-1)

M = cv2.getPerspectiveTransform(pts1, pts2)

dst = cv2.warpPerspective(img, M, (1000, 1000))

plt.subplot(121),plt.imshow(img),plt.title('image')
plt.subplot(122),plt.imshow(dst),plt.title('Perspective')
plt.show()

img

댓글남기기