서버를 운용하거나 영상 처리를 수행할 때 저장공간 또는 수행 속도를 위해 이미지 크기를 변환하기도 한다. 또는 이미지 데이터를 학습 데이터로 만드는 전처리(Preprocessing) 과정이나 학습된 모델에 정해진 사이즈에 맞추기 위해서도 이미지 크기를 변환할 수 있다. 우리는 평소 이미지 파일을 켜놓고 늘렸다 줄였다 손쉽게 이미지 모서리를 드래그하여 변형을 시킬 수 있었다. 이 과정을 위해 예를 들어본다.
우선 새 옷을 구매하였다고 가정해보자. 만약 새로 산 바지가 좀 작다고 양 끝을 잡고 당겨 제멋대로 늘려버리면 이 바지의 형태가 제대로 잡혀있을까? 그렇지 않을 것이다. 또는 큰 스웨터를 세탁기에 그대로 돌려버리면 옷이 작아질 것이다. 즉, 원본 상태에서 변형이 가해졌고 이로 인해 원형의 모습을 잃게 된다. 예시가 마땅히 떠오르지 않아 스웨터를 세탁기에 돌린 경우를 들었지만 이 경우는 잘 축소된 것으로 볼 수도 있겠다.
이미지 크기 변환
이미지 크기를 변환하는 과정을 resize라 부른다. 이 과정에서 보간법이나 병합법을 통해 원형의 데이터 손실을 줄이는 방법이 필요하다. 그래야 학습 데이터로 쓰던 영상처리를 하던 의미 있는 데이터로 가치를 발휘할 수 있기 때문이다. 이미지를 확대하는 경우에는 픽셀에 대한 보간법(Interpolation), 이미지를 축소하는 경우에는 픽셀에 대한 병합법 등을 사용한다.
우리는 OpenCV의 resize() 함수를 이용해 이미지 크기를 조절할 수 있다.
cv2.resize(src, dsize, dst=None, fx=None, fy=None, interpolation=None)
- src: input image
- dsize: 결과 영상 크기. (w, h) 튜플. (0, 0)이면 fx와 fy 값을 이용하여 결정.
- dst: output image
- fx, fy: x와 y방향 스케일 비율(scale factor). (dsize 값이 0일 때 유효)
- interpolation: 보간법. default = cv2.INTER_LINEAR
dsize가 (0, 0)이면 스케일 비율로 출력 영상을 결정할 수 있다. 그러므로 dsize 나 fx, fy 둘 중 하나는 꼭 명시해야 한다.
절대 크기와 상대 크기
위 resize 함수를 이용하여 이미지 크기를 조절하는 2가지 방법이 존재한다.
- 절대 크기
e.g. 임의의 크기(640×480이나 123×456 등의 이미지 크기)로 변환 - 상대 크기
이때, 입력 이미지의 크기와 비례하도록 너비와 높이가 계산됨
먼저 절대 크기로 이미지를 변환하는 방식이다. 절대 크기는 튜플(Tuple) 형식으로 (너비, 높이)의 값을 할당해 사용한다. 절대 크기는 아래와 같은 수식을 통해 계산된다.
$$ dsize.width = round(fx*src.cols) $$
$$ dsize.height = round(fy*src.rows) $$
cols와 rows는 openCV를 이용해 이미지를 load한 후 shape를 통해 반환되는 튜플을 통해 알 수 있다. 이 튜플은 (y, x, channel)와 같은 순서로 반환되며 여기서 y와 x가 각각 cols와 rows와 매칭되는 것으로 이해하면 된다.
다음은 상대 크기이다. 상대 크기는 절대 크기에 (0, 0)을 할당한 다음, 상대 크기의 값을 할당해 사용한다. 절대 크기에 (0, 0)을 할당하는 이유로는 fx와 fy에서 계산된 크기가 dsize에 할당되기 때문이다. 따라서 상대 크기로 이미지를 변경하기 위해서는 절대 크기에 0의 값을 할당해 사용한다. 상대 크기는 아래와 같은 수식을 통해 계산된다.
$$ fx = dsize.width/src.cols $$
$$ fy = dsize.height/src.rows $$
여기서 cols와 rows도 절대 크기에서와 동일한 방식으로 알 수 있다.
위와 같은 방법들을 이용해 이미지를 확대하는 과정인 Upsampling과 이미지를 축소하는 과정인 Downsampling을 수행할 수 있다.
보간법(Interpolation)
또한 OpenCV는 아래와 같은 보간법(Interpolation)을 제공한다.
- INTER_NEAREST: a nearest-neighbor interpolation
- 가장 빠르지만 퀄리티가 많이 떨어져 잘 쓰이지 않음 - INTER_LINEAR: a bilinear interpolation (used by default)
- 4개의 픽셀을 이용
- 효율성이 가장 좋음
- 속도도 빠르고 퀄리티도 적당
- 확대(Upsampling)에 주로 사용 - INTER_AREA: resampling using pixel area relation. It may be a preferred method for image decimation, as it gives moire’-free results. But when the image is zoomed, it is similar to the INTER_NEAREST method.
- 영역적인 정보를 추출해서 결과 영상을 세팅
- 축소(Downsampling)에 주로 사용 - INTER_CUBIC: a bicubic interpolation over 4x4 pixel neighborhood
- 16개의 픽셀을 이용
- cv2.INTER_LINEAR 보다 느리지만 퀄리티는 더 좋음
- 확대(Upsampling)에 주로 사용 - INTER_LANCZOS4: a Lanczos interpolation over 8x8 pixel neighborhood
- 64개의 픽셀을 이용
- 좀 더 복잡해서 오래 걸리지만 퀄리티는 좋음
일반적으로 쌍 선형 보간법(INTER_LINEAR)이 가장 많이 사용된다. 좀 더 세부적으로 나눴을 때, 이미지 확대(Upsampling)의 경우 바이큐빅 보간법(INTER_CUBIC) 또는 쌍 선형 보간법(INTER_LINEAR)을 가장 많이 사용하고, 이미지 축소(Downsampling)는 영역 보간법(INTER_AREA)을 가장 많이 사용한다. 다만 영역 보간법을 이용해 이미지를 확대하는 경우 이웃 보간법과 비슷한 결과를 반환한다.
Upsampling
아래는 각 보간법들을 적용하여 이미지의 크기 변환을 수행하는 python 소스 코드이다. 먼저 이미지를 확대(Upsampling)하는 경우이다.
import cv2
src = cv2.imread('brokenEgg.jpeg')
y, x, c = src.shape
print("x: " + str(x) + ", y: " + str(y) + ", channel: " + str(c))
# x: 837, y: 911, channel: 3
INTER_NEAREST = cv2.resize(src, (1920, 1280), interpolation=cv2.INTER_NEAREST)
INTER_LINEAR = cv2.resize(src, (1920, 1280), interpolation=cv2.INTER_LINEAR)
INTER_AREA = cv2.resize(src, (1920, 1280), interpolation=cv2.INTER_AREA)
INTER_CUBIC = cv2.resize(src, (1920, 1280), interpolation=cv2.INTER_CUBIC)
INTER_LANCZOS4 = cv2.resize(src, (1920, 1280), interpolation=cv2.INTER_LANCZOS4)
cv2.imshow('src', src[int(500/(1920/y)):int(900/(1920/y)), int(400/(1280/x)):int(800/(1280/x))])
cv2.imshow('INTER_NEAREST', INTER_NEAREST[500:900, 400:800])
cv2.imshow('INTER_LINEAR', INTER_LINEAR[500:900, 400:800])
cv2.imshow('INTER_AREA', INTER_AREA[500:900, 400:800])
cv2.imshow('INTER_CUBIC', INTER_CUBIC[500:900, 400:800])
cv2.imshow('INTER_LANCZOS4', INTER_LANCZOS4[500:900, 400:800])
cv2.waitKey()
cv2.destroyAllWindows()
캡쳐 화면에서 잘 보이지는 않지만 실제로 확인하였을 때 NEAREST와LINEAR 간의 화질 차이는 꽤 크다. 나머지 보간법은 LINEAR와 큰 차이가 없는 것처럼 보인다. 그러므로 속도도 빠르고 퀄리티도 적당한 LINEAR을 많이 사용한다.
Downsampling
다음은 이미지 축소(Downsampling)이다.
import cv2
src = cv2.imread('brokenEgg.jpeg')
y, x, c = src.shape
print("x: " + str(x) + ", y: " + str(y) + ", channel: " + str(c))
# x: 837, y: 911, channel: 3
INTER_NEAREST = cv2.resize(src, (380, 480), interpolation=cv2.INTER_NEAREST)
INTER_LINEAR = cv2.resize(src, (380, 480), interpolation=cv2.INTER_LINEAR)
INTER_AREA = cv2.resize(src, (380, 480), interpolation=cv2.INTER_AREA)
INTER_CUBIC = cv2.resize(src, (380, 480), interpolation=cv2.INTER_CUBIC)
INTER_LANCZOS4 = cv2.resize(src, (380, 480), interpolation=cv2.INTER_LANCZOS4)
cv2.imshow('INTER_NEAREST', INTER_NEAREST)
cv2.imshow('INTER_LINEAR', INTER_LINEAR)
cv2.imshow('INTER_AREA', INTER_AREA)
cv2.imshow('INTER_CUBIC', INTER_CUBIC)
cv2.imshow('INTER_LANCZOS4', INTER_LANCZOS4)
cv2.waitKey()
cv2.destroyAllWindows()
영상 축소 시 디테일이 사라지는 경우가 발생할 수 있다. 이는 한 픽셀로 구성된 성분이 축소를 하게 되면서 사라지는 경우가 존재하기 때문이다. 아래 그림은 그 예이다.
위 그림과 같이 단순히 INTER_LINEAR만으로 축소한 영상은 선이 사라지는 결과를 얻게될 수 있다. 따라서 입력 영상을 부드럽게 필터링한 후 이미지를 축소하거나 영역 보간법인 INTER_AREA 플래그를 사용해 여러 번 축소를 반복하는 것이 바람직하다.
참고 자료
https://076923.github.io/posts/Python-opencv-8/
https://deep-learning-study.tistory.com/185
소스 코드
https://github.com/sehoon787/Personal_myBlog/blob/main/Data%20Science/CV/Resize.py