diff --git a/page_dewarp.py b/page_dewarp.py index 6ef5b33..baaa41c 100755 --- a/page_dewarp.py +++ b/page_dewarp.py @@ -348,13 +348,20 @@ def blob_mean_and_tangent(contour): area = moments['m00'] - mean_x = moments['m10'] / area - mean_y = moments['m01'] / area - - moments_matrix = np.array([ - [moments['mu20'], moments['mu11']], - [moments['mu11'], moments['mu02']] - ]) / area + if area != 0: + mean_x = moments['m10'] / area + mean_y = moments['m01'] / area + + moments_matrix = np.array([ + [moments['mu20'], moments['mu11']], + [moments['mu11'], moments['mu02']] + ]) / area + else: + mean_x = mean_y = 0 + moments_matrix = np.array([ + [0.0, 0.0], + [0.0, 0.0] + ]) _, svd_u, _ = cv2.SVDecomp(moments_matrix) @@ -445,9 +452,11 @@ def make_tight_mask(contour, xmin, ymin, width, height): def get_contours(name, small, pagemask, masktype): mask = get_mask(name, small, pagemask, masktype) + # In some environments/versions, cv2.findContours apparently returns a 2-tuple instead of 3-tuple + # https://github.com/facebookresearch/maskrcnn-benchmark/issues/339 + # We are always interested in the second-to-last member (first or second member) of the tuple - _, contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, - cv2.CHAIN_APPROX_NONE) + contours = cv2.findContours(mask, cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_NONE)[-2] contours_out = [] @@ -554,7 +563,7 @@ def sample_spans(shape, spans): xmin, ymin = cinfo.rect[:2] step = SPAN_PX_PER_STEP - start = ((len(means)-1) % step) / 2 + start = int(((len(means)-1) % step) / 2) #error => cast float to int contour_points += [(x+xmin, means[x]+ymin) for x in range(start, len(means), step)] @@ -670,7 +679,7 @@ def visualize_spans(name, small, pagemask, spans): display = small.copy() display[mask] = (display[mask]/2) + (regions[mask]/2) - display[pagemask == 0] /= 4 + display[pagemask == 0] //= 4 debug_show(name, 2, 'spans', display) @@ -736,20 +745,20 @@ def objective(pvec): ppts = project_keypoints(pvec, keypoint_index) return np.sum((dstpoints - ppts)**2) - print ' initial objective is', objective(params) + print (' initial objective is', objective(params)) if DEBUG_LEVEL >= 1: projpts = project_keypoints(params, keypoint_index) display = draw_correspondences(small, dstpoints, projpts) debug_show(name, 4, 'keypoints before', display) - print ' optimizing', len(params), 'parameters...' + print (' optimizing', len(params), 'parameters...') start = datetime.datetime.now() res = scipy.optimize.minimize(objective, params, - method='Powell') + method='SLSQP') end = datetime.datetime.now() - print ' optimization took', round((end-start).total_seconds(), 2), 'sec.' - print ' final objective is', res.fun + print (' optimization took', round((end-start).total_seconds(), 2), 'sec.') + print (' final objective is', res.fun) params = res.x if DEBUG_LEVEL >= 1: @@ -773,7 +782,7 @@ def objective(dims): res = scipy.optimize.minimize(objective, dims, method='Powell') dims = res.x - print ' got page dims', dims[0], 'x', dims[1] + print (' got page dims', dims[0], 'x', dims[1]) return dims @@ -786,13 +795,13 @@ def remap_image(name, img, small, page_dims, params): width = round_nearest_multiple(height * page_dims[0] / page_dims[1], REMAP_DECIMATE) - print ' output will be {}x{}'.format(width, height) + print (' output will be {}x{}'.format(width, height)) height_small = height / REMAP_DECIMATE width_small = width / REMAP_DECIMATE - page_x_range = np.linspace(0, page_dims[0], width_small) - page_y_range = np.linspace(0, page_dims[1], height_small) + page_x_range = np.linspace(0, page_dims[0], int(width_small)) #error => cast float to int + page_y_range = np.linspace(0, page_dims[1], int(height_small)) #error => cast float to int page_x_coords, page_y_coords = np.meshgrid(page_x_range, page_y_range) @@ -820,7 +829,7 @@ def remap_image(name, img, small, page_dims, params): None, cv2.BORDER_REPLICATE) thresh = cv2.adaptiveThreshold(remapped, 255, cv2.ADAPTIVE_THRESH_MEAN_C, - cv2.THRESH_BINARY, ADAPTIVE_WINSZ, 25) + cv2.THRESH_BINARY, ADAPTIVE_WINSZ, 17) pil_image = Image.fromarray(thresh) pil_image = pil_image.convert('1') @@ -841,7 +850,7 @@ def remap_image(name, img, small, page_dims, params): def main(): if len(sys.argv) < 2: - print 'usage:', sys.argv[0], 'IMAGE1 [IMAGE2 ...]' + print ('usage:', sys.argv[0], 'IMAGE1 [IMAGE2 ...]') sys.exit(0) if DEBUG_LEVEL > 0 and DEBUG_OUTPUT != 'file': @@ -856,8 +865,7 @@ def main(): basename = os.path.basename(imgfile) name, _ = os.path.splitext(basename) - print 'loaded', basename, 'with size', imgsize(img), - print 'and resized to', imgsize(small) + print ('loaded', basename, 'with size', imgsize(img),'\nand resized to', imgsize(small)) if DEBUG_LEVEL >= 3: debug_show(name, 0.0, 'original', small) @@ -868,20 +876,19 @@ def main(): spans = assemble_spans(name, small, pagemask, cinfo_list) if len(spans) < 3: - print ' detecting lines because only', len(spans), 'text spans' + print (' detecting lines because only', len(spans), 'text spans') cinfo_list = get_contours(name, small, pagemask, 'line') spans2 = assemble_spans(name, small, pagemask, cinfo_list) if len(spans2) > len(spans): spans = spans2 if len(spans) < 1: - print 'skipping', name, 'because only', len(spans), 'spans' + print ('skipping', name, 'because only', len(spans), 'spans') continue span_points = sample_spans(small.shape, spans) - print ' got', len(spans), 'spans', - print 'with', sum([len(pts) for pts in span_points]), 'points.' + print (' got', len(spans), 'spans','\nwith', sum([len(pts) for pts in span_points]), 'points.') corners, ycoords, xcoords = keypoints_from_samples(name, small, pagemask, @@ -904,12 +911,97 @@ def main(): outfiles.append(outfile) - print ' wrote', outfile - print + print (' wrote', outfile) + print("") + + print ('to convert to PDF (requires ImageMagick):') + print (' convert -compress Group4 ' + ' '.join(outfiles) + ' output.pdf') + +def dewarp(name, img): + + small = resize_to_screen(img) + + pagemask, page_outline = get_page_extents(small) + + cinfo_list = get_contours(name, small, pagemask, 'text') + spans = assemble_spans(name, small, pagemask, cinfo_list) + + if len(spans) < 3: + print (' detecting lines because only', len(spans), 'text spans') + cinfo_list = get_contours(name, small, pagemask, 'line') + spans2 = assemble_spans(name, small, pagemask, cinfo_list) + if len(spans2) > len(spans): + spans = spans2 + + if len(spans) < 1: + print ('skipping', name, 'because only', len(spans), 'spans') + return None + + span_points = sample_spans(small.shape, spans) + + corners, ycoords, xcoords = keypoints_from_samples(name, small, + pagemask, + page_outline, + span_points) + + rough_dims, span_counts, params = get_default_params(corners, + ycoords, xcoords) + + dstpoints = np.vstack((corners[0].reshape((1, 1, 2)),) + + tuple(span_points)) - print 'to convert to PDF (requires ImageMagick):' - print ' convert -compress Group4 ' + ' '.join(outfiles) + ' output.pdf' + params = optimize_params(name, small, + dstpoints, + span_counts, params) + + page_dims = get_page_dims(corners, rough_dims, params) + outfile = remap_image(name, img, small, page_dims, params) + + return outfile + +def is_curve(name, img): + + small = resize_to_screen(img) + + pagemask, page_outline = get_page_extents(small) + + cinfo_list = get_contours(name, small, pagemask, 'text') + spans = assemble_spans(name, small, pagemask, cinfo_list) + + if len(spans) < 3: + print (' detecting lines because only', len(spans), 'text spans') + cinfo_list = get_contours(name, small, pagemask, 'line') + spans2 = assemble_spans(name, small, pagemask, cinfo_list) + if len(spans2) > len(spans): + spans = spans2 + + if len(spans) < 1: + print ('skipping', name, 'because only', len(spans), 'spans') + return None + + span_points = sample_spans(small.shape, spans) + + corners, ycoords, xcoords = keypoints_from_samples(name, small, + pagemask, + page_outline, + span_points) + + rough_dims, span_counts, params = get_default_params(corners, + ycoords, xcoords) + + dstpoints = np.vstack((corners[0].reshape((1, 1, 2)),) + + tuple(span_points)) + + params = optimize_params(name, small, + dstpoints, + span_counts, params) + + if abs(params[0]) < 0.1 and abs(params[1]) < 0.1 and abs(params[2]) < 0.1: + return True + else: + print(params[:8]) + return False if __name__ == '__main__': main() diff --git a/requirements.txt b/requirements.txt index c03f9c9..cfabac9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,4 @@ -page_dewarp -numpy -scipy -Image -cv2>=3.0 +numpy>=1.21,<2 +scipy>=1.7,<2 +pillow>=8.4,<9 +opencv-python>=3.4,<4