안녕하세요.
이번 글에서는 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 공식 문서를 찾아보시는 것을 권장드립니다.
이 글이 파이썬 아핀 변환 과정에 도움이 되셨기를 기원합니다.
감사합니다.
'Python > Vision Code' 카테고리의 다른 글
| [OpenCV] cv2.resize 보간 방법 정리 (0) | 2025.11.16 |
|---|---|
| insightface FaceAnalysis로 얼굴 추출 및 정보 분석 예제 (0) | 2025.10.12 |
| 파이썬 diffusers 모듈로 stable diffusion 사용해보기 예제 (0) | 2025.10.03 |