dilate_textregions_contours: simplify (via shapely's Polygon.buffer()), ensure validity

This commit is contained in:
Robert Sachunsky 2025-08-19 11:58:45 +02:00
parent a2359ea4c4
commit f994ea5f0b
3 changed files with 33 additions and 104 deletions

View file

@ -27,6 +27,7 @@ from loky import ProcessPoolExecutor
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
import cv2 import cv2
import numpy as np import numpy as np
from shapely.geometry import Polygon
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 numba import cuda from numba import cuda
@ -68,6 +69,7 @@ from .utils.contour import (
get_text_region_boxes_by_given_contours, get_text_region_boxes_by_given_contours,
get_textregion_contours_in_org_image, get_textregion_contours_in_org_image,
get_textregion_contours_in_org_image_light, get_textregion_contours_in_org_image_light,
make_valid,
return_contours_of_image, return_contours_of_image,
return_contours_of_interested_region, return_contours_of_interested_region,
return_contours_of_interested_region_by_min_size, return_contours_of_interested_region_by_min_size,
@ -3774,107 +3776,9 @@ class Eynollah:
return all_found_textline_polygons return all_found_textline_polygons
def dilate_textregions_contours(self, all_found_textline_polygons): def dilate_textregions_contours(self, all_found_textline_polygons):
#print(all_found_textline_polygons) return [np.array(make_valid(Polygon(poly[:, 0])).buffer(5).exterior.coords,
for j in range(len(all_found_textline_polygons)): dtype=int)[:, np.newaxis]
con_ind = all_found_textline_polygons[j] for poly in all_found_textline_polygons]
#print(len(con_ind[:,0,0]),'con_ind[:,0,0]')
area = cv2.contourArea(con_ind)
con_ind = con_ind.astype(float)
x_differential = np.diff( con_ind[:,0,0])
y_differential = np.diff( con_ind[:,0,1])
x_differential = gaussian_filter1d(x_differential, 0.1)
y_differential = gaussian_filter1d(y_differential, 0.1)
x_min = float(np.min( con_ind[:,0,0] ))
y_min = float(np.min( con_ind[:,0,1] ))
x_max = float(np.max( con_ind[:,0,0] ))
y_max = float(np.max( con_ind[:,0,1] ))
x_differential_mask_nonzeros = [ ind/abs(ind) if ind!=0 else ind for ind in x_differential]
y_differential_mask_nonzeros = [ ind/abs(ind) if ind!=0 else ind for ind in y_differential]
abs_diff=abs(abs(x_differential)- abs(y_differential) )
inc_x = np.zeros(len(x_differential)+1)
inc_y = np.zeros(len(x_differential)+1)
if (y_max-y_min) <= (x_max-x_min):
dilation_m1 = round(area / (x_max-x_min) * 0.12)
else:
dilation_m1 = round(area / (y_max-y_min) * 0.12)
if dilation_m1>8:
dilation_m1 = 8
if dilation_m1<6:
dilation_m1 = 6
#print(dilation_m1, 'dilation_m1')
dilation_m1 = 6
dilation_m2 = int(dilation_m1/2.) +1
for i in range(len(x_differential)):
if abs_diff[i]==0:
inc_x[i+1] = dilation_m2*(-1*y_differential_mask_nonzeros[i])
inc_y[i+1] = dilation_m2*(x_differential_mask_nonzeros[i])
elif abs_diff[i]!=0 and x_differential_mask_nonzeros[i]==0 and y_differential_mask_nonzeros[i]!=0:
inc_x[i+1]= dilation_m1*(-1*y_differential_mask_nonzeros[i])
elif abs_diff[i]!=0 and x_differential_mask_nonzeros[i]!=0 and y_differential_mask_nonzeros[i]==0:
inc_y[i+1] = dilation_m1*(x_differential_mask_nonzeros[i])
elif abs_diff[i]!=0 and abs_diff[i]>=3:
if abs(x_differential[i])>abs(y_differential[i]):
inc_y[i+1] = dilation_m1*(x_differential_mask_nonzeros[i])
else:
inc_x[i+1]= dilation_m1*(-1*y_differential_mask_nonzeros[i])
else:
inc_x[i+1] = dilation_m2*(-1*y_differential_mask_nonzeros[i])
inc_y[i+1] = dilation_m2*(x_differential_mask_nonzeros[i])
inc_x[0] = inc_x[-1]
inc_y[0] = inc_y[-1]
con_scaled = con_ind*1
con_scaled[:,0, 0] = con_ind[:,0,0] + np.array(inc_x)[:]
con_scaled[:,0, 1] = con_ind[:,0,1] + np.array(inc_y)[:]
con_scaled[:,0, 1][con_scaled[:,0, 1]<0] = 0
con_scaled[:,0, 0][con_scaled[:,0, 0]<0] = 0
area_scaled = cv2.contourArea(con_scaled.astype(np.int32))
con_ind = con_ind.astype(np.int32)
results = [cv2.pointPolygonTest(con_ind, (con_scaled[ind,0, 0], con_scaled[ind,0, 1]), False)
for ind in range(len(con_scaled[:,0, 1])) ]
results = np.array(results)
#print(results,'results')
results[results==0] = 1
diff_result = np.diff(results)
indices_2 = [ind for ind in range(len(diff_result)) if diff_result[ind]==2]
indices_m2 = [ind for ind in range(len(diff_result)) if diff_result[ind]==-2]
if results[0]==1:
con_scaled[:indices_m2[0]+1,0, 1] = con_ind[:indices_m2[0]+1,0,1]
con_scaled[:indices_m2[0]+1,0, 0] = con_ind[:indices_m2[0]+1,0,0]
#indices_2 = indices_2[1:]
indices_m2 = indices_m2[1:]
if len(indices_2)>len(indices_m2):
con_scaled[indices_2[-1]+1:,0, 1] = con_ind[indices_2[-1]+1:,0,1]
con_scaled[indices_2[-1]+1:,0, 0] = con_ind[indices_2[-1]+1:,0,0]
indices_2 = indices_2[:-1]
for ii in range(len(indices_2)):
con_scaled[indices_2[ii]+1:indices_m2[ii]+1,0, 1] = con_scaled[indices_2[ii],0, 1]
con_scaled[indices_2[ii]+1:indices_m2[ii]+1,0, 0] = con_scaled[indices_2[ii],0, 0]
all_found_textline_polygons[j][:,0,1] = con_scaled[:,0, 1]
all_found_textline_polygons[j][:,0,0] = con_scaled[:,0, 0]
return all_found_textline_polygons
def dilate_textline_contours(self, all_found_textline_polygons): def dilate_textline_contours(self, all_found_textline_polygons):
for j in range(len(all_found_textline_polygons)): for j in range(len(all_found_textline_polygons)):

View file

@ -2156,6 +2156,7 @@ def return_boxes_of_images_by_order_of_reading_new(
x_end_itself=x_end_copy.pop(il) x_end_itself=x_end_copy.pop(il)
#print(y_copy,'y_copy2') #print(y_copy,'y_copy2')
# FIXME: cannot convert numpy.float64 to integer...
for column in range(x_start_itself, x_end_itself+1): for column in range(x_start_itself, x_end_itself+1):
#print(column,'cols') #print(column,'cols')
y_in_cols=[] y_in_cols=[]

View file

@ -1,7 +1,7 @@
from functools import partial from functools import partial
import cv2 import cv2
import numpy as np import numpy as np
from shapely import geometry from shapely.geometry import Polygon
from .rotate import rotate_image, rotation_image_new from .rotate import rotate_image, rotation_image_new
@ -43,7 +43,7 @@ def filter_contours_area_of_image(image, contours, hierarchy, max_area, min_area
if len(c) < 3: # A polygon cannot have less than 3 points if len(c) < 3: # A polygon cannot have less than 3 points
continue continue
polygon = geometry.Polygon([point[0] for point in c]) polygon = Polygon([point[0] for point in c])
area = polygon.area area = polygon.area
if (area >= min_area * np.prod(image.shape[:2]) and if (area >= min_area * np.prod(image.shape[:2]) and
area <= max_area * np.prod(image.shape[:2]) and area <= max_area * np.prod(image.shape[:2]) and
@ -58,7 +58,7 @@ def filter_contours_area_of_image_tables(image, contours, hierarchy, max_area, m
if len(c) < 3: # A polygon cannot have less than 3 points if len(c) < 3: # A polygon cannot have less than 3 points
continue continue
polygon = geometry.Polygon([point[0] for point in c]) polygon = Polygon([point[0] for point in c])
# area = cv2.contourArea(c) # area = cv2.contourArea(c)
area = polygon.area area = polygon.area
##print(np.prod(thresh.shape[:2])) ##print(np.prod(thresh.shape[:2]))
@ -332,3 +332,27 @@ def return_contours_of_interested_region_by_size(region_pre_p, pixel, min_area,
return img_ret[:, :, 0] return img_ret[:, :, 0]
def make_valid(polygon: Polygon) -> Polygon:
"""Ensures shapely.geometry.Polygon object is valid by repeated rearrangement/simplification/enlargement."""
points = list(polygon.exterior.coords)
# try by re-arranging points
for split in range(1, len(points)):
if polygon.is_valid or polygon.simplify(polygon.area).is_valid:
break
# simplification may not be possible (at all) due to ordering
# in that case, try another starting point
polygon = Polygon(points[-split:]+points[:-split])
# try by simplification
for tolerance in range(int(polygon.area + 1.5)):
if polygon.is_valid:
break
# simplification may require a larger tolerance
polygon = polygon.simplify(tolerance + 1)
# try by enlarging
for tolerance in range(1, int(polygon.area + 2.5)):
if polygon.is_valid:
break
# enlargement may require a larger tolerance
polygon = polygon.buffer(tolerance)
assert polygon.is_valid, polygon.wkt
return polygon