|
|
@@ -565,6 +565,203 @@ class GridRecovery:
|
|
|
return grid_lines
|
|
|
|
|
|
@staticmethod
|
|
|
+ def _add_implicit_dividers_from_large_cells(
|
|
|
+ bboxes: List[List[float]],
|
|
|
+ row_dividers: List[float],
|
|
|
+ col_dividers: List[float],
|
|
|
+ min_gap_ratio: float = 0.1,
|
|
|
+ min_span_ratio: float = 2.5,
|
|
|
+ tolerance: float = 5.0
|
|
|
+ ) -> tuple[List[float], List[float]]:
|
|
|
+ """
|
|
|
+ 从大框边界补充隐式分割线
|
|
|
+
|
|
|
+ 1. 优先处理边界单元格(表格的最顶部、最底部、最左侧、最右侧):
|
|
|
+ 无条件添加边界单元格的边界到dividers中,确保表格边界完整。
|
|
|
+ 这解决了由于find_grid_lines的min_support限制导致的边界缺失问题。
|
|
|
+
|
|
|
+ 2. 检测跨多列/多行的大框,如果其边界与已有分割线距离超过阈值,
|
|
|
+ 则添加隐式分割线,避免大框被错误归并到已有行/列。
|
|
|
+
|
|
|
+ Args:
|
|
|
+ bboxes: 所有单元格 bbox 列表
|
|
|
+ row_dividers: 已有行分割线(已排序)
|
|
|
+ col_dividers: 已有列分割线(已排序)
|
|
|
+ min_gap_ratio: 最小间距比例(相对于平均行高/列宽)
|
|
|
+ min_span_ratio: 最小跨列/行比例(相对于平均宽度/高度)
|
|
|
+ tolerance: 坐标容差(像素)
|
|
|
+
|
|
|
+ Returns:
|
|
|
+ (补充后的行分割线, 补充后的列分割线),已去重并排序
|
|
|
+ """
|
|
|
+ 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)
|
|
|
+ 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))
|
|
|
+
|
|
|
+ return new_row_dividers, new_col_dividers
|
|
|
+
|
|
|
+ @staticmethod
|
|
|
def recover_grid_structure(bboxes: List[List[float]]) -> List[Dict]:
|
|
|
"""
|
|
|
从散乱的单元格 bbox 恢复表格的行列结构 (row, col, rowspan, colspan)
|
|
|
@@ -586,14 +783,22 @@ class GridRecovery:
|
|
|
y_coords.append(b[1])
|
|
|
y_coords.append(b[3])
|
|
|
|
|
|
- row_dividers = GridRecovery.find_grid_lines(y_coords, tolerance=5, min_support=2)
|
|
|
+ row_dividers_raw = GridRecovery.find_grid_lines(y_coords, tolerance=5, min_support=2)
|
|
|
|
|
|
# 2. 识别列分割线 (X轴)
|
|
|
x_coords = []
|
|
|
for b in bboxes:
|
|
|
x_coords.append(b[0])
|
|
|
x_coords.append(b[2])
|
|
|
- col_dividers = GridRecovery.find_grid_lines(x_coords, tolerance=5, min_support=2)
|
|
|
+ col_dividers_raw = GridRecovery.find_grid_lines(x_coords, tolerance=5, min_support=2)
|
|
|
+
|
|
|
+ # 2.5. 从大框边界补充隐式分割线
|
|
|
+ row_dividers, col_dividers = GridRecovery._add_implicit_dividers_from_large_cells(
|
|
|
+ bboxes, row_dividers_raw, col_dividers_raw,
|
|
|
+ min_gap_ratio=0.1, # 最小间距比例(相对于平均行高/列宽)
|
|
|
+ min_span_ratio=2.5, # 跨2.5倍平均宽度/高度以上才考虑
|
|
|
+ tolerance=5.0
|
|
|
+ )
|
|
|
|
|
|
# 3. 构建网格结构
|
|
|
structured_cells = []
|