728x90

2021/02/22 - [기록 note] - 2021-02-22(OpenCV_4)

 

2021-02-22(OpenCV_4)

오늘은 기하학적 변환을 공부했습니다 좌표계산하는게 고도의 계산력이 필요한건 아니지만 꽤 헷갈렸습니다 상상해 가면서 이해하려고 하니까 시간도 꽤 들었구요 특히, 마지막에 종합실습을

ghdrldud329.tistory.com

 

 

이동변환

### 영상이동변환

import sys
import numpy as np
import cv2


src = cv2.imread('tekapo.bmp') #640*480

if src is None:
    print('Image load failed!')
    sys.exit()

# affine 변환행렬을 먼저 만들어야 한다->변환행렬을 만드는 함수는 getAffineTransform
aff= np.array([[1,0,200],[0,1,100]], dtype=np.float32) #가로 200픽셀, 세로 100픽셀 이동

# warpAffine는 변환행렬을 알고 있는경우, 결과 영상을 보기 위한 함수
dst=cv2.warpAffine(src, aff, (0,0)) #(0.0):입력영상과 동일한 크기 출력

cv2.imshow('src', src)
cv2.imshow('dst', dst)
cv2.waitKey()
cv2.destroyAllWindows()

 

전단 변환

import sys
import numpy as np
import cv2


src = cv2.imread('tekapo.bmp')

if src is None:
    print('Image load failed!')
    sys.exit()
                #0.5: x좌표 
aff = np.array([[1,0.5,0], [0,1,0]], dtype=np.float32)
'''
출력영상 크기를 (w+h*0.5,h)로 수정해야 x방향만큼 밀려나간 이미지를 볼수 있다
밀려나간 x값만큼 출력영상에 더해주면 된다
가로크기가 h*0.5만큼 밀렸다. 여기서 (0,0)으로 출력영상을 찍으면 밀린 이미지는 짤려서 보이지 않는다
입력영상은 정수형태로 있어야 하므로 int로 형변환 시킨다.
'''
h, w = src.shape[:2]

dst=cv2.warpAffine(src, aff,(w + int(h * 0.5), h))




cv2.imshow('src', src)
cv2.imshow('dst', dst)
cv2.waitKey()
cv2.destroyAllWindows()

 

크기변환

### 영상의 크기변환

import sys
import numpy as np
import cv2


src = cv2.imread('rose.bmp') # src.shape=(320, 480) = (세로,가로)

if src is None:
    print('Image load failed!')
    sys.exit()

dst1 = cv2.resize(src, (0, 0), fx=4, fy=4, interpolation=cv2.INTER_NEAREST) #INTER_NEAREST: 가장 저품질
dst2 = cv2.resize(src, (1920, 1280))  # cv2.INTER_LINEAR, 480*4=1920, 320*4=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) 
                  #dst1[세로좌표,가로좌표]
cv2.imshow('dst1', dst1[500:900, 400:800])
cv2.imshow('dst2', dst2[500:900, 400:800])
cv2.imshow('dst3', dst3[500:900, 400:800])
cv2.imshow('dst4', dst4[500:900, 400:800])
cv2.waitKey()
cv2.destroyAllWindows()

 

영상 피라미드 

### 이미지 파라미드

import sys
import numpy as np
import cv2


src = cv2.imread('cat.bmp')

if src is None:
    print('Image load failed!')
    sys.exit()

    #(x,y,w,h)
rc = (250, 120, 200, 200)  # rectangle tuple

# 원본 영상에 그리기
cpy = src.copy()
cv2.rectangle(cpy, rc, (0, 0, 255), 2) #빨간색으로 두께가 2픽셀짜리 사각형
cv2.imshow('src', cpy)
cv2.waitKey()

# 피라미드 영상에 그리기
# 이미지다운
for i in range(1, 4):
    src = cv2.pyrDown(src)
    cpy = src.copy()

    #shift: 가로세로를 얼만큼 줄일건지 결정
    #2이면 원본에 2배 줄이고, 3이면 원본에 3배
    cv2.rectangle(cpy, rc, (0, 0, 255), 2, shift=i)
    cv2.imshow('src', cpy)
    cv2.waitKey()
    cv2.destroyWindow('src') #이전src가 닫혔다가 새 src가 열리는 형태
    
#이미지 업
for i in range(1,4):
	cpy=cv2.pyrUp(cpy) #축소된 이미지를 입력으로 받는다
    cv2.imshow('src2',cpy)
    cv2.waitKey()
    cv2.destroyWindow()
  
cv2.destroyWindows()

cv2.destroyAllWindows()

 

회전변환_1

### 회전 변환

import sys
import math
import numpy as np
import cv2


src = cv2.imread('tekapo.bmp')

if src is None:
    print('Image load failed!')
    sys.exit()
#중심점이 좌측상단이다. 주로 이미지의 중심점으로 회전한다 => getRotationmatrix2D
#반시계방향으로 20도
#시계방향으로는 -20도로 음수를 붙인다.
rad = 20 * math.pi / 180 #각도(degreed) 20를 radian으로 고친 과정(단위 변경)
aff = np.array([[math.cos(rad), math.sin(rad),0],
                [-math.sin(rad), math.cos(rad),0]], dtype=np.float64)

dst= cv2.warpAffine(src, aff, (0,0))

cv2.imshow('src', src)
cv2.imshow('dst', dst)
cv2.waitKey()

cv2.destroyAllWindows()

 

회전변환_2

### 회전 변환2

import sys
import numpy as np
import cv2


src = cv2.imread('tekapo.bmp')

if src is None:
    print('Image load failed!')
    sys.exit()

#입력영상의 가로,세로 크기를 반으로 나눈다
#순서는 가로, 세로
cp=(src.shape[1]/2, src.shape[0]/2)

'''
getRotationMatrix2D(center,angle,scale)
center : 중심점 좌표
angle: 각도 (음수는 시계방향)
크기조절 scale, 회전만 하고싶은때는 1 입력
'''
rot = cv2.getRotationMatrix2D(cp,20,1)
print(rot)
dst=cv2.warpAffine(src,rot,(0,0))


cv2.imshow('src', src)
cv2.imshow('dst', dst)
cv2.waitKey()

cv2.destroyAllWindows()

 

투시변환

### 투시 변환

import sys
import numpy as np
import cv2


src = cv2.imread('namecard.jpg')

if src is None:
    print('Image load failed!')
    sys.exit()

w, h = 720, 400 #출력영상 크기 정의

                    #[좌측상단],[우측상단],[우측하단],[좌측하단]
srcQuad = np.array([[325, 307], [760, 369], [718, 611], [231, 515]], np.float32)

                    #[좌상단점],[우상단점],[우하단점],[좌하단점]
dstQuad = np.array([[0, 0], [w, 0], [w, h], [0, h]], np.float32) #ndarray로 만든다

pers = cv2.getPerspectiveTransform(srcQuad, dstQuad) #pers 3*3 형태의 투시변환 행렬을 받는다
dst = cv2.warpPerspective(src, pers, (w, h))

cv2.imshow('src', src)
cv2.imshow('dst', dst)
cv2.waitKey()
cv2.destroyAllWindows()

 

리매핑

### 리매핑

import sys
import numpy as np
import cv2

'''
map1: x좌표 정보
map2: y좌표 정보
'''
src = cv2.imread('tekapo.bmp')

if src is None:
    print('Image load failed!')
    sys.exit()

h, w = src.shape[:2]

# indices: x,y 좌표 의 인덱스 값
# map1(x좌표)는 행 원소가 1씩 증가
# map2(y좌표)는 열 원소가 1씩 증가
map2,map1 = np.indices((h,w), dtype=np.float32)
# print("ymap2",map2)
print("xmap1",map1[0:10, 0:10])

#상하(위아래로)로 10픽셀
#여러번 파도가 칠수있도록 map1/32 입력
map2 = map2+10*np.sin(map1/32)

#BORDER_DEFAULT: 영상 바깥쪽의 가상영상을 검정색으로 칠하는게 아니라 주변 픽셀과 비슷한 같으로 대체하여 채워준다
dst = cv2.remap(src, map1, map2, cv2.INTER_CUBIC, borderMode=cv2.BORDER_DEFAULT)

cv2.imshow('src', src)
cv2.imshow('dst', dst)
cv2.waitKey()

cv2.destroyAllWindows()

 

 

찌그러진 영상 펴기

### 종합실습

import sys
import numpy as np
import cv2


def drawROI(img, corners): #corners= np.array([[30, 30], [30, h-30], [w-30, h-30], [w-30, 30]])
    cpy = img.copy()

    c1 = (192, 192, 255) #1번컬러 ==맑은 핑크색, 원
    c2 = (128, 128, 255) #2번컬러 ==탁한 핑크색, 사각형 라인

    for pt in corners:
        #-1: 원의 내부를 채운다
        cv2.circle(cpy, tuple(pt), 25, c1, -1, cv2.LINE_AA)

    #사격형 표현하기
    #corners은 ndarray이므로 그대로 넣어주면 에러발생
    #넘길때 tuple로 묶어서 전달 
    cv2.line(cpy, tuple(corners[0]), tuple(corners[1]), c2, 2, cv2.LINE_AA)
    cv2.line(cpy, tuple(corners[1]), tuple(corners[2]), c2, 2, cv2.LINE_AA)
    cv2.line(cpy, tuple(corners[2]), tuple(corners[3]), c2, 2, cv2.LINE_AA)
    cv2.line(cpy, tuple(corners[3]), tuple(corners[0]), c2, 2, cv2.LINE_AA)

    #원래 이미지 img에 원과 직선들을 그려놓은 이미지 cpy를 addWeighted 이용해서 합성한다
    #가중치가 있으므로 배경이 살짝 비치는 정도(중첩효과)
    #addWeighted는 전체 픽셀을 계산하기 때문에 드래그 이동 시 조금 늦게 따라온다
    disp = cv2.addWeighted(img, 0.3, cpy, 0.7, 0) 

    #return에 그냥 cpy를 넣으면 가중치가 없으므로 img가 비치지 않고 이미지가 위에 덮힌다
    #하지만 빠르게 동작함, 끊김 없음
    return disp #cpy

#콜백함수이므로 아래처럼 다섯개의 파리미터를 갖는다
#flags: 마우스,키보드 상태(클릭하고 있는지 등)
def onMouse(event, x, y, flags, param):
    global srcQuad, dragSrc, ptOld, src # 위 5개 파라미터 이외에 사용되는 것들 불러오기

    #마우스가 눌렸을때의 event
    if event == cv2.EVENT_LBUTTONDOWN:
        for i in range(4):

            #srcQuad: 네개의 원 좌표
            #25는 원의 반지름
            #내가 클릭한 점이 원 안에 있다면 드래그를 시작한다
            #ex) (30,30)과 현재 찍은 좌표의 "거리"가 25미만인 경우에만 드래그
            if cv2.norm(srcQuad[i] - (x,y))< 25:

                #드래그 시작
                dragSrc[i] =True

                #마우스가 움직일때마다 원이 이동하는 변위를 알기 위한 변수
                #저장해 놓고 재 사용한다
                ptOld= (x,y)
                break
    
    #드래그를 뗄때
    if event == cv2.EVENT_LBUTTONUP:
        for i in range(4):
            dragSrc[i] = False #드래그 초기화

    #마우스 왼쪽이 눌러 있는 상태
    if event == cv2.EVENT_MOUSEMOVE:
        for i in range(4):
            if dragSrc[i]:#True인경우 = 어떤 점을 붙잡고 드래그 하고 있는 경우에만
                dx = x - ptOld[0] #현재 좌표 - 마우스가 이전에 있던 좌표=> dx 변위를 계산
                dy = y - ptOld[1] #즉 이전에 마우스에서 얼만큼 이동했는지 dx,dy를 통해 알수 있다

                #dx,dy만큼 srcQuad(네개 좌표)를이동 
                #-=를 하게되면 드래그 하고자 하는 방향의 반대 방향으로 가게된다
                srcQuad[i] += (dx, dy)

                #이동한 만큼 화면에 보여주기 위해서 아래 처럼 작성
                #아래 코드가 없으면 드래그,클릭 모두 작동 안함
                cpy = drawROI(src, srcQuad)
                cv2.imshow('img', cpy) #클릭, 드래그 한 영상
                
                '''
                ptOld를 x,y로 최신화를 안해주면 값이 크게 벌어진다.
                위에서 맨 처음 ptOld값은 처음 마우스로 반지름 원 25안의 임의의 포인트이다.
                이값을 53줄에 보면 ptOld 변수에 넣었다. 그리고 65줄에서는 x-ptOld[0], 마우스가 옮겨진 좌표-맨처음 딱 클릭한 원 25안의 임의의 좌표이다.
                65줄에서 이동한 거리를 구하고 이값을 srcQuad에 넣어서 더해줌으로써 제대로 이동이 가능해진다
                그리고 아까 65줄에서 뺄셈했던 x,y값을 넣어줘야지
                다음 for문의 65줄에서 (현재 위치 - 바로 직전의 포인트) 식이 성립이 된다.
                만일 계속 ptOld값을 처음 찍었던 임의의 포인트로 고정한다면 드래그를 멀리 이동할수록 아래처럼 값이 커져버린다 아래 예를들면
                ptOld= (10,10)일때 드래그를 쭉 옮긴다면 20-10 -> 40-10 -> 100-10 -> 150-10 순으로  값이 10,30,90,140 갑자기 커지는데 최신화를 하면
                20-10 -> 40-20 -> 100- 40 -> 150 - 100 으로 10,20,60,50 딱 이동한 거리만큼 구해진다
                '''
                ptOld = (x, y) #현재점으로 다시 셋팅
                break


# 입력 이미지 불러오기
src = cv2.imread('scanned.jpg')

if src is None:
    print('Image open failed!')
    sys.exit()

# 입력 영상 크기 및 출력 영상 크기
h, w = src.shape[:2]

dw = 500 #임의로 가로 크기 지정
dh = round(dw * 297 / 210)  # A4 용지 크기: 210x297cm의 비율에 맞게 계산

# 모서리 점들의 좌표, 드래그 상태 여부

#srcQuad: 내가 선택하고자 하는 모서리 네개의 ndarray
#반시계 방향
srcQuad = np.array([[30, 30], [30, h-30], [w-30, h-30], [w-30, 30]], np.float32)

#dstQuad: 반시계방향의 출력영상 네개의 모서리 위치
dstQuad = np.array([[0, 0], [0, dh-1], [dw-1, dh-1], [dw-1, 0]], np.float32)

#srcQuad 네개의 점들중에 현재 어떤 점을 드래그 하고 있는지에 대한 상태정보
dragSrc = [False, False, False, False]

# 모서리점, 사각형 그리기
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 키
        cv2.destroyWindow('img') 
        sys.exit()  #아예 프로그램을 종료함


#네점의 변환행렬 출력과 영상출력 
# 투시 변환
pers = cv2.getPerspectiveTransform(srcQuad, dstQuad) # 새로운 4개 점이 결정된 변환행렬 
print(pers)
dst = cv2.warpPerspective(src, pers, (dw, dh), flags=cv2.INTER_CUBIC)

# 결과 영상 출력
cv2.imshow('dst', dst)
cv2.waitKey()
cv2.destroyAllWindows()
728x90

+ Recent posts