Python/파이썬 OpenCV 공부

[파이썬 OpenCV] 문서 스캐너 구현하기 - cv2.warpPerspective, cv2.setMouseCallback

AI 꿈나무 2020. 10. 8. 22:49
반응형

황선규 박사님의 <OpenCV 4로 배우는 컴퓨터 비전과 머신 러닝>를 공부한 내용을 정리해 보았습니다.

 

예제 코드 출처 :  황선규 박사님 github홈페이지

 

『OpenCV 4로 배우는 컴퓨터 비전과 머신 러닝』

예제 소스 코드는 아래 링크를 참고하세요

sunkyoo.github.io


 황선규 박사님의 깃허브에 올라와 있는 문서 스캐너 코드를 따라해 보았습니다.

 

문서 스캐너 구현하기

 

 

 위 그림에서 대각선으로 놓여있는 문서를 똑바른 직사각형 크기의 영상으로 변환하겠습니다.

 

구현할 기능 3가지

마우스로 문서 모서리 선택과 이동시키기

키보드 ENTER키 인식

왜곡된 문서 영상을 직사각형 형태로 똑바로 펴기(투시변환 이용)

 

예제코드

import sys
import numpy as np
import cv2

# 관심영역을 모서리 네개로 선택하는 함수
def drawROI(img, corners): # corners는 아래의 scrQuad 좌표
    cpy = img.copy # 전송받은 이미지의 복사본을 만들어서 그 위에 그림을 그립니다.
    
    c1 = (192, 192, 255) # 모서리 색상 BGR
    c2 = (128, 128, 255) # 선 색상 BGR
    
    for pt in corners: # 모서리 수 만큼 원 생성, corners 정보 이용
        cv2.circle(cpy, tuple(pt), 25, c1, -1, cv2.LINE_AA)
        
    # 모서리를 잇는 선, 점들의 좌표는 튜플
    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)
    
    # addWeighted를 이용해서 입력 영상과 cpy영상에 가중치를 적용하여 투명도 적용
    # 모서리와 선 밑에 잇는 글씨도 보임. 하지만 연산이 오래 걸린다
    disp = cv2.addWeighted(img, 0.3, cpy, 0.7, 0)
    
    return disp
    
# 마우스 이벤트 처리
def onMouse(event, x, y, flags, param): # 외관상 5개 인자. flags는 키가 눌린 여부, param은 전송 데이터
    global srcQuad, dragSrc, pt0ld, src # 전역 변수 갖고 옴
    
    # 왼쪽 마우스가 눌렸을 때
    if event == cv2.EVENT_LBUTTONDOWN:
        for i in range(4):
            if cv2.norm(srcQuad[i] - (x, y)) < 25: # 클릭한 점이 원 안에 있는지 확인
                dragSrc[i] = True
                pt0ld = (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]: # dragSrc가 True일 때
                dx = x - pt0ld[0] # 이전의 마우스 점에서 dx, dy만큼 이동
                dy = y - pt0ld[1]
                
                srcQuad[i] += (dx, dy) # 이동한 만큼 더해줌
                
                cpy = drawROI(src, srcQuad)
                cv2.imshow('img', cpy) # 수정된 좌표로 모서리 이동
                pt0ld = (x, y) # 현재 점으로 설정
                
                
# 입력 이미지 불러오기
src = cv2.imread('docu.jpg')

if src is None:
    print('Image opne failed!')
    sys.exit
    
# 입력 영상 크기 및 출력 영상 크기
h, w = src.shape[:2]
dw = 500 # 똑바로 핀 영상의 가로 크기
dh = round(dw * 297 / 210) # A4 용지 크기: 210x297cm 이용

# 모서리 점들의 좌표, 드래그 상태 여부
# 내가 선택하려는 모서리 점 4개를 저장하는 넘파이 행렬, 30은 임의로 초기점의 좌표를 설정
# 완전히 구석이 아니라 모서리를 클릭할 수 있도록 자리를 둠
srcQuad = np.array([[30, 30], [30, h-30], [w-30, h-30], [w-30, 30]], np.float32) # 모서리 위치

# 반시계 방향으로 출력 방향의 위치
dstQuad = np.array([[0, 0], [0, dh-1], [dw-1, dh-1], [dw-1, 0]], np.float32)

# 4개의 점 중에서 현재 어떤 점을 드래고 하고 있나 상태를 저장, 점을 선택하면 True, 떼면 False
dragSrc = [False, False, False, False]

# 모서리점, 사각형 그리기
# src에 srcQuad좌표를 전송해서 화면에 나타냄
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) # 3X3 투시 변환 행렬 생성
dst = cv2.warpPerspective(src, pers, (dw, dh), flags=cv2.INTER_CUBIC) # 가로 세로 크기는 자동

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

 

 

 

 

 이처럼 투시 변환으로 삐뚤어진 문서를 똑바로 펴봤습니다.

 

반응형