Python/openCV

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

code2772 2023. 3. 9. 11:08
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과 동일하게 평평하게 만들기

 

반응형