이전 참고글
2. 반 자동 문서 스캐너
문서 스캐너를 구현하기 위해서 마우스 이벤트 정보를 저장하는 mouse_handler와 결과 이미지를 출력하는 show_resul라는 새로운 함수를 정의한다.
Show result
Show result는 이 전에 원근 이미지를 변형하는 과정을 함수로 나타낸 것이다. 다만 원 좌표는 mouse handler에서 리스트 형태로 받아오기 때문에 데이터 타입만 변환하여 나타낸다.
def show_result():
# 결과 이미지의 가로/세로 길이
width, height = 530, 710
src = np.float32(point_list)
dst = np.array([[0,0], [width, 0], [width, height], [0,height]],
dtype = np.float32)
matrix = cv2.getPerspectiveTransform(src, dst)
result = cv2.warpPerspective(src_img, matrix, (width, height))
cv2.imshow("result", result)
Mouse handler
Mouse handler는 사용자가 마우스 왼쪽버튼을 누르면 해당 지점의 좌표를 리스트에 저장하고, 그 지점에 원형의 포인트를 찍는다. 그리고 4번의 포인트를 찍으면 결과 이미지를 출력하는 show_result 함수를 실행하고 원본 이미지 또한 출력한다. 이때 좌표는 튜플형태로 저장한다.
# x,y 좌표정보를 저장할 리스트 생성
point_list = []
# 좌표에 찍을 포인트 색상 #pink
color = (255, 0, 255)
def mouse_handler(event, x, y, flags, param):
# 마우스 왼쪽버튼을 클릭하면 해당 지점의 좌표를 point_list에 저장
if event == cv2.EVENT_LBUTTONDOWN:
point_list.append((x, y))
# 클릭한 지점에 표시
for point in point_list:
cv2.circle(src_img, point, 15, color, cv2.FILLED)
# 4개의 포인트를 다 클릭하고 나면 결과를 출력한다.
if len(point_list) ==4:
show_result()
# 원본 이미지 출력
cv2.imshow("image", src_img)
새롭게 정의한 두 함수를 이용하여 반 자동 문서 스캐너를 사용할 수 있다.
import cv2
import numpy as np
src_img = cv2.imread("poker.jpg")
color = (255, 0, 255) # pink
point_list = []
def show_result():
width, height = 530, 710
src = np.float32(point_list)
dst = np.array([[0,0], [width, 0], [width, height], [0,height]],
dtype = np.float32)
matrix = cv2.getPerspectiveTransform(src, dst)
result = cv2.warpPerspective(src_img, matrix, (width, height))
cv2.imshow("result", result)
def mouse_handler(event, x, y, flags, param):
if event == cv2.EVENT_LBUTTONDOWN:
point_list.append((x, y))
for point in point_list:
cv2.circle(src_img, point, 15, color, cv2.FILLED)
if len(point_list) ==4:
show_result()
cv2.imshow("image", src_img)
cv2.namedWindow("image")
cv2.setMouseCallback("image", mouse_handler)
cv2.waitKey(0)
cv2.destroyAllWindows()
위 코드를 실행하면 image 창에 원본이미지가 실행된다. 이때 왼쪽 상단에 해당하는 지점부터 시계방향으로 포인트를 클릭하면 해당 부분을 result라는 새 창에 출력한다.
포인트를 따라 직선 긋기
좌표와 좌표 사이에 올바르게 직선이 그어졌는지 확인하기 위해 보조직선이 나타나게 해 줄 수 있다. 직선을 나타내기 위해서는 먼저 하나의 좌표가 찍히고, 그다음 좌표를 찍었을 때, 좌표와 좌표사이에 직선을 그려야 한다. 따라서 mouse_handler 함수에서 좌표가 찍히면(event) -> 선을 그린다(drawing)와 같은 형식의 코드를 짜야한다.
# mouse_handler 함수에 보조직선을 그리는 코드 추가
mport cv2
import numpy as np
# 기본 설정 (불러올 이미지, 포인트 색깔, 두께)
src_img = cv2.imread("poker.jpg")
color = (255, 0, 255) # pink
thinckness = 3
point_list = []
# 첫번째 좌표를 찍기전엔 선을 그리지 않는다
drawing = False
def mouse_handler(event, x, y, flags, param):
# drawing을 전역변수 설정을 한다.
global drawing
# 좌표를 찍으면 직선을 그린다(drawing = True)
if event == cv2.EVENT_LBUTTONDOWN:
drawing = True
point_list.append((x, y))
# 직선의 시작점
# 직선은 두번째 좌표가 찍힌 후 그려지기 때문에 첫번째 직선의 시작점은 None이다.
prev_point = None
for point in point_list:
# 현재 좌표에는 점을 찍는다
cv2.circle(src_img, point, 15, color, cv2.FILLED)
# 이전 좌표부터 현재 좌표까지 직선을 그린다
if prev_point:
cv2.line(src_img, prev_point, point, color, thinckness, cv2.LINE_AA)
# 시작좌표 = 이전좌표
prev_point = point
if len(point_list) ==4:
show_result()
cv2.imshow("image", src_img)
직선은 첫 번째 좌표를 찍고, 두 번째 좌표를 찍고 나면 첫 번째 좌표에서부터 두 번째 좌표까지 직선을 그린다. 따라서 prev_point는 이전 포인트로, 첫 번째 좌표를 찍었을 때는 실행되지 않는다(prev_point = None) 그다음 for문을 실행하기 전에 prev_point를 현재 point로 변수를 수정하고 다음 for문을 실행한다. 따라서 2번째 좌표를 찍을 때 prev_point는 첫 번째 좌표가 된다.
위와 같이 수정된 mouse_handler 함수와 함께 결과를 출력하면 다음과 같이 스캔된다.
위의 코드는 보조직선을 그려주지만 다음 좌표를 찍어야지만 직선을 그리기 때문에 실시간으로 사용자가 직선을 똑바로 그리고 있는지 확인이 어렵다. 또한 스캔된 구역의 보조 직선이 마지막 좌표에서 첫 번째 좌표까지는 그려지지 않는다. 이와 같은 점을 보완하기 위해서는 실시간으로 직선의 움직임을 확인하면서 마지막 좌표를 찍었을 때, 마지막 좌표와 첫 번째 좌표를 이어 그릴 수 있다.
실시간으로 선 긋기
위에서의 단점들을 보완하기 위해서도 mouse_handler 함수를 수정해야 한다.
def mouse_handler(event, x, y, flags, param):
global drawing
# 이미지를 copy한다.
dst_img = src_img.copy()
if event == cv2.EVENT_LBUTTONDOWN:
drawing = True
point_list.append((x, y))
if drawing:
prev_point = None
for point in point_list:
cv2.circle(dst_img, point, 15, color, cv2.FILLED)
# 이전좌표 - 현재좌표 직선 그리기
if prev_point:
cv2.line(dst_img, prev_point, point, color, thinckness, cv2.LINE_AA)
prev_point = point
# next_point = 마우스 좌표
next_point = (x, y)
# 마지막 좌표를 찍고 나면 마지막점은 첫번째 점(point_list[0])까지 직선을 그린다.
if len(point_list) ==4:
show_result()
next_point = point_list[0]
cv2.line(dst_img, prev_point, next_point, color, thinckness, cv2.LINE_AA)
cv2.imshow("image", dst_img)
copy
copy 함수는 얕은 복사이다. 때문에 위에서 원본이미지(src_img)를 copy한 dst_img에서 새롭게 수정되어 직선이 그려지면 원본 이미지인 src_img에 반영이 된다.
# copy example
a = [[1,2],[3,4]]
b = a.copy()
b[1].append(5)
print(a)
print(b)
[[1, 2], [3, 4, 5]]
[[1, 2], [3, 4, 5]]
copy함수의 이러한 점을 이용하여 끝없이 직선이 그려지는 오류가 발생하지 않게 하기 위해 원본이미지(src_img)를 copy한 새로운 이미지(dst_img)를 mouse_handler 함수 내에서 사용한다. 만약 src_img를 그대로 사용하면 다음과 같이 마우스 포인트를 따라 직선이 무한히 그려진다.
이러한 점을 참고하여 최종적으로 코드를 실행하면 다음과 같이 스캔할 수 있다
import cv2
import numpy as np
src_img = cv2.imread("poker.jpg")
color = (255, 0, 255) # pink
thinckness = 3
point_list = []
drawing = False
def mouse_handler(event, x, y, flags, param):
global drawing
dst_img = src_img.copy()
if event == cv2.EVENT_LBUTTONDOWN:
drawing = True
point_list.append((x, y))
if drawing:
prev_point = None
for point in point_list:
cv2.circle(dst_img, point, 15, color, cv2.FILLED)
if prev_point:
cv2.line(dst_img, prev_point, point, color, thinckness, cv2.LINE_AA)
prev_point = point
next_point = (x, y)
if len(point_list) ==4:
show_result()
next_point = point_list[0]
cv2.line(dst_img, prev_point, next_point, color, thinckness, cv2.LINE_AA)
cv2.imshow("image", dst_img)
def show_result():
width, height = 530, 710
src = np.float32(point_list)
dst = np.array([[0,0], [width, 0], [width, height], [0,height]], dtype = np.float32)
matrix = cv2.getPerspectiveTransform(src, dst)
result = cv2.warpPerspective(src_img, matrix, (width, height))
cv2.imshow("result", result)
cv2.namedWindow("image")
cv2.setMouseCallback("image", mouse_handler)
cv2.waitKey(0)
cv2.destroyAllWindows()
'Study > ML | DL' 카테고리의 다른 글
[Python] OpenCV를 이용한 이미지/영상 처리 - Adaptive Threshold, otsu algorithm Threshold (0) | 2023.06.08 |
---|---|
[Python] OpenCV를 이용한 이미지/영상 처리 - 이진화(Binarization), TrackBar (0) | 2023.06.07 |
[Python] OpenCV를 이용한 반 자동 문서스캐너 구현하기1 - Mouse event (0) | 2023.05.25 |
[Python] OpenCV를 이용한 이미지/영상 처리 - 이미지 변형(흑백, 흐림, 원근) (1) | 2023.05.24 |
[Python] OpenCV를 이용한 이미지/영상 처리 - 이미지 처리 (0) | 2023.05.19 |