● 목표
- 명함영상에서 명함영역을 검출하고 영역을 직사각형의 정상의 상태로 변환
● 세부목표:
- 명함영역의 각 변의 선분을 검출하고
- 이 선분들을 이용하여 명함의 네 꼭지점의 좌표를 계산한 후
- 꼭지점의 좌표를 이용하여 명함영역을 직사각형의 정상으로 변환한다.
● 기타:
- 주어진 두 장의 영상을 차례로 처리하는 하나의 프로그램으로 작성하세요.
- 필요한 경우 각 단계에서 *적절한* 최적화가 필요합니다. 여러분의 최적화 노력을 PPT에 간략하게 설명하기 바랍니다.
- 기타 세부적인 사항은 합리적으로 가정하시기 바랍니다.
- 예를 들어 명함이 영상에 *적절한* 크기 및 방향으로 나타나 있고 명함의 상단이 수평선을 기준으로 좌우 30도 이내로만 회전 되어있다.
- 입력영상에 대하여 처리에 도움이 될 수 있는 적절한 가정을 할 수 있습니다.
● 과제의 내용
1. 명함영상을 에지영상으로 변환한다.
A. 원래 영상과 에지 영상을 나란히 디스플레이함.
2. 에지영상에 선분 검출을 적용하여 명함영역의 4 변을 추출한다.
A. 원래 영상에 추출한 4 변을 각각 다른 색 선분으로 표시할 것.
B. 추출된 네변(선분), (즉, 좌, 우, 상, 하단 )의 기울기, y 절편, 양끝점의 좌표을 각각 출력할 것.
3. 전단계에서 추출한 네 선분을 이용하여 명함영역의 네 꼭지점을 계산한다.
A. 각 꼭지점은 명함의 두 변의 교차점이다.
B. 원래 영상에 네 꼭지점을 각각 다른 색 점으로 표시할 것 .
C. 네 꼭지점(좌상, 좌하, 우상, 우하 코너)의 좌표를 출력한다.
4. 네 꼭지점을 이용하여 명함영역이 직사각형이 되도록 기하변환 한다.
A. 기하 변환된 영상을 디스플레이한다.
5. 기타
A. *가능한, 그리고 최대한* 주어진 두 장의 예제 영상들을 포함하여 어떠한 입력 영상에 대해서도 자동으로 처리가 가능하도록 한다.
B. 각 단계 마다 컬러, 그레이, 이진 영상 등에 적절한 잡음 제거 등의 필터링이 적용되어야 합니다.
C. 각 단계의 처리과정을 최적화하고 최적화 과정과 노력을 PPT에서 설명하여야 합니다.
● 참고사항
* 아래는 제안, 예시의 하나이며 이러한 과정을 반드시 따를 필요는 없습니다. 과제에 참고로 하기 바랍니다.
1. 컴퓨터 비젼이 사람이 사람의 시각 작용을 흉내낼 수 있습니다. 그러한 시스템을 만들려면 사람이 명함영역을 검출해 내는 방법을 먼저 생각해 보아야 합니다. 그런 다음 컴퓨터 비젼 기술로 이 방법을 구현하면 됩니다.
2. 먼저 영상에 나타난 명함을 시각적으로 설명해 봅시다. 아래와 같은 예시로 설명할 수 있을 것입니다.
A. 명함 영역은 흰색의 사각형 영역이다.
B. 명함 내부에는 회사 로고, 사진 등이 들어있다.
C. 또한, 명함 내부에는 문자열이 있는 데 직선상에 나란히 나타난다.
D. 명함의 상(또는, 하/좌/우)단 경계는 선분이며 명함 영역의 위(또는, 아래/왼/오른)쪽에 존재한다.
E. 명함의 좌측 상단의 코너 포인트는 명함의 상단 및 좌단의 선분의 교차점이다. 나머지 코너 포인트들도 두 선분의 교차점들이다.
F. 명함은 상단은 수평에 가깝다. 명함이 회전되더라도 좌우 30도 이상 회전 되지는 않는다. 그러므로 상단이 하단 밑에 있다든가(즉, 명함이 거꾸로 나타남) 하는 일은 없다.
G. 이 외에도 명함을 자동 처리하는 데 도움이 될 만한 여러가지 가정을 추가할 수 있습니다.
3. 위에서 생각해낸 명함의 시각적인 특성을 이용하여 명함 처리 과정을 수학, 컴퓨터 비젼 기술로 구현할 수 있는 방법을 고안해 봅시다.
과제내용






















Code
https://github.com/jin05154/edge-detection/blob/main/edge-detection-perspective-transform.py
GitHub - jin05154/edge-detection: Detect a business card in a random photo and warp the card to top-view perspective
Detect a business card in a random photo and warp the card to top-view perspective - GitHub - jin05154/edge-detection: Detect a business card in a random photo and warp the card to top-view perspec...
github.com
import numpy as np import cv2 import random from scipy.spatial import distance as dist def wait(): wait = cv2.waitKey(0) while (wait != 32): wait = cv2.waitKey(0) print(wait) def mouse_handler(event, x, y, flags, param): # 마우스로 좌표 알아내기 if event == cv2.EVENT_LBUTTONUP: clicked = [x, y] print(clicked) # card2 - 배경과 명함의 구분이 모호한 경우 def grab_cut(resized): mask_img = np.zeros(resized.shape[:2], np.uint8) # 초기 마스크를 만든다. # grabcut에 사용할 임시 배열을 만든다. bgdModel = np.zeros((1, 65), np.float64) fgdModel = np.zeros((1, 65), np.float64) # rect = (130, 51, 885-130, 661-51) #mouse_handler로 알아낸 좌표 / card1일때 rect = (107, 89, 580, 467) # card2 일 때 cv2.grabCut(resized, mask_img, rect, bgdModel, fgdModel, 3, cv2.GC_INIT_WITH_RECT) # grabcut 실행 mask_img = np.where((mask_img == 2) | (mask_img == 0), 0, 1).astype('uint8') # 배경인 곳은 0, 그 외에는 1로 설정한 마스크를 만든다. img = resized * mask_img[:, :, np.newaxis] # 이미지에 새로운 마스크를 곱해 배경을 제외한다. background = resized - img background[np.where((background >= [0, 0, 0]).all(axis=2))] = [0, 0, 0] img_grabcut = background + img cv2.imshow('grabcut', img_grabcut) wait() new_edged = edge_detection(img_grabcut) wait() global new_contour new_contour = contoursGrab(new_edged) #에지 검출 : 흑백 -> 가우시안블러링 -> 캐니 def edge_detection(img): gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) _, gray = cv2.threshold(gray, 120, 255, cv2.THRESH_TOZERO) gray = cv2.GaussianBlur(gray, (7, 7), 0) gray = cv2.bilateralFilter(gray, 9, 75, 75) gray = cv2.edgePreservingFilter(gray, flags=1, sigma_s=45, sigma_r=0.2) edged = cv2.Canny(gray, 75, 200, True) #cv2.imshow("grayscale", gray) #wait() cv2.imshow("edged", edged) #wait() return edged def contours(edge): global checkpnt #edged = edge_detection(resize_img) (cnts, _) = cv2.findContours(edge.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) # 계층관계가 필요없기 때문에 contour만 추출 cnts = sorted(cnts, key = cv2.contourArea, reverse = True)[:5] # contourArea : contour가 그린 면적 for i in cnts: peri = cv2.arcLength(i, True) # contour가 그리는 길이 반환 approx = cv2.approxPolyDP(i, 0.02 * peri, True) # 길이에 2% 정도 오차를 둔다 if len(approx) == 4: # 도형을 근사해서 외곽의 꼭짓점이 4개라면 명암의 외곽으로 설정 screenCnt = approx size = len(screenCnt) break if len(approx) != 4 and checkpnt == 0: # 사각형이 그려지지 않는다면 grab_cut 실행 size = 0 checkpnt += 1 grab_cut(resize_img) if len(approx) != 4 and checkpnt > 0: size = 0 if (size > 0): # 2.A 원래 영상에 추출한 4 변을 각각 다른 색 선분으로 표시한다. cv2.line(resize_img, tuple(screenCnt[0][0]), tuple(screenCnt[size-1][0]), (255, 0, 0), 3) for j in range(size-1): color = list(np.random.random(size=3) * 255) cv2.line(resize_img, tuple(screenCnt[j][0]), tuple(screenCnt[j+1][0]), color, 3) #for i in screenCnt: #이렇게 하면 네 변을 다른 색으로 표현 불가능(네 변이 모두 다 똑같은 색으로 나온다.) #color = list(np.random.random(size=3) * 256) #cv2.drawContours(resize_img, [screenCnt], -1,color, 3) # 2.B 추출된 선분(좌, 우, 상, 하)의 기울기, y절편, 양끝점의 좌표를 각각 출력 axis = np.zeros(4) # 기울기 = (y증가량) / (x증가량) #left_axis = (screenCnt[0][0][1] - screenCnt[1][0][1]) / (screenCnt[0][0][0] - screenCnt[1][0][0]) #down_axis = (screenCnt[1][0][1] - screenCnt[2][0][1]) / (screenCnt[1][0][0] - screenCnt[2][0][0]) #right_axis = (screenCnt[2][0][1] - screenCnt[3][0][1]) / (screenCnt[2][0][0] - screenCnt[3][0][0]) #upper_axis = (screenCnt[3][0][1] - screenCnt[0][0][1]) / (screenCnt[3][0][0] - screenCnt[0][0][0]) axis[3] = (screenCnt[3][0][1] - screenCnt[0][0][1]) / (screenCnt[3][0][0] - screenCnt[0][0][0]) for k in range(3): axis[k] = (screenCnt[k][0][1] - screenCnt[k+1][0][1]) / (screenCnt[k][0][0] - screenCnt[k+1][0][0]) left_axis = axis[0] #좌 기울기 down_axis = axis[1] #하 기울기 right_axis = axis[2] #우 기울기 upper_axis = axis[3] #상 기울기 print("(2.B) 순서대로 좌, 우, 상, 하 선분의 기울기") print(left_axis, right_axis, upper_axis, down_axis) print("\n") # y = ax + b 에서 x = 0일때의 b가 y절편 / 기울기를 알고 두 좌표를 알 때의 방정식 : y - y1 = (y2 - y1)/(x2 - x1) * (x - x1) #좌 선분의 y절편 #left_y - screenCnt[1][0][1] = left_axis * (left_x - screenCnt[1][0][0]) #left_y = (left_axis * left_x) - (left_axis * screenCnt[1][0][0]) + screenCnt[1][0][1] #따라서 left_y = screenCnt[1][0][1] - (left_axis * screenCnt[1][0][0]) left_y = screenCnt[1][0][1] - (left_axis * screenCnt[1][0][0]) #좌 y절편 #우 선분의 y절편 #right_y - screenCnt[3][0][1] = right_axis * (right_x - screenCnt[3][0][0]) #right_y = (right_axis * right_x) - (right_axis * screenCnt[3][0][0]) + screenCnt[3][0][1] #따라서 right_y = screenCnt[3][0][1] - (right_axis * screenCnt[3][0][0]) right_y = screenCnt[3][0][1] - (right_axis * screenCnt[3][0][0]) #우 y절편 #상 선분의 y절편 #upper_y - screenCnt[0][0][1] = upper_axis * (upper_x - screenCnt[0][0][0]) #upper_y = (upper_axis * upper_x) - (upper_axis * screenCnt[0][0][0]) + screenCnt[0][0][1] #따라서 upper_y = screenCnt[0][0][1] - (upper_axis * screenCnt[0][0][0]) upper_y = screenCnt[0][0][1] - (upper_axis * screenCnt[0][0][0]) #상 y절편 #하 선분의 y절편 #donw_y - screenCnt[2][0][1] = down_axis * (down_x - screenCnt[2][0][0]) #down_y = (down_axis * down_x) - (down_axis * screenCnt[2][0][0]) + screenCnt[2][0][1] #따라서 down_y = screenCnt[2][0][1] - (down_axis * screenCnt[2][0][0]) down_y = screenCnt[2][0][1] - (down_axis * screenCnt[2][0][0]) #하 y절편 print("(2.B) 순서대로 좌, 우, 상, 하 선분의 y절편") print(left_y, right_y, upper_y, down_y) print("\n") #양끝점의 좌표 print("(2.B) 순서대로 좌, 우, 상, 하 선분의 양 끝점") print((screenCnt[0][0][0], screenCnt[0][0][1]), (screenCnt[1][0][0], screenCnt[1][0][1])) #좌 선분의 양 끝점 print((screenCnt[2][0][0], screenCnt[2][0][1]), (screenCnt[3][0][0], screenCnt[3][0][1])) #우 성분의 양 끝점 print((screenCnt[0][0][0], screenCnt[0][0][1]), (screenCnt[3][0][0], screenCnt[3][0][1])) #상 성분의 양 끝점 print((screenCnt[1][0][0], screenCnt[1][0][1]), (screenCnt[2][0][0], screenCnt[2][0][1])) #하 성분의 양 끝점 print("\n") # 3.B 네 꼭짓점을 각각 다른 색 점으로 표시한다. cv2.drawContours(resize_img, screenCnt, 0, (0, 0, 0), 15) #검 cv2.drawContours(resize_img, screenCnt, 1, (255, 0, 0), 15) #파 cv2.drawContours(resize_img, screenCnt, 2, (0, 255, 0), 15) #녹 cv2.drawContours(resize_img, screenCnt, 3, (0, 0, 255), 15) #적 cv2.imshow("With_Color_Image", resize_img) # 3.C 네 꼭지점(좌상, 좌하, 우상, 우하)의 좌표를 출력한다. vertex = solving_vertex(screenCnt.reshape(4,2)) #(topLeft, bottomLeft, topRight, bottomRight) = vertex print("(3.C) 순서대로 좌상, 좌하, 우상, 우하의 꼭짓점 좌표") print(vertex) def contoursGrab(edged): (cnts, _) = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) cnts = sorted(cnts, key=cv2.contourArea, reverse=True)[:5] # contourArea : contour가 그린 면적 largest = max(cnts, key=cv2.contourArea) rect = cv2.minAreaRect(largest) r = cv2.boxPoints(rect) box = np.int0(r) size = len(box) # 2.A 원래 영상에 추출한 4 변을 각각 다른 색 선분으로 표시한다. cv2.line(resize_img, tuple(box[0]), tuple(box[size - 1]), (255, 0, 0), 3) for j in range(size - 1): color = list(np.random.random(size=3) * 255) cv2.line(resize_img, tuple(box[j]), tuple(box[j + 1]), color, 3) # 4개의 점 다른색으로 표시 boxes = [tuple(i) for i in box] cv2.line(resize_img, boxes[0], boxes[0], (0, 0, 0), 15) # 검 cv2.line(resize_img, boxes[1], boxes[1], (255, 0, 0), 15) # 파 cv2.line(resize_img, boxes[2], boxes[2], (0, 255, 0), 15) # 녹 cv2.line(resize_img, boxes[3], boxes[3], (0, 0, 255), 15) # 적 cv2.imshow("With_Color_Image", resize_img) return boxes def order_dots(pts): p = np.array(pts) xSorted = p[np.argsort(p[:, 0]), :] leftMost = xSorted[:2, :] rightMost = xSorted[2:, :] leftMost = leftMost[np.argsort(leftMost[:, 1]), :] (a, c) = leftMost D = dist.cdist(a[np.newaxis], rightMost, "euclidean")[0] (b, d) = rightMost[np.argsort(D), :] return a, b, c, d def transformationGrab(resized, pts): p = np.array(pts) rect = np.zeros((4, 2), dtype="float32") s = p.sum(axis=1) rect[0] = pts[np.argmin(s)] rect[2] = pts[np.argmax(s)] diff = np.diff(pts, axis=1) rect[1] = pts[np.argmin(diff)] rect[3] = pts[np.argmax(diff)] a, b, c, d = rect w1 = abs(c[0] - d[0]) w2 = abs(a[0] - b[0]) h1 = abs(b[1] - c[1]) h2 = abs(a[1] - d[1]) w = max([w1, w2]) h = max([h1, h2]) #dst = np.float32([[0, 0], [w - 1, 0], [w - 1, h - 1], [0, h - 1]]) dst = np.array([ [0, 0], [640, 0], [640, 480], [0, 480] ], dtype=np.float32) M = cv2.getPerspectiveTransform(rect, dst) result = cv2.warpPerspective(resized, M, dsize=(640, 480)) cv2.imshow("transformation", result) return result def solving_vertex(pts): points = np.zeros((4,2), dtype= "uint32") #x,y쌍이 4개 쌍이기 때문 #원점 (0,0)은 맨 왼쪽 상단에 있으므로, x+y의 값이 제일 작으면 좌상의 꼭짓점 / x+y의 값이 제일 크면 우하의 꼭짓점 s = pts.sum(axis = 1) points[0] = pts[np.argmin(s)] #좌상 points[3] = pts[np.argmax(s)] #우하 #원점 (0,0)은 맨 왼쪽 상단에 있으므로, y-x의 값이 가장 작으면 우상의 꼭짓점 / y-x의 값이 가장 크면 좌하의 꼭짓점 diff = np.diff(pts, axis = 1) points[2] = pts[np.argmin(diff)] #우상 points[1] = pts[np.argmax(diff)] #좌하 src.append(points[0]) src.append(points[1]) src.append(points[2]) src.append(points[3]) return points def transformation(): #print(src) src_np = np.array(src, dtype=np.float32) #print(src_np) dst_np = np.array([ [0, 0], [0, 480], [640, 0], [640, 480] ], dtype=np.float32) M = cv2.getPerspectiveTransform(src=src_np, dst=dst_np) result = cv2.warpPerspective(resize_img, M=M, dsize=(640, 480)) cv2.imshow("result", result) class Point: def __init__(self, x, y): self.x = x self.y = y def __str__(self): return f"[{self.x} {self.y}]" # y절편 구하기 def get_intercepts(ordered_dots): slopes = get_slopes(ordered_dots) a, b, c, d = [Point(*coord) for coord in ordered_dots] left = -a.x * slopes[0] + a.y right = -b.x * slopes[1] + b.y top = -a.x * slopes[2] + a.y bottom = -c.x * slopes[3] + c.y return left, right, top, bottom # 기울기 구하기 def get_slopes(ordered_dots): a, b, c, d = [Point(*coord) for coord in ordered_dots] left = (a.y - c.y) / (a.x - c.x) right = (b.y - d.y) / (b.x - d.x) top = (a.y - b.y) / (a.x - b.x) bottom = (c.y - d.y) / (c.x - d.x) return left, right, top, bottom if __name__ == "__main__": filename = 'card1.jpg' ori_img = cv2.imread(filename) resize_img = cv2.resize(ori_img, dsize=(640, 480), interpolation=cv2.INTER_AREA) # 원래 사진이 4000x3000 이라서 사이즈 조절하였음. global checkpnt global new_contour checkpnt = 0 src = [] # 명함 영역 꼭지점의 좌표 # grab_cut() # wait() # edge_detection(grab_cut()) # wait() cv2.imshow('original_img', resize_img) wait() edged = edge_detection(resize_img) wait() contours(edged) wait() if checkpnt == 0: transformation() else: pts = new_contour transformationGrab(resize_img, pts) dots = order_dots(pts) a, b, c, d = dots print("2-B 추출된 네변(선분), (즉, 좌, 우, 상, 하단 )의 기울기, y 절편, 양끝점의 좌표을 각각 출력할 것.\n") print("기울기 :") print(get_slopes(dots)) print("\n") print("y절편 :") print(get_intercepts(dots)) print("\n") print("양끝점 :") print(a, c) print(b, d) print(a, b) print(c, d) print("\n") print("3-C. 네 꼭지점(좌상, 좌하, 우상, 우하 코너)의 좌표를 출력한다") print(a) print(b) print(c) print(d) wait()
점수 10/10
❤와 댓글은 큰 힘이 됩니다. 감사합니다 :-)
'archive. > Schoolwork' 카테고리의 다른 글
컴퓨터구조론 - PC 부품 기능과 PC 조립 단계 (0) | 2021.12.20 |
---|---|
[Java] 데이터베이스기초 과제 - JDBC 직원 정보 검색 시스템 Company GUI (1) | 2021.03.03 |
[Python] 그래픽스 07주차 과제 - 번호 인식을 위한 전처리(이진화) (0) | 2020.09.05 |
[Python] 그래픽스 06주차 과제 - Perspective transform (0) | 2020.09.05 |
[Python] 그래픽스 05주차 과제 - Canny Edge 검출 (9) | 2020.09.05 |