안녕하세요. 코딩산책입니다.
이미지 크기 조정과 자르기는 이미지 처리에서 가장 기본적이면서도 중요한 작업 중 하나입니다. 웹 개발, 디자인, 데이터 분석, 인공지능 등 다양한 분야에서 이미지의 크기를 조절하거나 특정 부분만을 사용해야 하는 경우가 많습니다. 특히, 컴퓨터 비전 작업에서는 이미지를 분석하기 전 크기를 최적화하거나, 필요 없는 부분을 제거하는 작업이 필수적입니다. OpenCV는 이러한 작업을 손쉽게 수행할 수 있는 강력한 도구를 제공합니다. 이 글에서는 OpenCV를 사용해 이미지를 원하는 크기로 변경하거나, 이미지에서 필요한 영역만을 선택해 자르는 방법을 소개합니다. 간단한 함수와 직관적인 사용법을 통해 이미지 처리 작업을 보다 효과적으로 수행할 수 있습니다. 이제 이미지 크기 조정과 자르기 방법을 자세히 알아보겠습니다.
1. OpenCV의 좌표 체계
OpenCV에서 이미지를 다룰 때, 좌표 체계를 이해하는 것이 매우 중요합니다. OpenCV는 2차원 행렬로 이미지를 표현하며, 각 픽셀은 행렬의 요소로 다뤄집니다. 이때 좌표는 이미지의 픽셀 위치를 나타내며, 수학에서 사용하는 좌표 체계와는 약간의 차이가 있습니다. 수학에서 그래프를 그릴 때, 일반적으로 x축의 양의 방향은 동쪽, y축의 양의 방향은 북쪽을 가리킵니다. 하지만 OpenCV에서는 x축의 양의 방향은 동일하게 동쪽을 가리키지만, y축의 양의 방향은 남쪽을 가리킵니다. 이미지가 512x512 크기라면, 원점은 왼쪽 상단에 위치하며 최대 너비와 높이는 오른쪽 하단에 위치하게 됩니다.
2. 이미지 자르기
이미지를 자르기 위해서는 이미지 자체를 하나의 행렬(matrix)로 다루면 됩니다. OpenCV에서는 별도의 자르기 함수 없이 배열 인덱싱을 통해 이미지를 자를 수 있습니다. 자르기 시에는 높이와 너비의 시작점과 끝점을 지정하면 됩니다.
import cv
img = cv2.imread('resources/baboon.bmp')
cv2.imshow('Original Image', img)
# Check image size
print(img.shape) # (480, 500, 3)
cropped_img = img[50:150,30:130]
cv2.imshow('Cropped Image', cropped_img)
# Check the cropped image size
print(cropped_img.shape) # (100, 100, 3)
cv2.waitKey(0)
cv2.destroyAllWindows()
3. 이미지 크기 조정
이미지 크기 조정은 OpenCV에서 매우 중요한 기능 중 하나입니다. 또한 이미지의 크기를 조정하려면, 먼저 이미지의 현재 크기를 알아야 합니다. 예를 들어, ‘baboon.bmp’라는 이름의 이미지를 불러온 후, img.shape를 이용해 이미지의 크기를 확인할 수 있습니다. 아래 코드는 이미지의 크기를 확인하고, 크기를 조정하는 방법을 보여줍니다. 이미지를 축소하거나 확대할 때 cv2.resize() 함수를 사용하며, 픽셀 값의 재계산이 필요하기 때문에 보간법(Interpolation)을 사용합니다. 보간법은 픽셀 값을 계산하는 방법으로 이미지 품질에 큰 영향을 미치며, OpenCV에서는 다양한 보간법을 제공합니다.
3.1. cv2.INTER_NEAREST (최근접 이웃 보간법, Nearest Neighbor Interpolation)
최근접 이웃 보간법은 가장 간단한 보간법으로, 픽셀 값이 변환된 좌표에서 가장 가까운 픽셀 값을 그대로 가져오기 때문에 빠르고 계산이 간단합니다. 하지만 이미지 확대 시 계단 현상(aliasing)이 발생하여 품질이 떨어지며, 부드럽지 않은 이미지를 생성할 수 있습니다. 일반적으로 성능이 중요할 때나 단순한 용도로 사용됩니다.
\[I{\prime}(x{\prime}, y{\prime}) = I\left( \text{round}(x), \text{round}(y) \right)\]
여기서 \(I{\prime}(x{\prime}, y{\prime})\)는 새 이미지의 픽셀 값, \(I(x, y)\) 는 원본 이미지의 픽셀 값입니다. \(x\) 와 \(y\) 는 실수 좌표를 정수로 반올림해 가장 가까운 이웃 픽셀 값을 그대로 가져옵니다.
3.2. cv2.INTER_LINEAR (양선형 보간법, Bilinear Interpolation)
cv2.resize()에서 기본으로 사용되는 보간법으로 양선형 보간법, cv2.INTER_LINEAR을 사용하여 크기를 조정합니다. 양선형 보간법은 가장 가까운 4개의 픽셀을 선형적으로 가중합하여 새로운 픽셀 값을 계산합니다. INTER_NEAREST와 비교해 더 부드럽고 자연스러운 이미지를 얻을 수 있지만, 경계 부분이 다소 흐릿해질 수 있습니다. 일반적으로 확대 및 축소 작업에서 자주 사용됩니다. 이 방법은 중간 정도의 품질을 제공하며, 속도와 품질의 균형을 맞추는 데 적합합니다.
\[I{\prime}(x{\prime}, y{\prime}) = (1 - a)*(1 - b)*I(x_1, y_1) + a(1 - b)*I(x_2, y_1) + (1 - a)*b*I(x_1, y_2) + a*b*I(x_2, y_2)\]
여기서 \(x_1\), \(x_2\) 는 \(x\) 주변의 가장 가까운 정수 좌표, \(y_1\), \(y_2\) 는 \(y\) 주변의 가장 가까운 정수 좌표입니다. \(a = x - x_1\) 이고, \(b = y - y_1\) 입니다.
3.3. cv2.INTER_CUBIC (3차 보간법, Bicubic Interpolation)
3차 보간법은 주변 4x4 픽셀 블록을 이용해 계산하는 방식으로, 2차원에서 3차 다항식을 사용합니다. 이미지의 질감이 매우 부드럽고 자연스러우며, 고품질의 확대된 이미지를 얻을 수 있습니다. 계산 비용은 높지만, 이미지 확대 시 가장 자주 사용하는 방법 중 하나입니다.
\[I{\prime}(x{\prime}, y{\prime}) = \sum_{i=-1}^{2} \sum_{j=-1}^{2} w(i, j) I(x+i, y+j)\]
여기서 \(w(i, j)\) 는 16개의 주변 픽셀에 대한 가중치로, 3차 다항식을 기반으로 계산됩니다. 이 가중치는 다음과 같이 정의됩니다:
\[w(x) = \begin{cases}
(a + 2) |x|^3 - (a + 3) |x|^2 + 1 & \text{if } |x| \leq 1 \\
a |x|^3 - 5a |x|^2 + 8a |x| - 4a & \text{if } 1 < |x| < 2 \\
0 & \text{if } |x| \geq 2
\end{cases}\]
여기서 \(a = -0.5\) 가 자주 사용됩니다.
3.4. cv2.INTER_LANCZOS4 (Lanczos 보간법, Lanczos Interpolation)
Lanczos 보간법은 고차원의 Sinc 함수 기반 보간법으로, 가장 복잡하고 계산량이 많습니다. 높은 품질의 이미지 확대를 제공하며, 특히 경계선이 날카로운 이미지를 처리할 때 적합합니다. 하지만 계산 비용이 높기 때문에 실시간 처리가 필요한 작업에서는 잘 사용되지 않으며, 주로 이미지 품질을 중요하게 여기는 작업에서 사용됩니다.
\[ I{\prime}(x{\prime}, y{\prime}) = \sum_{i=-n}^{n} \sum_{j=-n}^{n} I(x+i, y+j) \cdot \text{Lanczos}(x - i) \cdot \text{Lanczos}(y - j) \]
여기서 Lanczos 함수는 다음과 같이 정의됩니다:
\[\text{Lanczos}(x) = \begin{cases}
\frac{\sin(\pi x) \sin(\pi x / a)}{(\pi x)^2} & \text{if } -n < x < n \\
0 & \text{otherwise}
\end{cases}\]
일반적으로 \(n = 4\) 로 설정됩니다.
3.5. cv2.INTER_AREA (영역 보간법, Area-Based Interpolation)
영역 기반 보간법은 OpenCV에서 이미지 축소에 특화된 방법입니다. 다른 보간법과는 달리, INTER_AREA는 보간이라기보다는 영역에 기반한 가중 평균을 계산하는 방식입니다. 즉, 축소되는 이미지에서 각 새로운 픽셀 값은 원본 이미지에서 해당 픽셀에 대응하는 영역의 평균값으로 계산됩니다. 픽셀들의 면적을 고려하여 평균을 계산하기 때문에, 이미지 축소 시 노이즈가 적고, 부드럽고 자연스러운 이미지를 생성할 수 있습니다. 축소된 이미지에서 디테일이 유지되며, 특히 작은 크기로 축소할 때 유용합니다. 하지만 이미지 확대에서는 사용되지 않습니다.
축소할 때, 새로운 픽셀 값 \(I{\prime}(x{\prime}, y{\prime})\) 는 원본 이미지의 영역에서 여러 픽셀 값을 합산하고, 그 영역의 크기로 나눈 값으로 정의됩니다. 이를 수식으로 표현하면 다음과 같습니다:
\[I{\prime}(x{\prime}, y{\prime}) = \frac{1}{A} \sum_{i=1}^{m} \sum_{j=1}^{n} I(x + i, y + j)\]
여기서, \( I{\prime}(x{\prime}, y{\prime}) \)는 축소된 이미지의 픽셀 값, \( I(x + i, y + j) \) 는 원본 이미지에서 영역에 해당하는 픽셀 값, \( A = m \times n \) 는 원본 이미지에서 축소된 이미지의 한 픽셀에 해당하는 영역의 면적, \(m\) 과 \(n\) 은 원본 이미지에서 축소된 이미지의 픽셀이 차지하는 가로, 세로 범위(픽셀 수)를 의미합니다.
3.6. 보간법에 따른 이미지 차이
각 보간법은 특정 상황에 적합하며, 이미지의 크기를 확대할 때와 축소할 때의 결과물은 다를 수 있습니다. 아래는 보간법을 선택할 때 고려할 요소입니다.
• 확대할 때: cv2.INTER_CUBIC이나 cv2.INTER_LANCZOS4가 좋은 품질을 제공합니다.
• 축소할 때: cv2.INTER_AREA가 왜곡 없이 부드러운 이미지를 만듭니다.
• 속도가 중요할 때: cv2.INTER_NEAREST 또는 cv2.INTER_LINEAR가 빠른 계산 속도를 제공합니다.
cv2.resize 확대(x5) 예시:
import cv2
img = cv2.imread('resources/baboon.bmp')
cv2.imshow('Original Image', img)
# Check image size
print(img.shape) # (480, 500, 3)
# Resize image
img_nearest = cv2.resize(img, (2500, 2400), interpolation=cv2.INTER_NEAREST)
img_linear = cv2.resize(img, (2500, 2400), interpolation=cv2.INTER_LINEAR)
img_cubic = cv2.resize(img, (2500, 2400), interpolation=cv2.INTER_CUBIC)
img_lanczos = cv2.resize(img, (2500, 2400), interpolation=cv2.INTER_LANCZOS4)
cv2.imshow('Nearest Neighborhood Interpolation', img_nearest)
cv2.imshow('Linear Interpolation', img_nearest)
cv2.imshow('Cubic Interpolation', img_cubic)
cv2.imshow('Lanczos Interpolation', img_lanczos)
# Check the adjusted image size
print(img_nearest.shape) # (2400, 2500, 3)
cv2.waitKey(0)
cv2.destroyAllWindows()
cv2.resize 축소(x5) 예시:
import cv2
img = cv2.imread('resources/baboon.bmp')
cv2.imshow('Original Image', img)
# Check image size
print(img.shape) # (480, 500, 3)
# Resize image
img_nearest = cv2.resize(img, (100, 96), interpolation=cv2.INTER_NEAREST)
img_linear = cv2.resize(img, (100, 96), interpolation=cv2.INTER_LINEAR)
img_area = cv2.resize(img, (100, 96), interpolation=cv2.INTER_AREA)
cv2.imshow('Nearest Neighborhood Interpolation', img_nearest)
cv2.imshow('Linear Interpolation', img_nearest)
cv2.imshow('Area-Based Interpolation', img_area)
# Check the adjusted image size
print(img_nearest.shape) # (96, 100, 3)
cv2.waitKey(0)
cv2.destroyAllWindows()
위에서 설명한 바와 같이 각각의 보간법에 따라서 trade-off가 발생합니다.
확대 시에 속도가 중요한 경우에는 nearest 혹은 linear를 사용할 수 있지만 aliasing이 발생하거나 고주파성분(예: 이미지의 디테일이나 선명도)이 손상될 수 있는걸 위의 확대 이미지로 확인할 수 있습니다. 반면 속도를 희생하고 품질을 높이기 위해 cubic 혹은 lanczos를 사용하지만 이를 위해서는 수행 속도의 개선이 필요합니다.
축소 시에도 마찬가지로 속도를 빠르게 해야 하거나 이미지의 품질로 인해 영향을 받지 않는다면 nearest 혹은 linear를 사용할 수 있지만, 되도록이면 area-based를 사용하여 속도나 품질을 함께 대응할 수 있는 방법을 선택하는 것이 합리적인 방법입니다.
ps.) 작가의 개인적인 경험 혹은 견해이지만 현업에서는 cubic을 가장 많이 사용하였으며, 속도 개선을 위해 아키텍처에 따른 코드 최적화를 적용하여 사용함. lanczos를 사용해야 하는 경우도 있었지만 이는 이미지의 디테일이나 선명도가 중요한 분야에서만 사용되고 그렇지 않은 경우에는 보통적으로 cubic을 사용하였음.
4. 결론
OpenCV에서 이미지를 처리할 때, 이미지의 좌표 체계와 배열 인덱싱을 이해하는 것이 중요합니다. 이 글에서는 이미지의 크기를 조정하고 자르는 방법에 대해 알아보았습니다. OpenCV에서는 cv2.resize 함수를 사용해 이미지 크기를 조정할 수 있으며, 목적에 맞는 보간법을 선택하는 것이 중요합니다. 이미지 품질이 중요할수록 INTER_CUBIC 또는 INTER_LANCZOS4를 사용하고, 속도가 중요한 경우 INTER_NEAREST 또는 INTER_LINEAR가 적합합니다. INTER_LINEAR는 실시간 처리에서 자주 사용되고, INTER_CUBIC과 INTER_LANCZOS4는 인쇄물이나 그래픽 작업에서 많이 활용됩니다. 추가적으로, 자르기는 배열 인덱스를 사용해 간단히 처리할 수 있습니다.
해당 포스트가 유용하셨다면 하단의 좋아요와 구독하기 부탁드립니다. ^^
★ 모든 내용은 아래의 링크를 참조하였습니다. ★
'강의 (Lecture) > OpenCV 마스터 with Python (초급)' 카테고리의 다른 글
OpenCV + Python 웹캠을 활용한 색상 인식 및 그리기 (실습) (0) | 2024.10.11 |
---|---|
OpenCV + Python 색 공간 변환과 색상 검출 (2) | 2024.10.10 |
OpenCV + Python 산술 연산과 논리 연산 (0) | 2024.10.08 |
OpenCV + Python 히스토그램 분석 (1) | 2024.10.07 |
OpenCV + Python 이미지의 밝기와 명암 조절 (0) | 2024.10.05 |