CG에서도 다루었지만, 영상처리 기말범위에도 있기에 한 번 더 정리해보려고 한다.
Transformation은 변환으로 이미지를 축소, 확대 하거나 y축으로 이동하거나, 원점을 중심으로 회전을 하거나하는 등의 변환을 의미한다.
위의 변환이 포함되는 변환으로 행렬식은
로 나타낸다.
이를 컴퓨터 그래픽스에서 Homogeneous Coordinate, 영상처리에선 Affine Matrix라고 부르는 행렬로 나타내면
로 나타낸다.
위의 식을 참고하면 편하다.
Affine Transformation을 해도 기존 이미지와 변환 이미지에서 유지되는 성질이 있다.
평행한 두 선이 있을 때 그 평행성은 변환 후에도 유지 된다는 말이다.
또한 길이 비율도 유지 된다.
하지만 아래는 유지되지 않는 성질이다.
위의 내가 그린 저퀄그림에서도 찾아 볼 수 있다.
선의 길이는 모두 변했으며 선끼리 이루어진 각도 또한 변화하였다.
Affine Transformation과 다른 변환이다.
이 변환은 3차원 공간상에서 원근법적으로 바라볼 때 보이는 모양으로 변환하는 것이다.
으로 총 파라미터가 9개 이다.
양 변에 를 나눠줌으로써, 8개로 줄일 수 있다.
과 이 0이면 Affine Transformation으로 좀 더 포괄적인 표현이 가능하다.
좌표공간을 변환하는 방법이 2가지가 있다.
Forward mapping은 입력 이미지를 변환을 하여 변환 이미지를 만들었을 때, 사용자의 눈에 보이는 화면에 표시될 정점(빨간 x)의 값을 변환 이미지로 부터 interpolation하는 것이다.
그래서 변환 이미지로 부터 화면에 표시될 정점의 intensity를 구하기 위해 픽셀마다 interpolation해야하는 연산량이 많을 수 있다.
Inverse mapping은 사용자의 눈에 보이는 화면에 표시될 정점(빨간 x)을 역변환하여, 원래 이미지에 대입하는 것이다.
원래 이미지에서 화면에 표시될 정점과 대응시켜 interpolation한다.
그래서 Forward mapping보다는 연산 과정이 효율적이다.
Foward mapping은 이미지 자체를 변화시키고, 사용자에 눈에 보일 픽셀을 연산하지만, Inverse mapping은 사용자의 눈에 보일 좌표 값만 역변환하고, 그 픽셀들의 값을 연산하기 때문이다.
요약하자면
Forward Mapping: 원본 이미지의 각 픽셀을 목적 이미지로 매핑. 구현이 단순하지만 빈 공간이 생길 수 있음.
Inverse Mapping: 목적 이미지의 각 픽셀이 원본 이미지에서 어디에서 왔는지 계산. 더 균일한 픽셀 분포를 유지하지만 구현이 더 복잡할 수 있음.
이미지 두개의 특징점을 찾아 연결하는 기술이다.
파노라마같이 사진 여러작을 찍어 가로로 길게 연결할 때도 사용이 된다.
어떠한 분포가 있을 때, 그 분포에서 수학적 식을 대입해 매칭하는 기술이다.
기존은 최소제곱오차의 방법으로 오차를 구하고 이를 제곱해 모든 Summation하여 가장 최소가 되는 함수를 찾는 것이 머신러닝의 기본 방법이다.
하지만, RANSAC은 가장 많은 수의 데이터들로부터 지지를 받는 함수를 찾는 알고리즘이다.
2~4의 과정을 원하는 만큼 반복한다.
import cv2
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.image as img
import math
# RGB image를 display하는 함수
def show_rgb_image(img, title=None):
plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
plt.axis("off")
if title:
plt.title(title)
plt.show()
# Gray image를 display하는 함수
def show_gray_image(img, title=None):
plt.imshow(img, cmap="gray")
plt.axis("off")
if title:
plt.title(title)
plt.show()
matplotlib로 이미지를 출력하기 위해 rgb channel로 출력하는 함수와, grayscale channel로 출력하는 함수를 만든다.
img = cv2.imread('painting.tif')
show_rgb_image(img, "Original Image")
원하는 이미지를 로드하고 출력해본다.
이미지를 이동하고 회전시킬 것이기 때문에, 원본 이미지 자체를 주변이 검은색으로 패딩된 이미지를 사용했다.
패딩을 하지 않으면 위의 사진처럼 잘리기 때문이다.
물론, 회전한 만큼 가로와 세로가 늘어난 값을 계산해 결과 이미지의 크기를 크게해줘도 되지만 귀찮아서이다.
affine transformation은 파라미터가 6개, perspective transformation은 9개(w로 나누면 8개)라고 배웠다.
openCV
에서는 warpAffine()
의 InputArray를 2 x 3(파라미터 6개)
warpPersepective()
의 InputArray를 3 x 3(파라미터 9개)로 줄 수 있다.
def translation(image, horizontal_delta, vertical_delta):
matrix = np.array([[1, 0, vertical_delta],
[0, 1, horizontal_delta]], dtype=np.float32)
result = cv2.warpAffine(image, matrix, None)
return result
def scaling(image, horizontal_scale, vertical_scale):
matrix = np.array([[1.8, 0, 0],
[0, 1.8, 0]], dtype=np.float32)
result = cv2.warpAffine(image, matrix, (0, 0))
result = cv2.warpAffine(image, matrix, None)
return result
def rotation(image, angle):
center_x = image.shape[1] / 2
center_y = image.shape[0] / 2
rad = math.radians(angle) # Angle이 degree로 주어지므로, radian으로 변환
# 행렬곱 시의 차원이 맞도록 하기 위해, T1, R, T2는 3 x 3 (Homogeneous) 행렬을 사용합니다.
# 1. 이미지의 중심을 원점으로 이동시키는 변환 행렬
T1 = np.array([[1, 0, -center_x],
[0, 1, -center_y],
[0, 0, 1]])
# 2. 원점(0, 0)을 중심으로 angle만큼 반시계 회전시키는 변환 행렬
R = np.array([[np.cos(rad), -np.sin(rad), 0],
[np.sin(rad), np.cos(rad), 0],
[0, 0, 1]])
# 3. 회전된 이미지를 다시 원래 위치로 이동시키는 변환 행렬
T2 = np.array([[1, 0, center_x],
[0, 1, center_y],
[0, 0, 1]])
# T2, R, T1 행렬의 행렬곱을 통해 최종 변환 행렬을 계산
final_matrix = np.matmul(T2, np.matmul(R, T1))
# 변환된 이미지를 생성
result = cv2.warpPerspective(image, final_matrix, None)
return result
def shearing(image, horizontal_shear_ratio, vertical_shear_ratio):
matrix = np.array([[1, horizontal_shear_ratio, 0],
[vertical_shear_ratio, 1, 0]], dtype=np.float32)
result = cv2.warpAffine(image, matrix, None)
return result
이를 출력해보면 잘 되는 것을 알 수 있다.
기울어진 좌표축에 놓인 평면의 이미지를 평평하게 만들어 볼 것이다.
원본 이미지는 다음과 같다. 코너를 원활히 찾기 위해서 화면을 4분할하여 Harris Corner Detection
을 수행할 것이다.
# gray_img를 기준으로 corner를 찾은 후,
# Transform은 원래의 color image를 기준으로 수행합니다.
gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 영역 분할
h, w = img.shape[:2]
divisions = [
(0, w//2, 0, h//2),
(w//2, w, 0, h//2),
(0, w//2, h//2, h),
(w//2, w, h//2, h)
]
# 영역 시각화를 위해 분할된 이미지에 경계선 그리기
img_divisions = img.copy()
cv2.line(img_divisions, (w//2, 0), (w//2, h), (255, 0, 0), 3) # 수직선
cv2.line(img_divisions, (0, h//2), (w, h//2), (255, 0, 0), 3) # 수평선
# 이미지 출력
show_rgb_image(img_divisions, "Divided Image")
이미지를 로드하고 화면을 4분할 한다.
# Harris 코너 감지기 초기화
dst = cv2.cornerHarris(gray_img, 2, 3, 0.04)
# 각 영역별로 강도가 가장 강한 지점(corner)를 selected_corners에 append
selected_corners = []
for x1, x2, y1, y2 in divisions:
roi_dst = dst[y1:y2, x1:x2] # 각 영역의 Harris 응답 이미지 추출
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(roi_dst) # 최소 및 최대 응답 값과 위치 찾기
if max_val > 0.01 * dst.max(): # 응답 값이 임계값보다 큰 경우만 코너로 인식
max_loc = (max_loc[0] + x1, max_loc[1] + y1) # 전체 이미지 기준으로 위치 조정
selected_corners.append(max_loc)
print("Selected Corners:", selected_corners)
cornerHarris
의 파라미터는 여러번 시도해본 결과 잘 검출되도록 찾아야한다.
img_selected_corners = img.copy()
for x, y in selected_corners:
cv2.circle(img_selected_corners, (x, y), 30, (255, 0, 0), 10)
show_rgb_image(img_selected_corners, "Selected Corners")
각 코너를 시각화해보면 잘 검출된 것을 확인할 수 있다.
# 변환 전 좌표(src) 설정
src = np.float32(selected_corners)
# 변환 후 좌표(dst) 설정
dst = np.float32([(0, 0), (w, 0), (0, h), (w, h)])
# 퍼스펙티브 변환 행렬 계산
final_matrix = cv2.getPerspectiveTransform(src, dst)
# 퍼스펙티브 변환 수행
result = cv2.warpPerspective(img, final_matrix, (w, h))
show_rgb_image(result, "After Perspective Transformation")
getPerspectiveTrasform()
이라는 함수를 통해 4개의 source 좌표를 최종 4개의 destination 좌표로 변환하는 행렬을 구할 수 있다.
이를 warpPerspective()
에 적용하면...
잘 펴지는 것을 확인할 수 있다.