[OpenCV 4로 배우는 컴퓨터 비전과 머신 러닝] 8장_정리

SUN·2022년 12월 21일
0

[8장 영상의 기하학적 변환]

8.1 어파인 변환

영상의 평행 이동, 확대 및 축소, 회전 등의 조합으로 만들 수 있는 기하학적 변환(geometric transform)을 나타낸다

  • 기하학적 변환 :

    영상을 구성하는 픽셀의 배치 구조를 변경함으로써 전체 영상의 모양을 바꾸는 작업

    픽셀값은 그대로 유지하면서 위치를 변경하는 작업

8.1.1 어파인 변환(Affine transformation)

영상을 평행 이동시키거나 회전, 크기 변환 등을 통해 만들 수 있는 변환을 통칭한다
영상을 한쪽 방향으로 밀어서 만든 것 같은 전단 변환도 어파인 변환에 포함
직선은 그대로 직선으로 나타나고, 직선간의 길이 비율과 평행 관계가 그대로 유지된다
직사각형 형태의 영상은 어파인 변환에 의해 평행사변형에 해당하는 모양으로 변경된다

여섯개의 파라미터가 어파인 변환을 결정한다
여섯개의 파라미터로 구성된 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(검정색). 이동 변환을 했을 때 생기는 빈공간을 어떤 색으로 채울것인지

8.1.2 이동 변환(translation transformation)

영상을 가로 또는 세로 방향으로 일정 크기만큼 이동시키는 연산을 의미
시프트(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()

8.1.3 전단 변환(shear transformation)

직사각형 형태의 영상을 한쪽 방향으로 밀어서 평행사변형 모양으로 변형되는 변환
층밀림 변환이라고도 한다

  • 가로 방향 전단 변환
    값이 증가함에 따라 영상이 조금씩 가로 방향으로 이동

  • 세로 방향 전단 변환
    x좌표 값이 증가함에 따라 영상이 조금씩 세로 방향으로 이동

전단 변환은 영상의 픽셀을 가로 방향 또는 세로 방향으로 이동하지만, 픽셀이 어느 위치에 있는가에 따라 이동 정도가 달라진다

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()

8.1.4 크기 변환(scale transformation)

영상의 전체적인 크기를 확대 또는 축소하는 변환

몇몇 영상 인식 시스템은 정해진 크기의 영상만을 입력으로 받기때문에 영상을 해당 크기에 맞게 변경하여 입력으로 전달해야한다

또는

복잡한 알고리즘을 수행하기에 앞서 연산 시간을 단축하기 위하여 입력 영상의 크기를 줄여서 사용하는 경우도 있다

어파인 변환 행렬을 생성하고 wrapAffine() 함수를 이용하면 영상의 크기 변환을 수행 할 수 있다
하지만 실제 영상처리 시스템에서 매우 빈번하게 사용되므로, resize()라는 함수를 이용한다

  • 보간법(interpolation)
    결과 영상의 픽셀 값을 결정하기 위해 입력 영상에서 주변 픽셀 값을 이용하는 방식

    INTER_NEAREST : 연산 속도 가장 빠르지만, 화질이 좋지 않음
    INTER_LINEAR : 연산 속도 빠르고 화질도 충분히 좋음, resize() 함수 기본값
    INTER_CUBIC : INTER_LINEAR 보다 더 좋은 화질
    INTER_LANCZOS4 : 위와 마찬가지
    INTER_AREA 방법을 사용하면 무아레 현상이 적게 발상하며 화질 면에서 유리하다

  • 무아레(moire)
    카메라로 영상 촬영 시 이미지 센서의 샘플링 주파수 성분과 촬영 대상의 주파수 성분으로 발생하는 간섭현상
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 둘중 하나는 꼭 명시해야한다

8.1.5 회전 변환(ratation transformation)

특정 좌표를 기준으로 영상을 원하는 각도만큼 회전하는 변환

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도 회전

8.1.6 대칭 변환

대칭 변환은 입력 영상과 같은 크기의 결과 영상을 생성하며, 입력 영상의 픽셀과 결과 영상의 픽셀이 일대일로 대응되므로 보간법이 필요하지 않다

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 : 출력 영상

8.2 투시 변환(perspective transform)

어파인 변환보다 자유도가 높다

직사각형 형태의 영상을 임의의 블록 사각형 형태로 변경할 수 있는 변환

투시 변환에 의해 원본 영상에 있던 직선은 결과 영상에서 그대로 직선성이 유지되지만, 두 직선의 평행 관계는 깨어질 수 있다


투시 변환은 직선의 평행 관계가 유지되지 않기 때문에 결과 영상의 형태가 임의의 사각형으로 나타나게 된다 점 하나의 이동 관계로부터 x좌표에 대한 방정식 하나와 y좌표에 대한 방정식 하나를 얻을 수 있으므로, 점 네개의 이동 관계로부터 여덟 개의 방정식을 얻을 수 있다 이 방정식으로부터 투시 변환을 표현하는 파라미터 정보를 계산 할 수 있다
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에 나온다

이때 마우스를 이용한 좌표 선택은 카드의 좌측 상단 모서리 점부터 시작하여 시계 방향순서대로 선택해야하며, 마우스로 클릭한 위치는 붉은색원으로 표시됨

0개의 댓글