Python/Vision Code

[OpenCV] 아핀 변환 함수 cv2.warpAffine 예제로 사용법 알아보기

jimmy_AI 2025. 10. 19. 23:58
반응형

안녕하세요.

이번 글에서는 python opencv에서 제공하는 아핀 변환 함수인

cv2.warpAffine에 대하여 몇 가지 대표적인 활용 예제를 중심으로

사용법을 살펴보도록 하겠습니다.

 

먼저, 다음과 같은 모듈 설치 과정과 시각화 헬퍼 함수를 구현해보도록 하겠습니다.

# 모듈 설치 명령어: !pip install opencv-python numpy matplotlib

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

# 이미지를 화면에 표시하는 헬퍼 함수
def show_images(images, titles, figsize=(15, 5)):
    plt.figure(figsize=figsize)
    for i, (img, title) in enumerate(zip(images, titles)):
        plt.subplot(1, len(images), i + 1)
        if len(img.shape) == 3:
            plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
        else:
            plt.imshow(img, cmap='gray')
        plt.title(title)
        plt.axis('off')
    plt.tight_layout()
    plt.show()

 

 

기본 사용법

cv2.warpAffine의 기본적인 사용 방법은 다음과 같습니다.

(여기에 빈 부분 보간과 관련된 인자도 추가 가능)

결과이미지 = cv2.warpAffine(원본이미지, 변환행렬, (가로길이, 세로길이))

 

 

여기서 보통 M이라고 부르는 변환행렬은 2 x 3의 행렬 형태이며,

다음과 같은 변환 공식을 따릅니다.

2×3 행렬 M 형태:
[a  b  tx]
[c  d  ty]

변환 공식:
x' = a*x + b*y + tx
y' = c*x + d*y + ty

 

 

 

예시 이미지 준비

먼저, 다음과 같은 코드로 아핀 변환을 진행할 예시 이미지를 준비해 보겠습니다.

def create_sample_image():
    img = np.zeros((300, 400, 3), dtype=np.uint8)
    img[:, :] = (240, 240, 240)  # 밝은 회색 배경
    
    # 빨간 사각형
    cv2.rectangle(img, (50, 50), (150, 150), (0, 0, 255), -1)
    
    # 파란 원
    cv2.circle(img, (300, 100), 50, (255, 0, 0), -1)
    
    # 초록 삼각형
    pts = np.array([[200, 250], [150, 200], [250, 200]], np.int32)
    cv2.fillPoly(img, [pts], (0, 255, 0))
    
    # 텍스트
    cv2.putText(img, 'OpenCV', (50, 280), 
                cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 0), 2)
    
    return img

# 이미지 생성
original = create_sample_image()
height, width = original.shape[:2]

# 원본 이미지 표시
show_images([original], ['Original'])

 

 

이미지 회전: cv2.getRotationMatrix2D

아핀 변환으로 이미지를 회전하는 방법은 변환 행렬을 cv2.getRotationMatrix2D 함수로

다음과 같이 구하여 적용할 수 있습니다.(45도와 90도 회전 예시)

# 이미지 중심점 계산
center = (width // 2, height // 2)

# getRotationMatrix2D(중심점, 각도, 크기배율)
M_rotate_45 = cv2.getRotationMatrix2D(center, 45, 1)
M_rotate_90 = cv2.getRotationMatrix2D(center, 90, 1)

# 변환 적용
rotated_45 = cv2.warpAffine(original, M_rotate_45, (width, height))
rotated_90 = cv2.warpAffine(original, M_rotate_90, (width, height))

show_images([rotated_45, rotated_90], 
            ['45° rotate', '90° rotate'])

 

만일, 이미지 회전 시 위와 같이 빈 부분과 잘리는 부분을 보완하고 싶다면

다음과 같이 코드를 변경하여 적용해볼 수도 있습니다.

M = cv2.getRotationMatrix2D(center, 45, 1.0)

# 회전 후 필요한 새로운 이미지 크기 계산
cos = np.abs(M[0, 0])
sin = np.abs(M[0, 1])

new_width = int((height * sin) + (width * cos))
new_height = int((height * cos) + (width * sin))

# 변환 행렬 조정 (중심을 새 이미지 중심으로)
M[0, 2] += (new_width / 2) - center[0]
M[1, 2] += (new_height / 2) - center[1]

# 변환 적용
rotated_full = cv2.warpAffine(original, M, (new_width, new_height),
                               borderValue=(255, 255, 255))

show_images([rotated_full],
            ['45° rotate ver 2'])

 

 

이미지 평행이동, 기울이기

아핀 변환으로 이미지의 평행이동이나 기울이기도 쉽게 가능합니다.

 

먼저, 평행이동을 수행하는 예시 코드는 다음과 같습니다.

# 오른쪽으로 50픽셀, 아래로 30픽셀 이동하는 변환 행렬 생성
tx, ty = 50, 30
M_translate = np.float32([[1, 0, tx],
                          [0, 1, ty]])

translated = cv2.warpAffine(original, M_translate, (width, height))

show_images([translated],
            [f'move (x:{tx}, y:{ty})'])

 

이미지 기울이기도 다음과 같이 변환 행렬을 직접 생성하여 수행할 수 있습니다.

# X축 방향으로 기울이기
shear_factor = 0.3 # 기울임의 정도
M_shear_x = np.float32([[1, shear_factor, 0],
                        [0, 1, 0]])

sheared_x = cv2.warpAffine(original, M_shear_x, 
                           (width + int(height * shear_factor), height))

# Y축 방향으로 기울이기
M_shear_y = np.float32([[1, 0, 0],
                        [shear_factor, 1, 0]])

sheared_y = cv2.warpAffine(original, M_shear_y,
                           (width, height + int(width * shear_factor)))

show_images([sheared_x, sheared_y],
            ['X axis sheared', 'Y axis sheared'])

 

 

세 점을 이용한 변환: cv2.getAffineTransform

원본 이미지의 3개 점이 변환 후 어떤 3개의 점으로 이동할지 안다면,

이에 해당하는 아핀 변환을 수행할 수 있습니다.

 

이때 필요한 변환 행렬은 cv2.getAffineTransform 함수로 구할 수 있습니다.

# 원본 이미지의 3개 점
pts_src = np.float32([
    [50, 50],      # 왼쪽 위
    [350, 50],     # 오른쪽 위
    [50, 250]      # 왼쪽 아래
])

# 목표 위치의 3개 점
pts_dst = np.float32([
    [320, 100],    # 왼쪽 위 점 -> 오른쪽, 살짝 아래로
    [80, 80],      # 오른쪽 위 점 -> 왼쪽. 살짝 아래로
    [100, 280]     # 왼쪽 아래 점 -> 살짝 오른쪽 아래로
])

# 변환 행렬 자동 계산
M_affine = cv2.getAffineTransform(pts_src, pts_dst)

# 변환 적용
warped = cv2.warpAffine(original, M_affine, (width, height))

# 점들을 표시한 이미지 생성
img_with_points = original.copy()
for pt in pts_src:
    cv2.circle(img_with_points, tuple(pt.astype(int)), 5, (255, 0, 0), -1)

result_with_points = warped.copy()
for pt in pts_dst:
    cv2.circle(result_with_points, tuple(pt.astype(int)), 5, (0, 255, 0), -1)

show_images([img_with_points, result_with_points],
            ['Original (Blue Point)', 'Transformed (Green Point)'])

 

 

여러 변환을 한 번에 수행하기

아핀 변환에서는 여러 변환을 결합한 방법을 행렬곱을 통하여 한 번에 효율적으로 적용이 가능합니다.

# M1 변환 -> M2 변환을 순서대로 수행하는 예시

# 주의: 변환 행렬은 2x3이므로 마지막 행 [0,0,1]을 추가해야 곱셈 가능
M1_3x3 = np.vstack([M1, [0, 0, 1]])
M2_3x3 = np.vstack([M2, [0, 0, 1]])

# 행렬 곱셈 (주의: 순서가 2 -> 1인 것이 중요!)
M_combined_3x3 = M2_3x3 @ M1_3x3

# 다시 2x3로 변환
M_combined = M_combined_3x3[:2, :]

 

여기까지 파이썬에서 OpenCV를 활용한 아핀 변환의 대표적인 예제 케이스를 살펴보았습니다.

보간 방법이나 경계 처리 등 다루지 않은 기능도 일부 있으니 더 상세한 기능이 궁금하신 분들은

OpenCV 공식 문서를 찾아보시는 것을 권장드립니다.

 

이 글이 파이썬 아핀 변환 과정에 도움이 되셨기를 기원합니다.

감사합니다.