😏기하학적 변환

OpenCV 기하학적 변환

이동, 확대/축소, 회전부터 어핀 변환과 원근 변환까지 체계적으로 학습할 수 있습니다.

기본 기하학적 변환

기본 기하학적 변환에는 이미지 이동(Translation), 확대/축소(Scaling), 회전(Rotation)이 있습니다. 이러한 변환들은 모두 변환 행렬을 사용하여 구현됩니다.

이미지 이동 (Translation)

이미지를 특정 방향으로 이동시키는 변환입니다. 기본 수학적 원리는 다음과 같습니다:

x_new = x_old + dx
y_new = y_old + dy

이를 2 x 3 변환 행렬로 표현하면:

[1  0  dx]
[0  1  dy]
cv2.warpAffine() 함수

OpenCV에서는 cv2.warpAffine() 함수를 사용하여 변환 행렬을 적용합니다.

dst = cv2.warpAffine(src, matrix, dsize, dst, flags, borderMode, borderValue)

주요 매개변수:

  • src: 원본 이미지 (numpy 배열)

  • matrix: 2 x 3 변환행렬 (dtype=float32)

  • dsize: 결과 이미지의 크기 (width, height)

  • flags: 보간법 알고리즘 플래그

  • borderMode: 외곽 영역 보정 플래그

  • borderValue: 외곽 영역 색상 값

보간법 옵션 (flags):

  • cv2.INTER_LINEAR: 기본값, 인접한 4개 픽셀 값에 거리 가중치 사용

  • cv2.INTER_NEAREST: 가장 가까운 픽셀 값 사용

  • cv2.INTER_AREA: 픽셀 영역 관계를 이용한 재샘플링

  • cv2.INTER_CUBIC: 인접한 16개 픽셀 값에 거리 가중치 사용

외곽 영역 처리 (borderMode):

  • cv2.BORDER_CONSTANT: 고정 색상 값

  • cv2.BORDER_REPLICATE: 가장자리 복제

  • cv2.BORDER_WRAP: 반복

  • cv2.BORDER_REFLECT: 반사

이미지 이동 예제
# 평행 이동 예제
import cv2
import numpy as np

img = cv2.imread('image.jpg')
rows, cols = img.shape[0:2]
dx, dy = 100, 50  # 이동할 픽셀 거리

# 변환 행렬 생성
mtrx = np.float32([[1, 0, dx],
                   [0, 1, dy]])

# 단순 이동
dst = cv2.warpAffine(img, mtrx, (cols+dx, rows+dy))

# 외곽 픽셀을 파란색으로 보정
dst2 = cv2.warpAffine(img, mtrx, (cols+dx, rows+dy), None,
                      cv2.INTER_LINEAR, cv2.BORDER_CONSTANT, (255,0,0))

# 외곽 픽셀을 반사로 보정
dst3 = cv2.warpAffine(img, mtrx, (cols+dx, rows+dy), None,
                      cv2.INTER_LINEAR, cv2.BORDER_REFLECT)

cv2.imshow('original', img)
cv2.imshow('translated', dst)
cv2.imshow('border_constant', dst2)
cv2.imshow('border_reflect', dst3)
cv2.waitKey(0)
cv2.destroyAllWindows()

이미지 확대 및 축소 (Scaling)

이미지 크기를 조정하는 변환입니다. 기본 수학적 원리:

x_new = scale_x * x_old
y_new = scale_y * y_old

변환 행렬:

[scale_x    0      0]
[   0    scale_y  0]
변환 행렬을 이용한 스케일링
# 변환 행렬을 이용한 이미지 확대 및 축소
import cv2
import numpy as np

img = cv2.imread('image.jpg')
height, width = img.shape[:2]

# 0.5배 축소 변환 행렬
m_small = np.float32([[0.5, 0, 0],
                      [0, 0.5, 0]])

# 2배 확대 변환 행렬
m_big = np.float32([[2, 0, 0],
                    [0, 2, 0]])

# 보간법 적용 없이 확대 축소
dst1 = cv2.warpAffine(img, m_small, (int(height*0.5), int(width*0.5)))
dst2 = cv2.warpAffine(img, m_big, (int(height*2), int(width*2)))

# 보간법 적용한 확대 축소
dst3 = cv2.warpAffine(img, m_small, (int(height*0.5), int(width*0.5)),
                      None, cv2.INTER_AREA)
dst4 = cv2.warpAffine(img, m_big, (int(height*2), int(width*2)),
                      None, cv2.INTER_CUBIC)

cv2.imshow("original", img)
cv2.imshow("small", dst1)
cv2.imshow("big", dst2)
cv2.imshow("small_inter_area", dst3)
cv2.imshow("big_inter_cubic", dst4)
cv2.waitKey(0)
cv2.destroyAllWindows()
cv2.resize() 함수 사용

더 간편한 방법으로 cv2.resize() 함수를 사용할 수 있습니다:

cv2.resize(src, dsize, dst, fx, fy, interpolation)

매개변수:

  • src: 입력 원본 이미지

  • dsize: 출력 영상 크기 (width, height), 생략하면 fx, fy 배율 적용

  • fx, fy: 크기 배율

  • interpolation: 보간법 알고리즘 선택 플래그

cv2.resize() 예제:

# cv2.resize()로 이미지 확대 및 축소
import cv2

img = cv2.imread('image.jpg')
height, width = img.shape[:2]

# 크기 지정으로 축소
dst1 = cv2.resize(img, (int(width*0.5), int(height*0.5)),
                  interpolation=cv2.INTER_AREA)

# 배율 지정으로 확대
dst2 = cv2.resize(img, None, None, 2, 2, cv2.INTER_CUBIC)

cv2.imshow("original", img)
cv2.imshow("small", dst1)  
cv2.imshow("big", dst2)
cv2.waitKey(0)
cv2.destroyAllWindows()

보간법 선택 가이드:

  • 축소할 때: cv2.INTER_AREA 권장

  • 확대할 때: cv2.INTER_CUBIC 또는 cv2.INTER_LINEAR 권장

이미지 회전 (Rotation)

이미지를 특정 각도로 회전시키는 변환입니다. 회전 변환 행렬:

[cos(θ)  -sin(θ)  tx]
[sin(θ)   cos(θ)  ty]

여기서 θ는 회전 각도, tx와 ty는 회전 후 이동 거리입니다.

변환 행렬을 직접 생성
# 변환행렬을 이용한 이미지 회전
import cv2
import numpy as np

img = cv2.imread('image.jpg')
rows, cols = img.shape[0:2]

# 라디안 각도 계산 (60진법을 호도법으로 변경)
d45 = 45.0 * np.pi / 180  # 45도
d90 = 90.0 * np.pi / 180  # 90도

# 회전을 위한 변환 행렬 생성
m45 = np.float32([[np.cos(d45), -1*np.sin(d45), rows//2],
                  [np.sin(d45), np.cos(d45), -1*cols//4]])
m90 = np.float32([[np.cos(d90), -1*np.sin(d90), rows],
                  [np.sin(d90), np.cos(d90), 0]])

# 회전 변환 행렬 적용
r45 = cv2.warpAffine(img, m45, (cols, rows))
r90 = cv2.warpAffine(img, m90, (rows, cols))

cv2.imshow("origin", img)
cv2.imshow("45_degree", r45)
cv2.imshow("90_degree", r90)
cv2.waitKey(0)
cv2.destroyAllWindows()
cv2.getRotationMatrix2D() 함수 사용

OpenCV는 회전 변환 행렬을 쉽게 생성할 수 있는 함수를 제공합니다:

mtrx = cv2.getRotationMatrix2D(center, angle, scale)

매개변수:

  • center: 회전축 중심 좌표 (x, y)

  • angle: 회전할 각도 (60진법)

  • scale: 확대 및 축소 비율

cv2.getRotationMatrix2D() 예제:

# OpenCV로 회전 변환행렬 구하기
import cv2

img = cv2.imread('image.jpg')
rows, cols = img.shape[0:2]

# 회전을 위한 변환 행렬 구하기
# 회전축: 중앙, 각도: 45도, 배율: 0.5
m45 = cv2.getRotationMatrix2D((cols/2, rows/2), 45, 0.5)
# 회전축: 중앙, 각도: 90도, 배율: 1.5
m90 = cv2.getRotationMatrix2D((cols/2, rows/2), 90, 1.5)

# 변환 행렬 적용
img45 = cv2.warpAffine(img, m45, (cols, rows))
img90 = cv2.warpAffine(img, m90, (cols, rows))

cv2.imshow('origin', img)
cv2.imshow("45_degree", img45)
cv2.imshow("90_degree", img90)
cv2.waitKey(0)
cv2.destroyAllWindows()

고급 기하학적 변환

고급 기하학적 변환에는 어핀 변환(Affine Transform)과 원근 변환(Perspective Transform)이 있습니다. 이러한 변환들은 이미지의 형태를 더 복잡하게 변형할 수 있습니다.

어핀 변환 (Affine Transform)

어핀 변환은 이미지를 2차원적으로 뒤트는 변환입니다. 평행선은 변환 후에도 평행선을 유지하는 특징이 있습니다.

cv2.getAffineTransform() 함수

3개의 점 쌍을 이용하여 어핀 변환 행렬을 구합니다:

matrix = cv2.getAffineTransform(pts1, pts2)

매개변수:

  • pts1: 변환 전 영상의 좌표 3개 (3 x 2 배열)

  • pts2: 변환 후 영상의 좌표 3개 (3 x 2 배열)

  • matrix: 반환되는 변환 행렬 (2 x 3 행렬)

어핀 변환 예제:

# 어핀 변환
import cv2
import numpy as np

img = cv2.imread('image.jpg')
rows, cols = img.shape[:2]

# 변환 전, 후 각 3개의 좌표 생성
pts1 = np.float32([[100, 50], [200, 50], [100, 200]])
pts2 = np.float32([[80, 70], [210, 60], [250, 120]])

# 변환 전 좌표를 이미지에 표시
cv2.circle(img, (100, 50), 5, (255, 0, 0), -1)
cv2.circle(img, (200, 50), 5, (0, 255, 0), -1)
cv2.circle(img, (100, 200), 5, (0, 0, 255), -1)

# 짝지은 3개의 좌표로 변환 행렬 계산
mtrx = cv2.getAffineTransform(pts1, pts2)

# 어핀 변환 적용
dst = cv2.warpAffine(img, mtrx, (int(cols*1.5), rows))

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

원근 변환 (Perspective Transform)

원근 변환은 이미지를 3차원적으로 변환합니다. 멀리 있는 것은 작게, 가까이 있는 것은 크게 보이는 원근법의 원리를 적용합니다.

cv2.getPerspectiveTransform() 함수

4개의 점 쌍을 이용하여 원근 변환 행렬을 구합니다:

mtrx = cv2.getPerspectiveTransform(pts1, pts2)

매개변수:

  • pts1: 변환 이전 영상의 좌표 4개 (4 x 2 배열)

  • pts2: 변환 이후 영상의 좌표 4개 (4 x 2 배열)

  • mtrx: 반환되는 변환행렬 (3 x 3 행렬)

cv2.warpPerspective() 함수

원근 변환은 전용 함수를 사용합니다:

dst = cv2.warpPerspective(img, mtrx, (cols, rows))

원근 변환 예제:

# 원근 변환
import cv2
import numpy as np

img = cv2.imread('image.jpg')
rows, cols = img.shape[:2]

# 원근 변환 전 후 4개 좌표
pts1 = np.float32([[0, 0], [0, rows], [cols, 0], [cols, rows]])
pts2 = np.float32([[100, 50], [10, rows-50], [cols-100, 50], [cols-10, rows-50]])

# 변환 전 좌표를 원본 이미지에 표시
cv2.circle(img, (0, 0), 10, (255, 0, 0), -1)
cv2.circle(img, (0, rows), 10, (0, 255, 0), -1)
cv2.circle(img, (cols, 0), 10, (0, 0, 255), -1)
cv2.circle(img, (cols, rows), 10, (0, 255, 255), -1)

# 원근 변환 행렬 계산
mtrx = cv2.getPerspectiveTransform(pts1, pts2)

# 원근 변환 적용
dst = cv2.warpPerspective(img, mtrx, (cols, rows))

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

문서 스캔 효과 구현

원근 변환의 실용적인 응용 예제로 문서 스캔 효과를 구현할 수 있습니다:

# 마우스와 원근 변환으로 문서 스캔 효과 내기
import cv2
import numpy as np

win_name = "scanning"
img = cv2.imread("document.jpg")
rows, cols = img.shape[:2]
draw = img.copy()
pts_cnt = 0
pts = np.zeros((4, 2), dtype=np.float32)

def onMouse(event, x, y, flags, param):
    global pts_cnt
    if event == cv2.EVENT_LBUTTONDOWN:
        cv2.circle(draw, (x, y), 10, (0, 255, 0), -1)
        cv2.imshow(win_name, draw)
        pts[pts_cnt] = [x, y]
        pts_cnt += 1
        
        if pts_cnt == 4:
            # 좌표 4개 중 상하좌우 찾기
            sm = pts.sum(axis=1)  # 각 좌표 x+y 계산
            diff = np.diff(pts, axis=1)  # 각 좌표 x-y 계산
            
            topLeft = pts[np.argmin(sm)]      # x+y가 가장 작은 값이 좌상단
            bottomRight = pts[np.argmax(sm)]   # x+y가 가장 큰 값이 우하단
            topRight = pts[np.argmin(diff)]    # x-y가 가장 작은 값이 우상단
            bottomLeft = pts[np.argmax(diff)]  # x-y가 가장 큰 값이 좌하단
            
            # 변환 전 4개 좌표
            pts1 = np.float32([topLeft, topRight, bottomRight, bottomLeft])
            
            # 변환 후 영상에 사용할 서류의 폭과 높이 계산
            w1 = abs(bottomRight[0] - bottomLeft[0])
            w2 = abs(topRight[0] - topLeft[0])
            h1 = abs(topRight[1] - bottomRight[1])
            h2 = abs(topLeft[1] - bottomLeft[1])
            width = max([w1, w2])
            height = max([h1, h2])
            
            # 변환 후 4개 좌표
            pts2 = np.float32([[0, 0], [width-1, 0],
                              [width-1, height-1], [0, height-1]])
            
            # 변환 행렬 계산 및 원근 변환 적용
            mtrx = cv2.getPerspectiveTransform(pts1, pts2)
            result = cv2.warpPerspective(img, mtrx, (width, height))
            cv2.imshow('scanned', result)

cv2.imshow(win_name, img)
cv2.setMouseCallback(win_name, onMouse)
cv2.waitKey(0)
cv2.destroyAllWindows()

주요 함수 요약

기본 변환 함수

cv2.warpAffine(): 어핀 변환 적용

  • 2 x 3 변환 행렬 사용

  • 이동, 확대/축소, 회전, 어핀 변환에 사용

cv2.warpPerspective(): 원근 변환 적용

  • 3 x 3 변환 행렬 사용

  • 원근 변환 전용

cv2.resize(): 이미지 크기 조정

  • 더 간편한 확대/축소 방법

  • 픽셀 크기 또는 배율로 조정 가능

변환 행렬 생성 함수

cv2.getRotationMatrix2D(): 회전 변환 행렬 생성

  • 회전 중심, 각도, 스케일 지정

cv2.getAffineTransform(): 어핀 변환 행렬 생성

  • 3개 점 쌍으로 변환 행렬 계산

cv2.getPerspectiveTransform(): 원근 변환 행렬 생성

  • 4개 점 쌍으로 변환 행렬 계산


실무 활용 팁

보간법 선택 가이드

이미지 축소 시: cv2.INTER_AREA 사용

  • 픽셀 영역 관계를 고려한 재샘플링

  • 축소 시 가장 좋은 품질 제공

이미지 확대 시: cv2.INTER_CUBIC 또는 cv2.INTER_LINEAR 사용

  • CUBIC: 더 부드러운 결과, 계산량 많음

  • LINEAR: 빠른 처리, 적당한 품질

실시간 처리: cv2.INTER_NEAREST 사용

  • 가장 빠른 처리 속도

  • 품질은 낮음

경계 처리 방법

cv2.BORDER_CONSTANT: 지정된 색상으로 채움

  • 깔끔한 배경이 필요할 때 사용

cv2.BORDER_REFLECT: 이미지를 반사시켜 채움

  • 자연스러운 경계 처리

  • 텍스처가 있는 이미지에 적합

cv2.BORDER_REPLICATE: 가장자리 픽셀 복제

  • 단색 배경이 있는 이미지에 적합

성능 최적화

데이터 타입 주의: 변환 행렬은 반드시 np.float32 타입 사용

메모리 효율성: 큰 이미지 처리 시 ROI(관심 영역) 활용

연산 순서: 여러 변환을 한 번에 처리할 수 있도록 변환 행렬 미리 계산


참고 자료