|
|
@@ -574,14 +574,20 @@ class GridRecovery:
|
|
|
tolerance: float = 5.0
|
|
|
) -> tuple[List[float], List[float]]:
|
|
|
"""
|
|
|
- 从大框边界补充隐式分割线
|
|
|
-
|
|
|
- 1. 优先处理边界单元格(表格的最顶部、最底部、最左侧、最右侧):
|
|
|
- 无条件添加边界单元格的边界到dividers中,确保表格边界完整。
|
|
|
- 这解决了由于find_grid_lines的min_support限制导致的边界缺失问题。
|
|
|
-
|
|
|
- 2. 检测跨多列/多行的大框,如果其边界与已有分割线距离超过阈值,
|
|
|
- 则添加隐式分割线,避免大框被错误归并到已有行/列。
|
|
|
+ 补充分割线(简化版)
|
|
|
+
|
|
|
+ 前提:连通域/矢量重构阶段已保证网格结构正确。
|
|
|
+ 因此这里不再做“大框推断/出现次数(min_support)推断”,只做两件事:
|
|
|
+
|
|
|
+ 1) **补全最外边界线**:确保 top/bottom/left/right 边界线存在
|
|
|
+ 2) **近线合并**:若两条线距离 <= tolerance,则合并为一条(取均值)
|
|
|
+
|
|
|
+ 说明:
|
|
|
+ - 对于 Y 方向(行):强制使用 min(all_tops) / max(all_bottoms) 作为外边界
|
|
|
+ (可解决你提到的 1511.0 这种只出现一次但是真实边界没被保留的问题)
|
|
|
+ - 对于 X 方向(列):为避免极少数离群 bbox 把 left/right 拉偏导致“多出空白列”,
|
|
|
+ 默认使用 5%/95% 分位数作为列外边界;若 min/max 与分位数足够接近(<= tolerance),
|
|
|
+ 则退化为 min/max。
|
|
|
|
|
|
Args:
|
|
|
bboxes: 所有单元格 bbox 列表
|
|
|
@@ -596,169 +602,51 @@ class GridRecovery:
|
|
|
"""
|
|
|
if not bboxes:
|
|
|
return row_dividers, col_dividers
|
|
|
-
|
|
|
- # 计算平均宽度和高度
|
|
|
- widths = [b[2] - b[0] for b in bboxes]
|
|
|
- heights = [b[3] - b[1] for b in bboxes]
|
|
|
- avg_width = sum(widths) / len(widths) if widths else 0
|
|
|
- avg_height = sum(heights) / len(heights) if heights else 0
|
|
|
-
|
|
|
- if avg_width == 0 or avg_height == 0:
|
|
|
- return row_dividers, col_dividers
|
|
|
-
|
|
|
- new_row_dividers = list(row_dividers)
|
|
|
- new_col_dividers = list(col_dividers)
|
|
|
-
|
|
|
- # 计算表格的整体边界(用于判断大框是否跨大部分列/行)
|
|
|
- if bboxes:
|
|
|
- all_lefts = [b[0] for b in bboxes]
|
|
|
- all_rights = [b[2] for b in bboxes]
|
|
|
- all_tops = [b[1] for b in bboxes]
|
|
|
- all_bottoms = [b[3] for b in bboxes]
|
|
|
- table_left = min(all_lefts)
|
|
|
- table_right = max(all_rights)
|
|
|
- table_top = min(all_tops)
|
|
|
- table_bottom = max(all_bottoms)
|
|
|
- table_width = table_right - table_left
|
|
|
- table_height = table_bottom - table_top
|
|
|
- else:
|
|
|
- table_width = table_height = 0
|
|
|
-
|
|
|
- # 检测大框并添加隐式分割线
|
|
|
- # 边界单元格的容差(允许小误差)
|
|
|
- boundary_tolerance = 2.0
|
|
|
-
|
|
|
- for bbox in bboxes:
|
|
|
- b_left, b_top, b_right, b_bottom = bbox[0], bbox[1], bbox[2], bbox[3]
|
|
|
- b_width = b_right - b_left
|
|
|
- b_height = b_bottom - b_top
|
|
|
-
|
|
|
- # 判断是否是边界单元格(表格的最顶部、最底部、最左侧、最右侧)
|
|
|
- is_top_boundary = abs(b_top - table_top) < boundary_tolerance
|
|
|
- is_bottom_boundary = abs(b_bottom - table_bottom) < boundary_tolerance
|
|
|
- is_left_boundary = abs(b_left - table_left) < boundary_tolerance
|
|
|
- is_right_boundary = abs(b_right - table_right) < boundary_tolerance
|
|
|
-
|
|
|
- # 处理边界单元格的行边界
|
|
|
- if is_top_boundary or is_bottom_boundary:
|
|
|
- if row_dividers:
|
|
|
- if is_top_boundary:
|
|
|
- closest_top = min(row_dividers, key=lambda y: abs(y - b_top))
|
|
|
- if abs(closest_top - b_top) > tolerance:
|
|
|
- new_row_dividers.append(b_top)
|
|
|
- if is_bottom_boundary:
|
|
|
- closest_bottom = min(row_dividers, key=lambda y: abs(y - b_bottom))
|
|
|
- if abs(closest_bottom - b_bottom) > tolerance:
|
|
|
- new_row_dividers.append(b_bottom)
|
|
|
- else:
|
|
|
- # 如果没有已有分割线,直接添加
|
|
|
- if is_top_boundary:
|
|
|
- new_row_dividers.append(b_top)
|
|
|
- if is_bottom_boundary:
|
|
|
- new_row_dividers.append(b_bottom)
|
|
|
-
|
|
|
- # 处理边界单元格的列边界
|
|
|
- if is_left_boundary or is_right_boundary:
|
|
|
- if col_dividers:
|
|
|
- if is_left_boundary:
|
|
|
- closest_left = min(col_dividers, key=lambda x: abs(x - b_left))
|
|
|
- if abs(closest_left - b_left) > tolerance:
|
|
|
- new_col_dividers.append(b_left)
|
|
|
- if is_right_boundary:
|
|
|
- closest_right = min(col_dividers, key=lambda x: abs(x - b_right))
|
|
|
- if abs(closest_right - b_right) > tolerance:
|
|
|
- new_col_dividers.append(b_right)
|
|
|
+
|
|
|
+ def _merge_close_lines(lines: List[float], tol: float) -> List[float]:
|
|
|
+ """按容差合并近似重复网格线(替代 set(),避免浮点近似导致多出窄列/窄行)"""
|
|
|
+ if not lines:
|
|
|
+ return []
|
|
|
+ lines_sorted = sorted(float(x) for x in lines)
|
|
|
+ merged: List[float] = []
|
|
|
+ cluster: List[float] = [lines_sorted[0]]
|
|
|
+ for x in lines_sorted[1:]:
|
|
|
+ if abs(x - cluster[-1]) <= tol:
|
|
|
+ cluster.append(x)
|
|
|
else:
|
|
|
- # 如果没有已有分割线,直接添加
|
|
|
- if is_left_boundary:
|
|
|
- new_col_dividers.append(b_left)
|
|
|
- if is_right_boundary:
|
|
|
- new_col_dividers.append(b_right)
|
|
|
-
|
|
|
- # # 判断是否是大框(跨多列或跨多行)
|
|
|
- # is_large_col = b_width >= avg_width * min_span_ratio
|
|
|
- # is_large_row = b_height >= avg_height * min_span_ratio
|
|
|
-
|
|
|
- # # 判断是否跨大部分表格宽度/高度(用于更激进的添加分割线)
|
|
|
- # spans_most_cols = table_width > 0 and b_width >= table_width * 0.8
|
|
|
- # spans_most_rows = table_height > 0 and b_height >= table_height * 0.8
|
|
|
-
|
|
|
- # # 处理行分割线(检查大框的顶部和底部)
|
|
|
- # if is_large_row or spans_most_rows:
|
|
|
- # min_gap = avg_height * min_gap_ratio
|
|
|
-
|
|
|
- # # 检查顶部边界
|
|
|
- # if row_dividers:
|
|
|
- # closest_top = min(row_dividers, key=lambda y: abs(y - b_top))
|
|
|
- # gap_top = abs(closest_top - b_top)
|
|
|
- # # 对于跨大部分列的大框,如果顶部明显在已有分割线之下,即使距离小也添加
|
|
|
- # # 判断标准:b_top > closest_top(在下方)且距离 > 0.5 像素
|
|
|
- # is_below_existing = b_top > closest_top and gap_top > 0.5
|
|
|
- # # 对于跨大部分行的大框,只要距离 > tolerance 就添加
|
|
|
- # # 对于普通大框,需要同时满足 > tolerance 和 > min_gap
|
|
|
- # should_add_top = (spans_most_cols and is_below_existing) or \
|
|
|
- # (spans_most_rows and gap_top > tolerance) or \
|
|
|
- # (gap_top > tolerance and gap_top > min_gap)
|
|
|
- # if should_add_top:
|
|
|
- # new_row_dividers.append(b_top)
|
|
|
- # else:
|
|
|
- # # 如果没有已有分割线,直接添加
|
|
|
- # new_row_dividers.append(b_top)
|
|
|
-
|
|
|
- # # 检查底部边界
|
|
|
- # if row_dividers:
|
|
|
- # closest_bottom = min(row_dividers, key=lambda y: abs(y - b_bottom))
|
|
|
- # gap_bottom = abs(closest_bottom - b_bottom)
|
|
|
- # # 对于跨大部分列的大框,如果底部明显在已有分割线之上,即使距离小也添加
|
|
|
- # # 判断标准:b_bottom < closest_bottom(在上方)且距离 > 0.5 像素
|
|
|
- # is_above_existing = b_bottom < closest_bottom and gap_bottom > 0.5
|
|
|
- # # 对于跨大部分行的大框,只要距离 > tolerance 就添加
|
|
|
- # # 对于普通大框,需要同时满足 > tolerance 和 > min_gap
|
|
|
- # should_add_bottom = (spans_most_cols and is_above_existing) or \
|
|
|
- # (spans_most_rows and gap_bottom > tolerance) or \
|
|
|
- # (gap_bottom > tolerance and gap_bottom > min_gap)
|
|
|
- # if should_add_bottom:
|
|
|
- # new_row_dividers.append(b_bottom)
|
|
|
- # else:
|
|
|
- # # 如果没有已有分割线,直接添加
|
|
|
- # new_row_dividers.append(b_bottom)
|
|
|
-
|
|
|
- # # 处理列分割线(检查大框的左侧和右侧)
|
|
|
- # if is_large_col or spans_most_cols:
|
|
|
- # min_gap = avg_width * min_gap_ratio
|
|
|
-
|
|
|
- # # 检查左侧边界
|
|
|
- # if col_dividers:
|
|
|
- # closest_left = min(col_dividers, key=lambda x: abs(x - b_left))
|
|
|
- # gap_left = abs(closest_left - b_left)
|
|
|
- # # 对于跨大部分列的大框,只要距离 > tolerance 就添加
|
|
|
- # # 对于普通大框,需要同时满足 > tolerance 和 > min_gap
|
|
|
- # should_add_left = (spans_most_cols and gap_left > tolerance) or \
|
|
|
- # (gap_left > tolerance and gap_left > min_gap)
|
|
|
- # if should_add_left:
|
|
|
- # new_col_dividers.append(b_left)
|
|
|
- # else:
|
|
|
- # # 如果没有已有分割线,直接添加
|
|
|
- # new_col_dividers.append(b_left)
|
|
|
-
|
|
|
- # # 检查右侧边界
|
|
|
- # if col_dividers:
|
|
|
- # closest_right = min(col_dividers, key=lambda x: abs(x - b_right))
|
|
|
- # gap_right = abs(closest_right - b_right)
|
|
|
- # # 对于跨大部分列的大框,只要距离 > tolerance 就添加
|
|
|
- # # 对于普通大框,需要同时满足 > tolerance 和 > min_gap
|
|
|
- # should_add_right = (spans_most_cols and gap_right > tolerance) or \
|
|
|
- # (gap_right > tolerance and gap_right > min_gap)
|
|
|
- # if should_add_right:
|
|
|
- # new_col_dividers.append(b_right)
|
|
|
- # else:
|
|
|
- # # 如果没有已有分割线,直接添加
|
|
|
- # new_col_dividers.append(b_right)
|
|
|
-
|
|
|
- # 去重并排序
|
|
|
- new_row_dividers = sorted(set(new_row_dividers))
|
|
|
- new_col_dividers = sorted(set(new_col_dividers))
|
|
|
-
|
|
|
+ merged.append(sum(cluster) / len(cluster))
|
|
|
+ cluster = [x]
|
|
|
+ merged.append(sum(cluster) / len(cluster))
|
|
|
+ return merged
|
|
|
+
|
|
|
+ # 统计 bbox 边界
|
|
|
+ all_lefts = [b[0] for b in bboxes]
|
|
|
+ all_rights = [b[2] for b in bboxes]
|
|
|
+ all_tops = [b[1] for b in bboxes]
|
|
|
+ all_bottoms = [b[3] for b in bboxes]
|
|
|
+
|
|
|
+ hard_top = float(min(all_tops))
|
|
|
+ hard_bottom = float(max(all_bottoms))
|
|
|
+
|
|
|
+ # 列边界:默认用分位数抵抗离群点(避免多出空白列)
|
|
|
+ lefts_sorted = sorted(float(x) for x in all_lefts)
|
|
|
+ rights_sorted = sorted(float(x) for x in all_rights)
|
|
|
+ q05 = int(round((len(lefts_sorted) - 1) * 0.05))
|
|
|
+ q95 = int(round((len(rights_sorted) - 1) * 0.95))
|
|
|
+ q05 = max(0, min(q05, len(lefts_sorted) - 1))
|
|
|
+ q95 = max(0, min(q95, len(rights_sorted) - 1))
|
|
|
+ robust_left = lefts_sorted[q05]
|
|
|
+ robust_right = rights_sorted[q95]
|
|
|
+ hard_left = float(min(all_lefts))
|
|
|
+ hard_right = float(max(all_rights))
|
|
|
+ # 如果 min/max 与分位数很接近(<= tolerance),说明不存在明显离群点,则使用 min/max
|
|
|
+ col_left = hard_left if abs(hard_left - robust_left) <= tolerance else robust_left
|
|
|
+ col_right = hard_right if abs(hard_right - robust_right) <= tolerance else robust_right
|
|
|
+
|
|
|
+ # 仅做:补边界 + 合并近线
|
|
|
+ new_row_dividers = _merge_close_lines(list(row_dividers) + [hard_top, hard_bottom], tol=tolerance)
|
|
|
+ new_col_dividers = _merge_close_lines(list(col_dividers) + [col_left, col_right], tol=tolerance)
|
|
|
+
|
|
|
return new_row_dividers, new_col_dividers
|
|
|
|
|
|
@staticmethod
|