소스 뷰어
어파인 변환(행렬 연산을 통한 기학학 변환)¶
- 어파인 변환은 변환 전과 변환 후의 두 어파인 공간(어파인 공간은 유클리드 공간의 어파인 기하학적 성징들을 일반환해서 만들어지는 구조) 사이의 공선점(colinear point: 한 직선 상에 있는 점들을 뜻함)을 보존하는 변환이다.
- 따라서 변환전에 직서은 변환 후에도 그대로 직선이며, 그 거리으 비율도 유지된다. 또한 변환전에 평행선도 변환 후에 평행선이 된다.
- 어파인 변환을 수행하는 방법은 크게 두가지가 있다. 하나는 회전 각도, 크기 변경 비율, 평행 이동의 정도를 지정해서 각가 변환 행렬을 구성한다. 그리고 각 변환 행렬을 행렬 곱으로 구성하면 하나의 변환 행렬을 만들 수 있다. 각 행렬을 곱하는 순서는 변환하고자는 방식에 따라서 달라질 수 있다. 이대 2x3 크기의 어파인 행렬로 구성하면 행렬의 곱을 계산할 수 없기 때문에 3x3 크기의 행렬로 구성하여 행렬 곱승 수행한다.
- OpenCV에서도 어파인 변환을 수행할 수 있는 cv2.warpAffine() 함수를 제공한다. 이 함수는 지정된 어파인 변환 행렬을 적용하면 입력 영상에 어파인 변환을 수행한 모적 영상을 반환한다. 또한. 어파인 변환 행렬을 만드는 함수로는 cv2.getAffineTransform()과 cv2.getRotationMatrix2D () 가 있다.
- cv2.getAffineTransform()은 변환 전의 좌표 3개와 변환 후의 좌표 3개를 지정하면 해당 변환을 수행해 줄 수 있는 어파인 행렬을 반환한다. cv2.getRotationMatrix2D()는 회전 변환과 크기 변경을 수행하는 어파인 행렬을 반환한다. 여기서 회전의 방향은 양수 일때 반시계 방향으로 회전하는 행렬을 반환한다. 이것은 영상 좌표에서 직교 좌표계에서의 회전과 같은 방향으로 표현하기 위함이다.
- cv2.warpAffine(src, M, dsize[, dst[, flags[, boardMode[, borderValue]]]]) : 입력 영상에 어파인 변환을 수행해서 반환
- src : 입력 영상
- dst : 반환 영상
- M :어파인 변환 행렬
- dsize : 반환 영상의 크기
- flags : 보간 방법
- borderMode : 경계 지점 방법
- cv2.getAffineTransform(src, dst) : 3개의 좌표쌍을 입력하면 어피안 변환 행렬을 반환
- src : 입력 영상 좌표 3개 (행렬로 구성)
- dst : 반환 영상 좌표 3개 (행렬로 구성)
- cv2.getRotationMatrix2D(center, angle, scale) : 회전 변환과 크기 변경을 수행할 수 있는 어파인 행렬을 반환
- center : 회전의 중심점
- angle : 회전 각도, 양수 각도가 반시계 방향 회전 수행
- scale : 변경할 크기
- cv2.invertAffineTransform(M [. iM]) : 어파인 변환행렬의 역 행렬을 반환
- M : 어파인 변환 행렬
- iM : 어파인 역변환 행렬
import numpy as np, cv2
def bilinear_value(img, pt):
x, y = np.int32(pt)
if y >= img.shape[0]-1: y = y - 1
if x >= img.shape[1]-1: x = x - 1
P1, P2, P3, P4 = np.float32(img[y:y+2,x:x+2].flatten())
alpha, beta = pt[1] - y, pt[0] - x # 거리 비율
M1 = P1 + alpha * (P3 - P1) # 1차 보간
M2 = P2 + alpha * (P4 - P2)
P = M1 + beta * (M2 - M1) # 2차 보간
return np.clip(P, 0, 255) # 화소값 saturation후 반환
def contain(p, shape): # 좌표(y,x)가 범위내 인지 검사
return 0<= p[0] < shape[0] and 0<= p[1] < shape[1]
# 수행시간 체크 함수
stime = 0
def ck_time(mode = 0 , msg = ""):
global stime
if (mode ==0 ):
stime = time.perf_counter()
elif (mode==1):
etime = time.perf_counter()
elapsed = (etime - stime)
print("수행시간 = %.5f sec" % elapsed) # 초 단위 경과 시간
elif (mode == 2):
etime = time.perf_counter()
return (etime - stime)
elif (mode== 3 ):
etime = time.perf_counter()
elapsed = (etime - stime)
print("%s = %.5f sec" %(msg, elapsed)) # 초 단위 경과 시간
어파인 변환 수행 함수¶
def affine_transform(img, mat):
rows, cols = img.shape[:2]
inv_mat = cv2.invertAffineTransform(mat) # 어파인 변환의 역행렬
## 리스트 생성 방식
pts = [np.dot(inv_mat, (j, i, 1)) for i in range(rows) for j in range(cols)]
dst = [bilinear_value(img, p) if contain(p, size) else 0 for p in pts]
dst = np.reshape(dst, (rows, cols)).astype('uint8') # 1차원 -> 2차원
## 반복문 방식
# dst = np.zeros(img.shape, img.dtype) # 목적 영상 생성
# for i in range(rows): # 목적 영상 순회- 역방향 사상
# for j in range(cols):
# pt = np.dot(inv_mat, (j, i, 1)) # 행렬 내적 계산
# if contain(pt, size): dst[i, j] = bilinear_value(img, pt) # 화소 양선형 보간
return dst
image = cv2.imread('img/affine.jpg', cv2.IMREAD_GRAYSCALE)
if image is None: raise Exception("영상 파일을 읽기 에러")
center = (200, 200) # 회전 변환 기준 좌표
angle, scale = 30, 1 # 회전 각도, 크기 지정 - 크기 변경은 안 함
size = image.shape[::-1] # 영상크기는 행렬 행태의 역순
pt1 = np.array([( 30, 70),(20, 240), (300, 110)], np.float32)
pt2 = np.array([(120, 20),(10, 180), (280, 260)], np.float32)
aff_mat = cv2.getAffineTransform(pt1, pt2) # 3개 좌표 쌍으로 어파인 행렬 생성
rot_mat = cv2.getRotationMatrix2D(center, angle, scale) # 회전 변환을 위한 어파인 행렬
dst1 = affine_transform(image, aff_mat) # 어파인 변환 수행
dst2 = affine_transform(image, rot_mat) # 회전 변환 수행
dst3 = cv2.warpAffine(image, aff_mat, size, cv2.INTER_LINEAR)
dst4 = cv2.warpAffine(image, rot_mat, size, cv2.INTER_LINEAR)
image = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR)
dst1 = cv2.cvtColor(dst1, cv2.COLOR_GRAY2BGR )
dst3 = cv2.cvtColor(dst3, cv2.COLOR_GRAY2BGR )
for i in range(len(pt1)):
cv2.circle(image, tuple(pt1[i].astype(int)), 3, (0, 0, 255), 2)
cv2.circle(dst1 , tuple(pt2[i].astype(int)), 3, (0, 0, 255), 2)
cv2.circle(dst3 , tuple(pt2[i].astype(int)), 3, (0, 0, 255), 2)
cv2.imshow("image", image)
cv2.imshow("dst1_affine", dst1); cv2.imshow("dst2_affine_rotate", dst2)
cv2.imshow("dst3_OpenCV_affine", dst3); cv2.imshow("dst4_OpenCV_affine_rotate", dst4)
cv2.waitKey(0)