본문 바로가기
Python/openCV

Python openCV (어파인, 크기변환, 영상회전, 보간법, 투시변환)

by code2772 2023. 3. 9.

[ 목차 ]

    728x90
    반응형
    영상의 기하학적 변환
    - 영상의 밝기, 명암비 조절, 필터링 등은 픽셀 위치는 고정한 상태에서 픽셀 값만 변경했지만, 기하학적 변환이라는 것은 픽셀 값은 그대로 유지하면서 위치를 변경하는 작업
    - 영상을 구성하는 픽셀의 배치 구조를 변경함으로 전체 영상의 모양을 바꾸는 작업
    
    ✔ 어파인 변환(affine transformation)
    영상을 평행 이동시키거나 회전, 크기 변환 등을 통해 만들 수 있는 변환을 통칭
        영상에서 (x,y) 좌표의 픽셀을 결과 영상의 (x' , y') 좌표로 변환하는 방법
            { x' = f1(x, y), y' = f2(x, y) }
    
            { x' = f1(x, y) = ax + by + c, , y' = f2(x, y) = dx + ey + f }
    
            위 수식을 행렬을 이용하여 아래와 같이 표현할 수 있음
            [[x'], [y']] = [[a, b], [d, e]] * [[x], [y]] + [[c], [f]]
    
            위 수식을 행렬을 이용하여 아래와 같이 용약할 수 있음
            [[x'], [y']] = [[a, b, c], [d, e, f]] * [[x], [y], [1]]
                            ---------   --------
                            # 2 by 3 행렬로(점 3개) 각 꼭지점의 위치를 유추할 수 있다.
    
    > 여섯개의 파라미터로 구성된 2*3 행렬 [[a, b, c], [d, e, f]]를 어파인 변환 행렬이라고 부름
    
    어파인 변환 행렬을 계산
    cv2.getAffineTransform(영상, 세 점의 좌표)
    
    어파인 변환 행렬을 가지고 있을 때 영상을 어파인 변환한 결과 영상 생성
    cv2.warpAffine(영상, 어파인 변환 행렬, 결과 영상 크기)

    ✔ 어파인 예제

    # 어파인 변환 예제
    
    import cv2
    import numpy as np
    
    src = cv2.imread('dog.jpg')
    
    # 어파인 행렬을 일단 만들어 보자 200(x축), 100(y축) 만큼 이동할 것이다
    
    # [1,0,tx],[0,1,ty]
    # 음수 tx값은 이미지를 왼쪽으로 이동, 양수 tx값은 이미지를 오른쪽으로 이동
    # 음수 ty값은 이미지를 위으로 이동, 양수 ty값은 이미지를 아래로 이동
    aff = np.array([[1,0,200],[0,1,100]], dtype=np.float32)
    
    #이미 어파인 행렬이 있기 때문에 aff를 넣어주었다.
    dst = cv2.warpAffine(src, aff, (0,0))
    
    cv2.imshow('src', src)
    cv2.imshow('dst', dst)
    cv2.waitKey()

    어파인 예제 결과

    ✔ 크기 변환(Scale)
    영상의 크기를 원본 영상보다 크게 또는 작게 만드는 변환
    cv2.resize(영상, 결과 영상 크기, x와 y방향 스케일 비율, 보간법)
    ✔ 보간법 :  기존에 알고 있는 특정 지점이나 지역의 속성값을 이용하여 알고자 하는 지점 또는 지역의 속성값을 찾아내는 방법이다
    cv2.INTER_NEAREST : 최근방 이웃 보간법. 속도가 가장 빠르지만 퀄리티가 많이 떨어짐.
                        openCV 문서에서는 다운 샘플링일 경우 cv2.INTER_NEAREST를 사용하기 권장하고 있음
    cv2.INTER_LINEAR : 양선형 보간법. 4새 픽셀을 이용하며 효율성이 가장 좋음
    cv2.INTER_CUBIC : 3차 회선 보간법. 16개의 픽셀을 이용. INTER_LINEAR 보다 느리지만 퀄리티는 가장 좋음
    
    cv2.INTER_AREA : 영상 다운 샘플링시 효과적. 영역적인 정보를 추출해서 결과 영상을 세팅

    ✔ 크기변환 예제

    import cv2
    
    src = cv2.imread('rose.bmp')
    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)
    
    cv2.imshow('src', src)
    cv2.imshow('dst1', dst1[500:900, 400:800]) # 처리량은 빠르지만 질이 좋지 않다
    cv2.imshow('dst2', dst2[500:900, 400:800]) # 기본값 : INTER_LINEAR
    cv2.imshow('dst3', dst3[500:900, 400:800])
    # 큐빅 : 사람눈에 큰 차이는 없지만 계산은 복잡하고 더 좋은 퀄리티를 보장한다.
    cv2.waitKey()

    ✔ 회전

    import cv2
    
    src = cv2.imread('dog.jpg')
    
    cp = (src.shape[1] / 2, src.shape[0] / 2) # 센터값(중심 좌표) 구하기
    
    rot = cv2.getRotationMatrix2D(cp, 20, 0.5)
    print(rot)
    dst = cv2.warpAffine(src, rot, (0,0))
    cv2.imshow('src', src)
    cv2.imshow('dst', dst)
    cv2.waitKey()

    회전 예제

    ✔ 영상의 회전(rotation)
    - 영상을 특정 각도만큼 회전시키는 변환(반시계 방향)
    cv2.getRotationMatrix2D(중심 좌표, 회전 각도, 확대 비율) -> affine 행렬값이 나온다.
    회전 각도: 반시계 방향. 음수는 시계방향

    ✔ 투시변환 예제 및 설명들

    ✔ 투시 변환 : 4개의 꼭지점을 모두 알아야한다.
    - 직사각형 형태의 영상을 임의의 블록 사각형 형태로 변경할 수 있는 변환
    - 원본 영상에 있던 직선은 결과 영상에서 그대로 유지되지 않고 평행 관계가 깨질 수 있음
    - 투시 변환은 보통 3*3 크기의 실수 행렬러 표현(8개의 파라미터로 표현할 수 있지만, 좌표 계산의 편의상 9개의 원소룰 갖는 행렬을 사용)
    
    # 평행하게 나올지 꺽인 상태에서 나올지에 따라 2*3, 3*3인지만 알 수 있으면 된다.
    
    점 4개의 이동 전, 이동 후 좌표를 입력하면 투시 변환 행렬을 반환
    cv2.getPerspectiveTransform(영상, 4개의 결과 좌표점)
    
    3*3 투시 변환 행렬을 가지고 있을 때 영상의 투시 변환 결과 영상을 생성
    cv2.warpPerspective(영상, 투시 변환 행렬, 결과 영상 크기)
    

    ✔ 예제 1

    import numpy as np
    import cv2
    
    src = cv2.imread('pic.jpg')
    w, h = 600, 400
    
    srcQuad = np.array([[370, 173],[1223,157],[1422,844],[211,865]], np.float32)
    dstQuad = np.array([[0, 0],[w, 0],[w, h],[0, h]], np.float32)
    
    
    pers = cv2.getPerspectiveTransform(srcQuad, dstQuad)
    dst = cv2.warpPerspective(src, pers,(w,h))
    
    cv2.imshow('src', src)
    cv2.imshow('dst', dst)
    
    cv2.waitKey()

    결과

    그림판을 사용하여 좌표를 구하였으며 비스듬한 사진을 평평하게 만들어주기

     

    ✔ 예제 2

    import sys
    import numpy as np
    import cv2
    
    
    def drawROI(img, corners):
        cpy = img.copy()  # 카피 이미지를 만든다.
    
        c1 = (0, 0, 255)  # 점 색상
        c2 = (255, 0, 0)  # 선 색상
    
        for pt in corners:  # 4바퀴 돌리기
            # circle : 원을 만들자
            cv2.circle(cpy, tuple(pt.astype(int)), 25, c1, -1, cv2.LINE_AA)
            # 25(반지름), c1 점 색상, -1 배경을 지우고 , cv2.LINE_AA 선 타입(직선)
    
        # line : 라인을 만들자
        cv2.line(cpy, tuple(corners[0].astype(int)), tuple(corners[1].astype(int)), c2, 2, cv2.LINE_AA)
        cv2.line(cpy, tuple(corners[1].astype(int)), tuple(corners[2].astype(int)), c2, 2, cv2.LINE_AA)
        cv2.line(cpy, tuple(corners[2].astype(int)), tuple(corners[3].astype(int)), c2, 2, cv2.LINE_AA)
        cv2.line(cpy, tuple(corners[3].astype(int)), tuple(corners[0].astype(int)), c2, 2, cv2.LINE_AA)
    
        return cpy
    
    
    # 이벤트, 좌표들, 클릭중인지, 데이터가 있으면 넘길것이 있는지
    def onMouse(event, x, y, flags, param):
        global srcQuad, dragSrc, ptOld, src  # 글로벌로 설정을 하영 중복을 배제한다.
    
        if event == cv2.EVENT_LBUTTONDOWN:
            # LBUTTONDOWN : 왼쪽 버튼이 눌리고 있냐??
            for i in range(4):
                # 4개의 좌표가 있기 때문에 4번을 반복한다.
                if cv2.norm(srcQuad[i] - (x, y)) < 25:
                    # 원의 반지름 안에 들어왔냐??
                    # norm 함수 : 정규화 시키는 함수이다.
                    dragSrc[i] = True
                    # 반지름 안에 들어왔으면
                    ptOld = (x, y)
                    break
    
        if event == cv2.EVENT_LBUTTONUP:  # 마우스 버튼이 떨어젔냐??
            for i in range(4):
                dragSrc[i] = False
                # 떨어지니깐 False : 초기화 시키기 위해 사용하는 것이다.
    
        if event == cv2.EVENT_MOUSEMOVE:  # 마우스를 드레그 한다면??
            for i in range(4):
                if dragSrc[i]:  # 드레그 중이라면 (True)
                    dx = x - ptOld[0]  # 0번에 해당하는 x좌표는
                    dy = y - ptOld[1]  # 1번이라는 y좌표
    
                    srcQuad[i] += (dx, dy)  # 더해서 해당 좌표를 만든다.
    
                    cpy = drawROI(src, srcQuad)  # 다시 그려라
                    cv2.imshow('img', cpy)  # 다시 화면을 보여주기
                    ptOld = (x, y)  # 그 값들을 다시 적용하자
                    break
    
    
    # 입력 이미지 불러오기
    src = cv2.imread('namecard.jpg')
    
    h, w = src.shape[:2]
    dh = 500
    # A4용지 크기 : 219*297cm
    dw = round(dh * 297 / 210)
    
    # 모서리 점들의 좌표, 드래그 상태 여부
    srcQuad = np.array([[30, 30], [30, h - 30], [w - 30, h - 30], [w - 30, 30]], np.float32)  # 점
    dstQuad = np.array([[0, 0], [0, dh], [dw, dh], [dw, 0]], np.float32)  # 짜른 이미지
    dragSrc = [False, False, False, False]  # 지금 현재 점이 드래그 중인지 움직이면 True로 변경
    
    # 모서리점, 사각형 그리기(얻어내는 함수를 의미한다)
    disp = drawROI(src, srcQuad)  # 원본에다가 드래그할 위치를 넘겨준다.
    cv2.imshow('img', disp)
    cv2.setMouseCallback('img', onMouse)  # 마우스 이벤트
    while True:
        key = cv2.waitKey()
        if key == 13:  # ENTER 키
            break
        elif key == 27:  # ESC 키
            sys.exit()
    
    if src is None:
        print('Image open failed!')
        sys.exit()
    
    cv2.waitKey()
    # 투시 변환
    pers = cv2.getPerspectiveTransform(srcQuad, dstQuad)
    dst = cv2.warpPerspective(src, pers, (dw, dh), flags=cv2.INTER_CUBIC)  # 확대 시킬거기 때문에 CUBIC을 사용
    
    cv2.imshow('dst', dst)
    cv2.waitKey()
    
    

    비스듬한 사진을 마우스 이벤트로 예제1과 동일하게 평평하게 만들기

     

    반응형