From 0dce1f24d224f7fa949e1550600cd5362423d2b3 Mon Sep 17 00:00:00 2001 From: Robert Sachunsky Date: Thu, 23 Apr 2026 20:08:40 +0200 Subject: [PATCH] =?UTF-8?q?do=5Fwork=5Fof=5Fslopes=5Fnew=5Fcurved:=20impro?= =?UTF-8?q?ve=20deskewing=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - return early if textline mask is empty - intersect textline mask with parent mask (so neighbouring, truncated textlines will not interfere) - fix bug when resulting angle is small: rather, compare with page angle - if there is more than 1 line in the region, * use median instead of mean to estimate y_diff * if height dominates over width and x_diff over y_diff, then assume 90°: transpose image, deskew on that, then add 90° to result - otherwise instead of just using page angle, try to estimate single-line angle by approximating slope of linear x-y regression on mask image; again, if height dominates over width, then assume +90° and use transposed image - drop unused `scale` param --- src/eynollah/eynollah.py | 8 ++-- src/eynollah/utils/separate_lines.py | 69 ++++++++++++++++++++-------- 2 files changed, 54 insertions(+), 23 deletions(-) diff --git a/src/eynollah/eynollah.py b/src/eynollah/eynollah.py index f3615a1..961cdaa 100644 --- a/src/eynollah/eynollah.py +++ b/src/eynollah/eynollah.py @@ -1034,14 +1034,13 @@ class Eynollah: slopes) def get_slopes_and_deskew_new_curved(self, contours_par, textline_mask_tot, boxes, - num_col, scale_par, slope_deskew, name): + num_col, slope_deskew, name): if not len(contours_par): return [], [], [] self.logger.debug("enter get_slopes_and_deskew_new_curved") results = map(partial(do_work_of_slopes_new_curved, textline_mask_tot_ea=textline_mask_tot, num_col=num_col, - scale_par=scale_par, slope_deskew=slope_deskew, MAX_SLOPE=MAX_SLOPE, KERNEL=KERNEL, @@ -2503,20 +2502,19 @@ class Eynollah: all_found_textline_polygons_marginals = dilate_textline_contours( all_found_textline_polygons_marginals) else: - scale_param = 1 textline_mask_tot_ea_erode = cv2.erode(textline_mask_tot_ea, kernel=KERNEL, iterations=2) all_found_textline_polygons, \ all_box_coord, slopes = self.get_slopes_and_deskew_new_curved( polygons_of_textregions, textline_mask_tot_ea_erode, boxes_text, - num_col_classifier, scale_param, slope_deskew, image['name']) + num_col_classifier, slope_deskew, image['name']) all_found_textline_polygons = small_textlines_to_parent_adherence2( all_found_textline_polygons, textline_mask_tot_ea, num_col_classifier) all_found_textline_polygons_marginals, \ all_box_coord_marginals, slopes_marginals = self.get_slopes_and_deskew_new_curved( polygons_of_marginals, textline_mask_tot_ea_erode, boxes_marginals, - num_col_classifier, scale_param, slope_deskew, image['name']) + num_col_classifier, slope_deskew, image['name']) all_found_textline_polygons_marginals = small_textlines_to_parent_adherence2( all_found_textline_polygons_marginals, textline_mask_tot_ea, num_col_classifier) (polygons_of_textregions, diff --git a/src/eynollah/utils/separate_lines.py b/src/eynollah/utils/separate_lines.py index f1a46b5..b701490 100644 --- a/src/eynollah/utils/separate_lines.py +++ b/src/eynollah/utils/separate_lines.py @@ -6,6 +6,7 @@ import cv2 from scipy.signal import find_peaks from scipy.ndimage import gaussian_filter1d from .rotate import rotate_image +from scipy.stats import linregress from .resize import resize_image from .contour import ( return_parent_contours, @@ -13,6 +14,7 @@ from .contour import ( return_contours_of_image, filter_contours_area_of_image, return_contours_of_interested_textline, + find_center_of_contours, find_contours_mean_y_diff, ) from . import ( @@ -1585,8 +1587,10 @@ def get_smallest_skew(img, sigma_des, angles, logger=None, plotter=None, name=No def do_work_of_slopes_new_curved( box_text, contour_par, textline_mask_tot_ea=None, - num_col=1, scale_par=1.0, slope_deskew=0.0, - logger=None, MAX_SLOPE=999, KERNEL=None, plotter=None, name=None + num_col=1, slope_deskew=0.0, + logger=None, MAX_SLOPE=999, + KERNEL=None, plotter=None, + name=None, ): if KERNEL is None: KERNEL = np.ones((5, 5), np.uint8) @@ -1595,38 +1599,67 @@ def do_work_of_slopes_new_curved( logger.debug("enter do_work_of_slopes_new_curved") x, y, w, h = box_text - all_text_region_raw = textline_mask_tot_ea[y: y + h, x: x + w].astype(np.uint8) - img_int_p = all_text_region_raw[:, :] + + mask_parent = np.zeros((h, w), dtype=np.uint8) + mask_parent = cv2.fillPoly(mask_parent, pts=[contour_par - [x, y]], color=1) + all_text_region_raw = textline_mask_tot_ea[y: y + h, x: x + w] * mask_parent + if not np.any(all_text_region_raw): + return [], slope_deskew + img_int_p = np.copy(all_text_region_raw) # img_int_p=cv2.erode(img_int_p,KERNEL,iterations = 2) # plt.imshow(img_int_p) # plt.show() if not np.prod(img_int_p.shape) or img_int_p.shape[0] / img_int_p.shape[1] < 0.1: - slope = 0 - slope_for_all = slope_deskew + slope = slope_deskew else: try: textline_con, hierarchy = return_contours_of_image(img_int_p) textline_con_fil = filter_contours_area_of_image(img_int_p, textline_con, hierarchy, max_area=1, min_area=0.0008) - y_diff_mean = find_contours_mean_y_diff(textline_con_fil) if len(textline_con_fil) > 1 else np.NaN - if np.isnan(y_diff_mean): - slope_for_all = MAX_SLOPE + if len(textline_con_fil) > 1: + cx, cy = find_center_of_contours(textline_con_fil) + y_diff_mean = np.median(np.diff(np.sort(np.array(cy)))) + x_diff_mean = np.median(np.diff(np.sort(np.array(cx)))) + if h > w and x_diff_mean / w > 2 * y_diff_mean / h: + # print(len(textline_con_fil), "transposed", x_diff_mean, y_diff_mean) + transposed = True + img_int_p = img_int_p.T + sigma = x_diff_mean + else: + transposed = False + sigma = y_diff_mean + slope = return_deskew_slop(img_int_p, max(1.0, 0.1 * sigma), + logger=logger, + name=name, + plotter=plotter) + if transposed: + slope = -90 - slope if slope < 0 else 90 - slope + if abs(slope - slope_deskew) < 0.5: + slope = slope_deskew else: - sigma_des = max(1, int(y_diff_mean * (4.0 / 40.0))) - img_int_p[img_int_p > 0] = 1 - slope_for_all = return_deskew_slop(img_int_p, sigma_des, logger=logger, name=name, plotter=plotter) - if abs(slope_for_all) < 0.5: - slope_for_all = slope_deskew + if h > 3 * w: + # print(1, "transposed", h, w) + transposed = True + img_int_p = img_int_p.T + else: + transposed = False + # do linear regression on mask to get slope + img_ys, img_xs = img_int_p.nonzero() + regression = linregress(x=img_xs, y=img_ys) + slope = 180 / np.pi * np.arctan(regression.slope) + # print(regression, regression.stderr) + if transposed: + slope = 90 - slope + if regression.stderr > 0.005: + slope = slope_deskew except: logger.exception("cannot determine angle of contours") - slope_for_all = MAX_SLOPE + slope = slope_deskew - if slope_for_all == MAX_SLOPE: - slope_for_all = slope_deskew - slope = slope_for_all + # print(slope, slope_deskew) crop_coor = box2rect(box_text)