영상의 평행 이동, 확대 및 축소, 회전 등의 조합으로 만들 수 있는 기하학적 변환(geometric transform)을 나타낸다
기하학적 변환 :
영상을 구성하는 픽셀의 배치 구조를 변경함으로써 전체 영상의 모양을 바꾸는 작업
픽셀값은 그대로 유지하면서 위치를 변경하는 작업
영상을 평행 이동시키거나 회전, 크기 변환 등을 통해 만들 수 있는 변환을 통칭한다
영상을 한쪽 방향으로 밀어서 만든 것 같은 전단 변환도 어파인 변환에 포함
직선은 그대로 직선으로 나타나고, 직선간의 길이 비율과 평행 관계가 그대로 유지된다
직사각형 형태의 영상은 어파인 변환에 의해 평행사변형에 해당하는 모양으로 변경된다
여섯개의 파라미터가 어파인 변환을 결정한다
여섯개의 파라미터로 구성된 2X3 행렬을 어파인 변환 행렬(affine transformation matrix)라고한다
즉, 어파인 변환은 2X3 실수형 행렬 하나로 표현 할 수 있다
최소 세 점의 이동 관계를 알아야한다
점 하나의 이동 관계로부터 x좌표와 y좌표에 대한 변환 수식 두 개를 얻을 수 있고,
점 세개의 이동관계로부터 총 여섯개의 방정식을 구할 수 있다
⇒ 점 세개의 이동관계를 알고있다면, 여섯개의 원소로 정의되는 어파인 변환 행렬을 구할 수 있다
def affine_transform():
src = cv2.imread('tekapo.bmp')
if src is None:
print('Image load failed!')
return
rows = src.shape[0]
cols = src.shape[1]
src_pts = np.array([[0, 0],
[cols - 1, 0],
[cols - 1, rows - 1]]).astype(np.float32)
dst_pts = np.array([[50, 50],
[cols - 100, 100],
[cols - 50, rows - 50]]).astype(np.float32)
affine_mat = cv2.getAffineTransform(src_pts, dst_pts)
dst = cv2.warpAffine(src, affine_mat, (0, 0))
cv2.imshow('src', src)
cv2.imshow('dst', dst)
cv2.waitKey()
cv2.destroyAllWindows()
cv2.getAffineTransform(src, dst)
src : 3개의 원본 좌표점
dst : 3개의 결과 좌표점
2X3 어파인 변환 행렬. CV_64FC1
(64 : bit단위로써 64bit
U : unsigned의 약자 (U : unsinged, S : Signed, F : Floating)
C1 : Channel의 약자, 1채널을 의미한다(1채널인 경우 C1을 생략 가능하여 CV_8CUx1 = CV_8U 와 같은 값을 지닌다)
cv2.wrapAffine(src, M, dsize, dst=None, flags=None, borderMode=None, borderValue=None)
src : 입력 영상
M : 2X3 어파인 변환 행렬
dsize : 결과 영상 크기. (w,h)튜플. (0,0)이면 src와 같은 크기로 설정
dst : 출력 영상
flags : 보간법
borderMode : 가장자리 픽셀 확장 방식. 기본값 cv2.BORDER_CONSTANT
borderValue : cv2.BORDER_CONSTANT일때, 사용할 상수 값. 기본값은 0(검정색). 이동 변환을 했을 때 생기는 빈공간을 어떤 색으로 채울것인지
영상을 가로 또는 세로 방향으로 일정 크기만큼 이동시키는 연산을 의미
시프트(shift) 연산이라고도 한다
2X3 실수 행렬 M을 만들고, 이를 wrapAffine() 함수 인자로 전달한다
def affine_translation():
src = cv2.imread('tekapo.bmp')
if src is None:
print('Image load failed!')
return
affine_mat = np.array([[1, 0, 150],
[0, 1, 100]]).astype(np.float32)
dst = cv2.warpAffine(src, affine_mat, (0, 0))
cv2.imshow('src', src)
cv2.imshow('dst', dst)
cv2.waitKey()
cv2.destroyAllWindows()
직사각형 형태의 영상을 한쪽 방향으로 밀어서 평행사변형 모양으로 변형되는 변환
층밀림 변환이라고도 한다
전단 변환은 영상의 픽셀을 가로 방향 또는 세로 방향으로 이동하지만, 픽셀이 어느 위치에 있는가에 따라 이동 정도가 달라진다
def affine_shear():
src = cv2.imread('tekapo.bmp')
if src is None:
print('Image load failed!')
return
rows = src.shape[0]
cols = src.shape[1]
mx = 0.3
affine_mat = np.array([[1, mx, 0],
[0, 1, 0]]).astype(np.float32)
dst = cv2.warpAffine(src, affine_mat, (int(cols + rows * mx), rows))
cv2.imshow('src', src)
cv2.imshow('dst', dst)
cv2.waitKey()
cv2.destroyAllWindows()
영상의 전체적인 크기를 확대 또는 축소하는 변환
몇몇 영상 인식 시스템은 정해진 크기의 영상만을 입력으로 받기때문에 영상을 해당 크기에 맞게 변경하여 입력으로 전달해야한다
또는
복잡한 알고리즘을 수행하기에 앞서 연산 시간을 단축하기 위하여 입력 영상의 크기를 줄여서 사용하는 경우도 있다
어파인 변환 행렬을 생성하고 wrapAffine() 함수를 이용하면 영상의 크기 변환을 수행 할 수 있다
하지만 실제 영상처리 시스템에서 매우 빈번하게 사용되므로, resize()라는 함수를 이용한다
def affine_scale():
src = cv2.imread('rose.bmp')
if src is None:
print('Image load failed!')
return
dst1 = cv2.resize(src, (0, 0), fx=4, fy=4, interpolation=cv2.INTER_NEAREST)
dst2 = cv2.resize(src, (1920, 1280))
dst3 = cv2.resize(src, (1920, 1280), interpolation=cv2.INTER_CUBIC)
dst4 = cv2.resize(src, (1920, 1280), interpolation=cv2.INTER_LANCZOS4)
cv2.imshow('src', src)
cv2.imshow('dst1', dst1[400:800, 500:900])
cv2.imshow('dst2', dst2[400:800, 500:900])
cv2.imshow('dst3', dst3[400:800, 500:900])
cv2.imshow('dst4', dst4[400:800, 500:900])
cv2.waitKey()
cv2.destroyAllWindows()
cv2.resize(src, dsize, dst=None, fx=None, fy=None, interpolation=None)
src : 입력 영상
dsize : 결과영상 크기. (w,h)튜플. (0,0)이면 fx와 fy값을 이용하여 결정
dst : 출력 영상
fx, fy : x와 y방향 스케일 비율(scale factor). (dsize 값이 0일때 유효)
interpolation : 보간법 지정. 기본값은 cv2.INTER_LINEAR
resize는 출력 영상 크기를 픽셀 단위로 설정 할 수 있다
dsize가 (0,0)이면 스케일 비율로 출력 영상을 결정 할 수 있다
그러므로 dsize나 fx, fy 둘중 하나는 꼭 명시해야한다
특정 좌표를 기준으로 영상을 원하는 각도만큼 회전하는 변환
angle 이 음수면 시계방향으로 회전
양수면 반시계 방향으로 회전
직사각형 형태의 영상을 회전면 입력 영상의 일부가 결과 영상에 나타나지 않을 수 이씅며, 입력 영상의 일부가 잘리지 않게 영상을 회전하려면 결과 영상의 크기를 더 크게 설정하고 회전과 이동 변환을 함께 고려해야한다
def affine_rotation():
src = cv2.imread('tekapo.bmp')
if src is None:
print('Image load failed!')
return
cp = (src.shape[1] / 2, src.shape[0] / 2)
affine_mat = cv2.getRotationMatrix2D(cp, 20, 1)
dst = cv2.warpAffine(src, affine_mat, (0, 0))
cv2.imshow('src', src)
cv2.imshow('dst', dst)
cv2.waitKey()
cv2.destroyAllWindows()
cv2.getRotationMatrix2D(center, angle, scale) → retval
center : 회전 중심 좌표. (x,y)튜플
angle : (반시계 방향) 회전 각도(degree). 음수는 시계방향
scale : 추가적인 확대 비율
retcal : 2X3 어파인 변환 행렬
rotate(img, flag)
img : 회전 할 이미지
flag
cv2.ROTATE_90_CLOCKWISE : 시계 방향으로 90도 회전
cv2.ROTATE_90_COUNTERCLOCKWISE : 반시계 방향으로 90도 회전 (=시계방향으로 270도 회전)
cv2.ROTATE_180 : 180도 회전
대칭 변환은 입력 영상과 같은 크기의 결과 영상을 생성하며, 입력 영상의 픽셀과 결과 영상의 픽셀이 일대일로 대응되므로 보간법이 필요하지 않다
flip() 함수
영상을 가로 방향, 세로 방향 또는 가로와 세로 양 방향에 대해 대칭 변환한 영상을 생성한다
좌우로 대칭변환하려면 flipCode =1
상하 대칭 변환 : 0
상하 대칭과 좌우 대칭을 모두 수행 : -1
상하 대칭과 좌우 대칭을 모두 수행한 결과 영상은 입력 영상을 180도 회전한 결과와 같다
def affine_flip():
src = cv2.imread('eastsea.bmp')
if src is None:
print('Image load failed!')
return
cv2.imshow('src', src)
for flip_code in [1, 0, -1]:
dst = cv2.flip(src, flip_code)
desc = 'flipCode: %d' % flip_code
cv2.putText(dst, desc, (10, 30), cv2.FONT_HERSHEY_SIMPLEX,
1.0, (255, 0, 0), 1, cv2.LINE_AA)
cv2.imshow('dst', dst)
cv2.waitKey()
cv2.destroyAllWindows()
cv2.flip(src, flipCode, dst=None)
scr : 입력 영상
flipCode : 대칭 방향 지정
dst : 출력 영상
어파인 변환보다 자유도가 높다
직사각형 형태의 영상을 임의의 블록 사각형 형태로 변경할 수 있는 변환
투시 변환에 의해 원본 영상에 있던 직선은 결과 영상에서 그대로 직선성이 유지되지만, 두 직선의 평행 관계는 깨어질 수 있다
import sys
import numpy as np
import cv2
def on_mouse(event, x, y, flags, param):
global cnt, src_pts
if event == cv2.EVENT_LBUTTONDOWN:
if cnt < 4:
src_pts[cnt, :] = np.array([x, y]).astype(np.float32)
cnt += 1
cv2.circle(src, (x, y), 5, (0, 0, 255), -1)
cv2.imshow('src', src)
if cnt == 4:
w = 200
h = 300
dst_pts = np.array([[0, 0],
[w - 1, 0],
[w - 1, h - 1],
[0, h - 1]]).astype(np.float32)
pers_mat = cv2.getPerspectiveTransform(src_pts, dst_pts)
dst = cv2.warpPerspective(src, pers_mat, (w, h))
cv2.imshow('dst', dst)
cnt = 0
src_pts = np.zeros([4, 2], dtype=np.float32)
src = cv2.imread('card.bmp')
if src is None:
print('Image load failed!')
sys.exit()
cv2.namedWindow('src')
cv2.setMouseCallback('src', on_mouse)
cv2.imshow('src', src)
cv2.waitKey(0)
cv2.destroyAllWindows()
cv2.getAffineTransform(src, dst)
src : 3개의 원본 좌표점
dst : 3개의 결과 좌표점
넘파이 행렬로 입력해줘야함
cv2.getPerspectiveTransform(src, dst, solveMethod=None) → retval
src : 4개의 원본 좌표점
dst : 4개의 결과 좌표점
retval : 투시 변환 행렬 (3X3 또는 4X4)
cv2.wrapPerspective(src, M, dsize, dst=None, flags=None, borderMode=None, borderValue=None)
src : 입력영상
M : 3X3 투시 변환 행렬
dsize : 결과 영상 크기. (w,h)튜플. (0,0)이면 src와 같은 크기로 설정
dst : 출력영상
flags : 보간법. 기본 값 cv2.INTER_LINEAR
borderMode : 가장자리 픽셀 확장 방식. 기본값은 cv2.BORDER_CONSTANT
borderValue : cv2.BORDER_CONSTANT 일때, 사용할 상수 값 .기본값은 0
창에서 사용자가 특정 카드의 네 모서리를 마우스로 클릭하면 직사각형 형태로 투시 변환된 결과 영상이 dst에 나온다
이때 마우스를 이용한 좌표 선택은 카드의 좌측 상단 모서리 점부터 시작하여 시계 방향순서대로 선택해야하며, 마우스로 클릭한 위치는 붉은색원으로 표시됨