안녕하세요. 코딩산책입니다.
이미지 처리와 컴퓨터 비전 분야에서 기하학적 변환(Geometric Transformation)은 가장 기본적이면서도 중요한 기술 중 하나입니다. 이미지를 이동하거나 회전시키고, 크기를 조정하거나 왜곡을 보정하는 작업은 다양한 응용 분야에서 필수적으로 사용됩니다. 예를 들어, 스마트폰으로 찍은 문서를 바로잡거나, 자율주행 차량에서 도로 이미지를 분석하는 데 기하학적 변환이 사용됩니다.
OpenCV의 주요 함수인 cv2.warpAffine, cv2.getRotationMatrix2D, cv2.warpPerspective, cv2.getPerspectiveTransform을 중심으로 이미지 기하학적 변환의 기본 이론과 실제 사용법을 다룹니다. 또한, 초보자도 따라 할 수 있도록 각 함수의 사용법과 예제 코드를 단계별로 설명하며, 실무에서 활용할 수 있는 고급 기법과 최적화 팁도 함께 소개합니다. 기하학적 변환의 개념부터 실제 응용까지, 이 글을 통해 OpenCV를 활용한 이미지 처리 기술을 한 단계 더 깊이 이해할 수 있을 것입니다.
1. 이미지 기하학적 변환이란?
이미지 기하학적 변환(Geometric Transformation)이란, 이미지를 구성하는 픽셀의 위치를 변경하여 새로운 이미지를 생성하는 과정을 말합니다. 이는 이미지를 회전시키거나 크기를 조정하거나, 특정한 모양으로 왜곡하는 등 다양한 작업에 사용됩니다. 기하학적 변환은 컴퓨터 비전과 이미지 처리에서 매우 중요한 역할을 하며, 다음과 같은 상황에서 자주 사용됩니다
- 이미지 전처리: 딥러닝 모델 학습을 위한 데이터 증강(Data Augmentation) 과정에서 이미지 회전, 확대/축소 등을 수행.
- 이미지 보정: 원근 왜곡이 있는 이미지를 바로잡거나, 특정 영역을 추출.
- 시각적 효과: 예를 들어, 사진 편집 프로그램에서 이미지 왜곡이나 변형 효과를 적용.
OpenCV는 Python을 통해 이러한 작업을 간단하고 효율적으로 수행할 수 있는 다양한 함수들을 제공합니다. 이 글에서는 OpenCV의 주요 함수와 이론을 활용하여 기하학적 변환을 이해하고 실제로 적용하는 방법을 배워보겠습니다.
- cv2.warpAffine: 선형 변환 (Affine Transformation)
- cv2.getRotationMatrix2D: 회전 변환 행렬 생성
- cv2.warpPerspective: 원근 변환 (Perspective Transformation)
- cv2.getPerspectiveTransform: 원근 변환 행렬 계산
2. 선형 변환과 변환 행렬의 기본 이해
기하학적 변환은 수학적으로 변환 행렬(Transformation Matrix)를 사용하여 정의됩니다. 이를 이해하기 위해 먼저 선형 변환에 대해 알아보겠습니다.
2.1. 변환 행렬의 이론적 배경
선형 변환은 이미지의 픽셀을 수학적 연산을 통해 새로운 위치로 매핑하는 과정을 의미합니다. 이러한 매핑은 일반적으로 행렬 곱셈으로 표현됩니다. 이미지의 픽셀 좌표는 2D 평면상에서 \((x,y)\)로 표현되며, 변환 후 좌표를 \((x′,y′)\)로 나타낼 수 있습니다. 이 관계는 아래의 변환 행렬로 설명됩니다.
\begin{equation} \begin{bmatrix} x' \\ y' \\ 1 \end{bmatrix} = \begin{bmatrix} a & b & c \\ d & e & f \\ 0 & 0 & 1 \\ \end{bmatrix} \cdot \begin{bmatrix} x \\ y \\ 1 \end{bmatrix} \end{equation}
여기서:
- \(a, b, d, e\) : 회전 및 크기 조정을 나타냄
- \(c, f\) : 이동 (Translation)을 나타냄
- \(1\) : 동차 좌표를 위한 값
2.2. 선형 변환의 종류
1. 이동 (Translation)
이동 변환은 이미지를 특정 방향으로 이동시키는 변환입니다.
\[\begin{bmatrix} 1 & 0 & t_{x} \\ 0 & 1 & t_{y} \\ 0 & 0 & 1 \\ \end{bmatrix}\]
\(t_{x}\), \(t_{y}\)는 각각 x축과 y축 이동 거리입니다.
2. 회전 (Rotation)
회전 변환은 이미지를 중심점 기준으로 회전시키는 변환입니다.
\[\begin{bmatrix} \cos\theta & -\sin\theta & 0 \\ \sin\theta & \cos\theta & 0 \\ 0 & 0 & 1 \\ \end{bmatrix}\]
\(\theta\)는 회전 각도(라디안)입니다.
3. 크기 조정 (Scaling)
크기 변환은 이미지를 확대 또는 축소하는 변환입니다.
\[\begin{bmatrix} s_{x} & 0 & 0 \\ 0 & s_{y} & 0 \\ 0 & 0 & 1 \\ \end{bmatrix}\]
\(s_{x}\), \(s_{y}\)는 각각 x축과 y축의 배율입니다.
3. Affine 변환 (cv2.warpAffine)
Affine 변환은 직선성을 유지하면서 이미지를 선형적으로 변환하는 방식입니다. 이미지의 크기, 위치, 방향을 바꾸지만 원래의 평행 관계는 유지됩니다. OpenCV에서 Affine 변환은 cv2.warpAffine 함수를 통해 수행되며, 다음과 같은 작업에서 사용됩니다.
- 이미지를 특정 각도로 회전
- 이미지를 확대하거나 축소
- 이미지를 특정 방향으로 이동
Affine 변환은 2x3 크기의 변환 행렬을 사용하며, 이 행렬을 통해 입력 이미지의 좌표를 새로운 좌표로 매핑합니다.
3.1. Affine 변환의 행렬 수식
Affine 변환 행렬은 다음과 같은 수식으로 표현됩니다.
\begin{equation} \begin{bmatrix} x' \\ y' \end{bmatrix} = \begin{bmatrix} a & b & t_{x} \\ d & e & t_{y} \end{bmatrix} \cdot \begin{bmatrix} x \\ y \\ 1 \end{bmatrix} \end{equation}
여기서:
- \(a\), \(b\), \(d\), \(e\)는 회전 및 스케일 조정 요소
- \(t_{x}\), \(t_{y}\)는 x축과 y축 방향의 이동
- (\(x\), \(y\))는 원본 이미지의 좌표
- (\(x^{'}\), \(y^{'}\))는 변환된 이미지의 좌표
소스코드
import cv2
import numpy as np
image = cv2.imread("lena.bmp")
rows, cols = image.shape[:2]
# translation
tx, ty = 50, 100
translation_matrix = np.float32([[1, 0, tx], [0, 1, ty]])
translated_image = cv2.warpAffine(image, translation_matrix, (cols, rows))
# rotation, scaling
scale = 1.0
angle = 45
center = (cols // 2, rows // 2)
rotation_matrix = cv2.getRotationMatrix2D(center, angle, scale)
rotated_image = cv2.warpAffine(image, rotation_matrix, (cols, rows))
cv2.imshow("Translated Image", translated_image)
cv2.imshow("Rotated Image", rotated_image)
cv2.waitKey(0)
cv2.destroyAllWindows()
![]() |
![]() |
4. 회전 변환
회전 변환은 이미지를 특정 중싱점을 기준으로 시계 방향 또는 반시계 방향으로 회전시키는 작업입니다. 이는 Affine 변환의 한 종류로 간주될 수 있으며, OpenCV에서는 이를 간단히 수행할 수 있도록 cv2.getRotationMatrix2D 함수를 제공하지만, Affine 변환의 수식을 이해하기에 도움이 될수 있도록 수식을 직접 구현해 보았다.
4.1. 회전 변환의 행렬 수식
회전을 수행하기 위한 행렬은 다음과 같이 정의됩니다.
\[ \lambda = scale \]
\[ \alpha = \lambda \cdot \cos\theta \]
\[ \beta = \lambda \cdot \sin\theta \]
\begin{equation} \begin{bmatrix} x^{'} \\ y^{'} \end{bmatrix} = \begin{bmatrix} \alpha & -\beta & (1-\alpha) \cdot x_{c} + \beta \cdot y_{c} \\ \beta & \alpha & (1-\alpha) \cdot y_{c} - \beta \cdot x_{c} \\ \end{bmatrix} \cdot \begin{bmatrix} x \\ y \\ 1 \end{bmatrix} \end{equation}
여기서:
- \(x_{c}\), \(y_{c}\)는 회전 중심점의 좌표
- \(\theta\)는 회전 각도(라디안)
OpenCV의 cv2.getRotationMatrix2D 함수는 위 수식과 동일하게 회전 행렬을 자동 생성합니다.
소스코드
import cv2
import numpy as np
image = cv2.imread("input.jpg")
rows, cols = image.shape[:2]
scale = 1.0
angle = 45
center = (cols // 2, rows // 2)
theta = np.radians(-angle)
alpha = scale * np.cos(theta)
beta = scale * np.sin(theta)
cx, cy = center
rotation_impl_matrix = np.array([
[alpha, -beta, (1 - alpha) * cx + beta * cy],
[beta, alpha, (1 - alpha) * cy - beta * cx]
])
rotated_impl_image = cv2.warpAffine(image, rotation_matrix, (cols, rows))
cv2.imshow("Rotated Impl Image", rotated_impl_image)
rotation_cv_matrix = cv2.getRotationMatrix2D(center, angle, scale)
rotated_cv_image = cv2.warpAffine(image, rotation_cv_matrix, (cols, rows))
cv2.imshow("Rotated CV Image", rotated_cv_image)
cv2.waitKey(0)
cv2.destroyAllWindows()
![]() |
![]() |
5. 원근 변환 (cv2.warpPerspective)
원근 변환 (Perspective Transformation)은 이미지를 기하학적으로 왜곡하여 특정 시점에서 바라본 것처럼 보이게 하는 변환입니다. 이 변환은 이미지의 네 개의 점을 기준으로 새로운 위치로 매핑하여 이미지를 조정합니다. 예를 들어, 기울어진 문서 사진을 직사각형 형태로 바로잡거나, 특정 영역을 확대하여 원근감을 제거할 때 사용됩니다. 원근 변환은 직선성을 유지하지만, Affine 변환과 달리 평행선이 반드시 유지되지 않습니다. 이는 이미지의 시점(viewpoint)을 바꾸는 데 특히 유용합니다.
5.1. 원근 변환의 수학적 원리
원근 변환은 3x3 변환 행렬 \(H\)를 사용하며, 이 행렬은 동차 좌표계(Homogeneous Coordinates)에서 표현됩니다. 변환 후 좌표 (\(x^{'}\), \(y^{'}\))는 다음과 같이 계산됩니다.
\begin{equation} \begin{bmatrix} x^{'} \\ y^{'} \\ w \end{bmatrix} = \begin{bmatrix} h_{11} & h_{12} & h_{13} \\ h_{21} & h_{22} & h_{23} \\ h_{31} & h_{32} & h_{33} \end{bmatrix} \cdot \begin{bmatrix} x \\ y \\ 1 \end{bmatrix} \end{equation}
변환 후 좌표는 \(w\)로 나누어 정규화됩니다.
\[ x^{'} = \frac{h_{11}x + h_{12}y + h_{13}}{h_{31}x + h_{32}y + h_{33}}, y^{'} = \frac{h_{21}x + h_{22}y + h_{23}}{h_{31}x + h_{32}y + h_{33}} \]
여기서 \( h_{ij} \) 는 변환 행렬의 요소로, 4쌍의 대응점(원본 이미지와 변환된 이미지의 좌표)을 통해 계산됩니다.
6. Perspective Transform과 매핑
cv2.getPerspectiveTransform 함수는 투시 변환 행렬 \( H \) 를 계산하는 데 사용됩니다.
이 함수는 원본 이미지의 4개 좌표와 결과 이미지의 4개 좌표를 입력으로 받아 \( H \) 를 반환합니다.
아래는 사다리꼴 이미지를 직사각형으로 변환하는 예제입니다.
import numpy as np
import cv2
image = cv2.imread("resources/trapezoid.jpg")
rows, cols = image.shape[:2]
def get_perspective_transform(src_pts, dst_pts):
A = []
B = []
for i in range(4):
x, y = src_pts[i]
x_p, y_p = dst_pts[i]
A.append([x, y, 1, 0, 0, 0, -x_p * x, -x_p * y])
A.append([0, 0, 0, x, y, 1, -y_p * x, -y_p * y])
B.append(x_p)
B.append(y_p)
A = np.array(A, dtype=np.float64)
B = np.array(B, dtype=np.float64)
h = np.linalg.solve(A, B)
H = np.append(h, 1).reshape(3, 3)
return H
src_pts = np.float32([[208, 74], [586, 74], [72, 516], [722, 516]])
dst_pts = np.float32([[0, 0], [200, 0], [0, 300], [200, 300]])
perspective_impl_matrix = get_perspective_transform(src_pts, dst_pts)
warped_impl_image = cv2.warpPerspective(image, perspective_impl_matrix, (200, 300))
perspective_cv_matrix = cv2.getPerspectiveTransform(src_pts, dst_pts)
warped_cv_image = cv2.warpPerspective(image, perspective_cv_matrix, (200, 300))
cv2.imshow("Warped Impl Image", warped_impl_image)
cv2.imshow("Warped CV Image", warped_cv_image)
cv2.waitKey(0)
cv2.destroyAllWindows()
![]() |
![]() |
![]() |
변환된 결과의 확인을 정확하게 하기 위해 가운데에 빨갠색의 사다리꼴을 하나 더 넣어 변환을 하였습니다. 사다리꼴의 이미지를 변환시에 위의 좌표가 아래의 좌표보다 참조 할 수 있는 영역이 적기때문에 오차가 발생하며, 그로 인해 변환된 이미지에서 위쪽 부분이 더 오차가 크다는걸 확인 할 수 있습니다.
7. 이미지 변환의 성능 최적화 및 팁
이미지 기하학적 변환을 실시간 애플리케이션(예: 객체 추적, 동영상 처리 등)이나 대규모 데이터에서 사용하려면 성능 최적화가 중요합니다. 여기서는 변환의 효율성을 높이고 정확성을 유지하기 위한 팁을 살펴봅니다.
7.1. ROI (Region of Interest) 활용
이미지 전체를 변환하지 않고, 관심 영역 (ROI) 만 변환하면 성능이 크게 개선되며, 향상된 속도로 처리할 수 있습니다. 예를 들어, 번호판 인식에서 자동차 전체 이미지를 처리하는 대신 번호판 영역만 선택적으로 처리하거나, 혹은 명함 인식에서 전체의 이미지를 처리하는게 아니라 명함의 영역만 선택적으로 처리하면 개선된 성능을 얻을 수 있습니다.
7.2. 저해상도 처리 활용
처리 속도를 높이기 위해 변환 전 이미지의 해상도를 축소하여 찾고자 하는 영역에서의 좌표를 찾은 후, 원본에서의 좌표로 변환하여 사용하는 방법이 있습니다. 이는 실시간 처리가 중요한 애플리케이션에 적합하며, 모바일 환경이나 혹은 임베디드 환경에서 많이 사용하는 방식입니다.
주의: 해상도 축소 과정에서 일부 세부 정보가 손실될 수 있으므로, 축소하는 최적의 해상도를 찾아 사용하면 적절한 균형을 찾아 실시간 처리기 가능합니다.
7.3. 병렬처리 및 가속화
OpenCV는 NVIDIA GPU, Intel CPU, Android, iPhone 환경에서 가속을 지원합니다. 대규모 이미지나 저사양의 모바일 환경 혹은 임베디드에서는 실시간 변환 작업을 위해 이와 같은 가속화 처리를 사용하지 않으면 처리 속도가 현저하게 떨어집니다. 따라서 CPU/GPU 환경에 맞는 가속화 옵션을 사용하는것을 추천드립니다. 이미지 처리에서의 가속화 및 병렬처리의 직접구현보다는 OpenCV에 구현되어 있는 함수를 사용하는것이 훨씬 더 성능이 뛰어나며, 직접 구현시에는 이보다 낮은 성능을 낼 수 있으니 꼭 비교해보시며 구현하길 추천드립니다.
결론
이미지 기하학적 변환은 단순한 이미지 조작 이상의 가능성을 제공합니다. 이 기술은 객체 인식, 영상 교정, 증강 현실, OCR 등 다양한 분야에서 핵심 역할을 합니다. OpenCV의 함수들은 사용하기 쉽고 효율적이어서, 초보 개발자부터 숙련된 전문가까지 누구나 유용하게 활용할 수 있습니다.
이번 글을 통해 선형 변환과 변환 행렬의 기본 원리, Affine 및 투시 변환의 수학적 배경과 실전 코드를 배웠을 것입니다. 더 나아가, ROI 활용, 저해상도 처리, 가속화 등의 최적화 방법도 익혔다면, 기하학적 변환의 성능을 한 단계 끌어올릴 수 있습니다.
이제 배운 내용을 직접 프로젝트에 적용해보세요! OpenCV의 기하학적 변환 기능은 데이터의 가치를 극대화하고, 더 나은 컴퓨터 비전 애플리케이션을 만드는 데 크게 기여할 것입니다. 앞으로도 이와 같은 핵심 기술을 지속적으로 학습하고 활용해 나가시길 바랍니다.
해당 포스트가 유용하셨다면 하단의 좋아요와 구독하기 부탁드립니다. ^^
[Codingwalks]에게 송금하기 - AQR
[Codingwalks]에게 송금하기 - AQR
aq.gy
★ 모든 내용은 아래의 링크를 참조하였습니다. ★
OpenCV: OpenCV-Python Tutorials
Core Operations In this section you will learn basic operations on image like pixel editing, geometric transformations, code optimization, some mathematical tools etc.
docs.opencv.org
'강의 (Lecture) > OpenCV 마스터 with Python (초급)' 카테고리의 다른 글
OpenCV + Python 외곽선 검출과 레이블링(labeling) - 2 (2) | 2024.10.30 |
---|---|
OpenCV + Python 외곽선 검출과 레이블링(labeling) - 1 (0) | 2024.10.30 |
OpenCV + Python 모폴로지 (Morphology) (0) | 2024.10.25 |
OpenCV + Python 이진화 (임계처리) (5) | 2024.10.22 |
OpenCV + Python 엣지(Edge) 검출과 허프(Hough) 변환 (2) | 2024.10.21 |