118 lines
3.7 KiB
Python
118 lines
3.7 KiB
Python
import cv2, numpy, glob
|
|
|
|
|
|
def read_img(file):
|
|
img = cv2.imread(file)
|
|
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
|
|
return img
|
|
|
|
def order_corners(corners):
|
|
xSorted = corners[numpy.argsort(corners[:, 0]), :]
|
|
left = xSorted[:2, :]
|
|
tl, bl = left[numpy.argsort(left[:, 1]), :]
|
|
|
|
right = xSorted[2:, :]
|
|
tr, br = right[numpy.argsort(right[:, 1]), :]
|
|
|
|
return numpy.array([tl, tr, bl, br], 'float32')
|
|
|
|
|
|
def autocrop(img):
|
|
# save copy to apply transform
|
|
orig_img = img.copy()
|
|
|
|
# smoothing to remove text and other small sharp edges
|
|
kernel = numpy.ones((5,5), numpy.uint8)
|
|
img = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel, iterations=5)
|
|
|
|
# edge detection + best contours
|
|
median_pixel_inten = numpy.median(img)
|
|
img = cv2.Canny(img, int(max(0, 0.7*median_pixel_inten)), int(min(255, 1.3*median_pixel_inten)), 3)
|
|
img = cv2.dilate(img, None, iterations=1)
|
|
img = cv2.erode(img, None, iterations=1)
|
|
conts, _ = cv2.findContours(img, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
|
|
conts = sorted(conts, key=cv2.contourArea, reverse=True)[:10]
|
|
|
|
# compute estimated bounding corners
|
|
for cont in conts:
|
|
corners = cv2.approxPolyDP(cont, cv2.arcLength(cont, True) * 0.02, True)
|
|
|
|
# if 4 corners found and cover an area > 1k pixels
|
|
if len(corners) == 4 and cv2.contourArea(corners) >= 1000:
|
|
corners = corners.reshape((4,2))
|
|
break
|
|
|
|
# default incase none found
|
|
corners = numpy.array([[0,0],[img.shape[1], 0], [0, img.shape[0]], [img.shape[1], img.shape[0]]])
|
|
|
|
# proper ordering for transformation calc
|
|
corners = order_corners(corners)
|
|
|
|
# compute new dimensions
|
|
w1 = numpy.sqrt((corners[3][0] - corners[2][0]) ** 2 + (corners[3][1] - corners[3][1]) ** 2)
|
|
w2 = numpy.sqrt((corners[1][0] - corners[0][0]) ** 2 + (corners[1][1] - corners[0][1]) ** 2)
|
|
h1 = numpy.sqrt((corners[3][0] - corners[1][0]) ** 2 + (corners[3][1] - corners[1][1]) ** 2)
|
|
h2 = numpy.sqrt((corners[2][0] - corners[0][0]) ** 2 + (corners[2][1] - corners[0][1]) ** 2)
|
|
max_w = int(max(w1, w2))
|
|
max_h = int(max(h1, h2))
|
|
dest_corners = numpy.array([[0, 0], [max_w - 1, 0], [0, max_h - 1] ,[max_w - 1, max_h - 1]], 'float32')
|
|
|
|
# compute and apply transformation
|
|
transf_mat = cv2.getPerspectiveTransform(corners, dest_corners)
|
|
img = cv2.warpPerspective(orig_img, transf_mat, (max_w-1, max_h-1), flags = cv2.INTER_LINEAR)
|
|
# rescale for next steps
|
|
img = cv2.resize(img, (int(img.shape[1] * 1200 / img.shape[0]), 1200))
|
|
return img
|
|
|
|
def is_low_contrast(img, thresh):
|
|
# reduce noise
|
|
pixel_range = numpy.percentile(img, [5,95])
|
|
|
|
# calc spread mesure
|
|
val = (pixel_range[1] - pixel_range[0]) / 255
|
|
return val < thresh
|
|
|
|
def is_blurry(img, thresh):
|
|
# compute laplacian
|
|
lap = cv2.Laplacian(img, cv2.CV_64F)
|
|
|
|
# remove noise and find largest value
|
|
blur = numpy.percentile(lap, [0, 99])[1]
|
|
return blur < thresh
|
|
|
|
def remove_shadows(img):
|
|
# spread out pixels
|
|
dil = cv2.dilate(img, (7,7))
|
|
|
|
# blur to infer background
|
|
blur = cv2.medianBlur(dil, 25)
|
|
|
|
# remove smoothed background (shadows)
|
|
img = 225 - cv2.absdiff(img, blur)
|
|
|
|
# threshold for b&w
|
|
_, img = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
|
|
return img
|
|
|
|
def write_img(img, file):
|
|
cv2.imwrite('outs/' + file, img)
|
|
|
|
def process_img(img):
|
|
if is_low_contrast(img, 0.6):
|
|
return -1, img
|
|
img = autocrop(img)
|
|
if is_blurry(img, 70):
|
|
return -1, img
|
|
img = remove_shadows(img)
|
|
return 0, img
|
|
|
|
def main():
|
|
files = glob.glob('*.jpg')
|
|
for file in files:
|
|
img = read_img(file)
|
|
ret, img = process_img(img)
|
|
if ret != -1:
|
|
write_img(img, file)
|
|
|
|
if __name__ == '__main__':
|
|
main()
|