do_work_of_slopes_new_curved: improve deskewing…

- 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
This commit is contained in:
Robert Sachunsky 2026-04-23 20:08:40 +02:00
parent 97d9b0ea50
commit 0dce1f24d2
2 changed files with 54 additions and 23 deletions

View file

@ -1034,14 +1034,13 @@ class Eynollah:
slopes) slopes)
def get_slopes_and_deskew_new_curved(self, contours_par, textline_mask_tot, boxes, 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): if not len(contours_par):
return [], [], [] return [], [], []
self.logger.debug("enter get_slopes_and_deskew_new_curved") self.logger.debug("enter get_slopes_and_deskew_new_curved")
results = map(partial(do_work_of_slopes_new_curved, results = map(partial(do_work_of_slopes_new_curved,
textline_mask_tot_ea=textline_mask_tot, textline_mask_tot_ea=textline_mask_tot,
num_col=num_col, num_col=num_col,
scale_par=scale_par,
slope_deskew=slope_deskew, slope_deskew=slope_deskew,
MAX_SLOPE=MAX_SLOPE, MAX_SLOPE=MAX_SLOPE,
KERNEL=KERNEL, KERNEL=KERNEL,
@ -2503,20 +2502,19 @@ class Eynollah:
all_found_textline_polygons_marginals = dilate_textline_contours( all_found_textline_polygons_marginals = dilate_textline_contours(
all_found_textline_polygons_marginals) all_found_textline_polygons_marginals)
else: else:
scale_param = 1
textline_mask_tot_ea_erode = cv2.erode(textline_mask_tot_ea, kernel=KERNEL, iterations=2) textline_mask_tot_ea_erode = cv2.erode(textline_mask_tot_ea, kernel=KERNEL, iterations=2)
all_found_textline_polygons, \ all_found_textline_polygons, \
all_box_coord, slopes = self.get_slopes_and_deskew_new_curved( all_box_coord, slopes = self.get_slopes_and_deskew_new_curved(
polygons_of_textregions, textline_mask_tot_ea_erode, polygons_of_textregions, textline_mask_tot_ea_erode,
boxes_text, 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 = small_textlines_to_parent_adherence2(
all_found_textline_polygons, textline_mask_tot_ea, num_col_classifier) all_found_textline_polygons, textline_mask_tot_ea, num_col_classifier)
all_found_textline_polygons_marginals, \ all_found_textline_polygons_marginals, \
all_box_coord_marginals, slopes_marginals = self.get_slopes_and_deskew_new_curved( all_box_coord_marginals, slopes_marginals = self.get_slopes_and_deskew_new_curved(
polygons_of_marginals, textline_mask_tot_ea_erode, polygons_of_marginals, textline_mask_tot_ea_erode,
boxes_marginals, 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 = small_textlines_to_parent_adherence2(
all_found_textline_polygons_marginals, textline_mask_tot_ea, num_col_classifier) all_found_textline_polygons_marginals, textline_mask_tot_ea, num_col_classifier)
(polygons_of_textregions, (polygons_of_textregions,

View file

@ -6,6 +6,7 @@ import cv2
from scipy.signal import find_peaks from scipy.signal import find_peaks
from scipy.ndimage import gaussian_filter1d from scipy.ndimage import gaussian_filter1d
from .rotate import rotate_image from .rotate import rotate_image
from scipy.stats import linregress
from .resize import resize_image from .resize import resize_image
from .contour import ( from .contour import (
return_parent_contours, return_parent_contours,
@ -13,6 +14,7 @@ from .contour import (
return_contours_of_image, return_contours_of_image,
filter_contours_area_of_image, filter_contours_area_of_image,
return_contours_of_interested_textline, return_contours_of_interested_textline,
find_center_of_contours,
find_contours_mean_y_diff, find_contours_mean_y_diff,
) )
from . import ( 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( def do_work_of_slopes_new_curved(
box_text, contour_par, box_text, contour_par,
textline_mask_tot_ea=None, textline_mask_tot_ea=None,
num_col=1, scale_par=1.0, slope_deskew=0.0, num_col=1, slope_deskew=0.0,
logger=None, MAX_SLOPE=999, KERNEL=None, plotter=None, name=None logger=None, MAX_SLOPE=999,
KERNEL=None, plotter=None,
name=None,
): ):
if KERNEL is None: if KERNEL is None:
KERNEL = np.ones((5, 5), np.uint8) 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") logger.debug("enter do_work_of_slopes_new_curved")
x, y, w, h = box_text 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) # img_int_p=cv2.erode(img_int_p,KERNEL,iterations = 2)
# plt.imshow(img_int_p) # plt.imshow(img_int_p)
# plt.show() # plt.show()
if not np.prod(img_int_p.shape) or img_int_p.shape[0] / img_int_p.shape[1] < 0.1: if not np.prod(img_int_p.shape) or img_int_p.shape[0] / img_int_p.shape[1] < 0.1:
slope = 0 slope = slope_deskew
slope_for_all = slope_deskew
else: else:
try: try:
textline_con, hierarchy = return_contours_of_image(img_int_p) textline_con, hierarchy = return_contours_of_image(img_int_p)
textline_con_fil = filter_contours_area_of_image(img_int_p, textline_con, textline_con_fil = filter_contours_area_of_image(img_int_p, textline_con,
hierarchy, hierarchy,
max_area=1, min_area=0.0008) 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 len(textline_con_fil) > 1:
if np.isnan(y_diff_mean): cx, cy = find_center_of_contours(textline_con_fil)
slope_for_all = MAX_SLOPE 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: else:
sigma_des = max(1, int(y_diff_mean * (4.0 / 40.0))) if h > 3 * w:
img_int_p[img_int_p > 0] = 1 # print(1, "transposed", h, w)
slope_for_all = return_deskew_slop(img_int_p, sigma_des, logger=logger, name=name, plotter=plotter) transposed = True
if abs(slope_for_all) < 0.5: img_int_p = img_int_p.T
slope_for_all = slope_deskew 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: except:
logger.exception("cannot determine angle of contours") logger.exception("cannot determine angle of contours")
slope_for_all = MAX_SLOPE slope = slope_deskew
if slope_for_all == MAX_SLOPE: # print(slope, slope_deskew)
slope_for_all = slope_deskew
slope = slope_for_all
crop_coor = box2rect(box_text) crop_coor = box2rect(box_text)