-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathpage.py
119 lines (93 loc) · 4.06 KB
/
page.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
"""
Crop background and transform perspective from the photo of page
"""
import numpy as np
import cv2
from utils import *
def detection(image):
"""Finding Page."""
# Edge detection
image_edges = _edges_detection(image, 200, 250)
# Close gaps between edges (double page clouse => rectangle kernel)
closed_edges = cv2.morphologyEx(image_edges,
cv2.MORPH_CLOSE,
np.ones((5, 11)))
# Countours
page_contour = _find_page_contours(closed_edges, resize(image))
# Recalculate to original scale
page_contour = page_contour.dot(ratio(image))
# Transform prespective
new_image = _persp_transform(image, page_contour)
return new_image
def _edges_detection(img, minVal, maxVal):
"""Preprocessing (gray, thresh, filter, border) + Canny edge detection."""
img = cv2.cvtColor(resize(img), cv2.COLOR_BGR2GRAY)
img = cv2.bilateralFilter(img, 9, 75, 75)
img = cv2.adaptiveThreshold(img, 255,
cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY, 115, 4)
# Median blur replace center pixel by median of pixels under kelner
# => removes thin details
img = cv2.medianBlur(img, 11)
# Add black border - detection of border touching pages
img = cv2.copyMakeBorder(img, 5, 5, 5, 5,
cv2.BORDER_CONSTANT,
value=[0, 0, 0])
return cv2.Canny(img, minVal, maxVal)
def _four_corners_sort(pts):
"""Sort corners in order: top-left, bot-left, bot-right, top-right."""
diff = np.diff(pts, axis=1)
summ = pts.sum(axis=1)
return np.array([pts[np.argmin(summ)],
pts[np.argmax(diff)],
pts[np.argmax(summ)],
pts[np.argmin(diff)]])
def _contour_offset(cnt, offset):
"""Offset contour because of 5px border."""
cnt += offset
cnt[cnt < 0] = 0
return cnt
def _find_page_contours(edges, img):
"""Finding corner points of page contour."""
im2, contours, hierarchy = cv2.findContours(edges,
cv2.RETR_TREE,
cv2.CHAIN_APPROX_SIMPLE)
# Finding biggest rectangle otherwise return original corners
height = edges.shape[0]
width = edges.shape[1]
MIN_COUNTOUR_AREA = height * width * 0.5
MAX_COUNTOUR_AREA = (width - 10) * (height - 10)
max_area = MIN_COUNTOUR_AREA
page_contour = np.array([[0, 0],
[0, height-5],
[width-5, height-5],
[width-5, 0]])
for cnt in contours:
perimeter = cv2.arcLength(cnt, True)
approx = cv2.approxPolyDP(cnt, 0.03 * perimeter, True)
# Page has 4 corners and it is convex
if (len(approx) == 4 and
cv2.isContourConvex(approx) and
max_area < cv2.contourArea(approx) < MAX_COUNTOUR_AREA):
max_area = cv2.contourArea(approx)
page_contour = approx[:, 0]
# Sort corners and offset them
page_contour = _four_corners_sort(page_contour)
return _contour_offset(page_contour, (-5, -5))
def _persp_transform(img, s_points):
"""Transform perspective from start points to target points."""
# Euclidean distance - calculate maximum height and width
height = max(np.linalg.norm(s_points[0] - s_points[1]),
np.linalg.norm(s_points[2] - s_points[3]))
width = max(np.linalg.norm(s_points[1] - s_points[2]),
np.linalg.norm(s_points[3] - s_points[0]))
# Create target points
t_points = np.array([[0, 0],
[0, height],
[width, height],
[width, 0]], np.float32)
# getPerspectiveTransform() needs float32
if s_points.dtype != np.float32:
s_points = s_points.astype(np.float32)
M = cv2.getPerspectiveTransform(s_points, t_points)
return cv2.warpPerspective(img, M, (int(width), int(height)))