|
|
@@ -0,0 +1,690 @@
|
|
|
+#!/usr/bin/env python3
|
|
|
+"""
|
|
|
+调试脚本:追踪 table_body 重叠处理算法的执行过程
|
|
|
+
|
|
|
+此脚本模拟 _remove_overlapping_boxes 方法的执行,添加详细的日志输出
|
|
|
+用于调试 table_body 被错误移除的问题。
|
|
|
+"""
|
|
|
+import sys
|
|
|
+from pathlib import Path
|
|
|
+from typing import Dict, Any, List, Set
|
|
|
+from collections import defaultdict
|
|
|
+
|
|
|
+# 添加项目路径
|
|
|
+project_root = Path(__file__).parent.parent.parent.parent
|
|
|
+sys.path.insert(0, str(project_root))
|
|
|
+
|
|
|
+# 直接实现 CoordinateUtils 的核心方法,避免依赖问题
|
|
|
+class CoordinateUtils:
|
|
|
+ @staticmethod
|
|
|
+ def calculate_iou(bbox1, bbox2):
|
|
|
+ """计算两个 bbox 的 IoU(交并比)"""
|
|
|
+ x_left = max(bbox1[0], bbox2[0])
|
|
|
+ y_top = max(bbox1[1], bbox2[1])
|
|
|
+ x_right = min(bbox1[2], bbox2[2])
|
|
|
+ y_bottom = min(bbox1[3], bbox2[3])
|
|
|
+
|
|
|
+ if x_right < x_left or y_bottom < y_top:
|
|
|
+ return 0.0
|
|
|
+
|
|
|
+ intersection_area = (x_right - x_left) * (y_bottom - y_top)
|
|
|
+ bbox1_area = (bbox1[2] - bbox1[0]) * (bbox1[3] - bbox1[1])
|
|
|
+ bbox2_area = (bbox2[2] - bbox2[0]) * (bbox2[3] - bbox2[1])
|
|
|
+
|
|
|
+ if bbox1_area == 0 or bbox2_area == 0:
|
|
|
+ return 0.0
|
|
|
+
|
|
|
+ return intersection_area / float(bbox1_area + bbox2_area - intersection_area)
|
|
|
+
|
|
|
+ @staticmethod
|
|
|
+ def calculate_overlap_ratio(bbox1, bbox2):
|
|
|
+ """计算重叠面积占小框面积的比例"""
|
|
|
+ x_left = max(bbox1[0], bbox2[0])
|
|
|
+ y_top = max(bbox1[1], bbox2[1])
|
|
|
+ x_right = min(bbox1[2], bbox2[2])
|
|
|
+ y_bottom = min(bbox1[3], bbox2[3])
|
|
|
+
|
|
|
+ if x_right < x_left or y_bottom < y_top:
|
|
|
+ return 0.0
|
|
|
+
|
|
|
+ intersection_area = (x_right - x_left) * (y_bottom - y_top)
|
|
|
+ area1 = (bbox1[2] - bbox1[0]) * (bbox1[3] - bbox1[1])
|
|
|
+ area2 = (bbox2[2] - bbox2[0]) * (bbox2[3] - bbox2[1])
|
|
|
+ min_area = min(area1, area2)
|
|
|
+
|
|
|
+ if min_area == 0:
|
|
|
+ return 0.0
|
|
|
+
|
|
|
+ return intersection_area / min_area
|
|
|
+
|
|
|
+ @staticmethod
|
|
|
+ def calculate_overlap_in_bbox1_ratio(bbox1, bbox2):
|
|
|
+ """计算 bbox1 被 bbox2 覆盖的面积比例"""
|
|
|
+ x_left = max(bbox1[0], bbox2[0])
|
|
|
+ y_top = max(bbox1[1], bbox2[1])
|
|
|
+ x_right = min(bbox1[2], bbox2[2])
|
|
|
+ y_bottom = min(bbox1[3], bbox2[3])
|
|
|
+
|
|
|
+ if x_right < x_left or y_bottom < y_top:
|
|
|
+ return 0.0
|
|
|
+
|
|
|
+ intersection_area = (x_right - x_left) * (y_bottom - y_top)
|
|
|
+ bbox1_area = (bbox1[2] - bbox1[0]) * (bbox1[3] - bbox1[1])
|
|
|
+
|
|
|
+ if bbox1_area == 0:
|
|
|
+ return 0.0
|
|
|
+
|
|
|
+ return intersection_area / bbox1_area
|
|
|
+
|
|
|
+
|
|
|
+def debug_remove_overlapping_boxes(
|
|
|
+ layout_results: List[Dict[str, Any]],
|
|
|
+ coordinate_utils: Any,
|
|
|
+ iou_threshold: float = 0.8,
|
|
|
+ overlap_ratio_threshold: float = 0.8,
|
|
|
+ table_body_area_threshold: float = 0.5,
|
|
|
+ table_body_confidence_threshold: float = 0.1,
|
|
|
+ table_body_overlap_threshold: float = 0.8
|
|
|
+) -> List[Dict[str, Any]]:
|
|
|
+ """
|
|
|
+ 调试版本的重叠框移除函数
|
|
|
+
|
|
|
+ 与原函数逻辑相同,但添加了详细的日志输出
|
|
|
+ """
|
|
|
+ if not layout_results or len(layout_results) <= 1:
|
|
|
+ return layout_results
|
|
|
+
|
|
|
+ # 复制列表避免修改原数据
|
|
|
+ results = [item.copy() for item in layout_results]
|
|
|
+ need_remove = set()
|
|
|
+
|
|
|
+ # 记录每个元素的移除历史
|
|
|
+ removal_history = defaultdict(list) # idx -> list of removal records
|
|
|
+
|
|
|
+ def _is_bbox_inside(inner_bbox: List[float], outer_bbox: List[float]) -> bool:
|
|
|
+ """检查 inner_bbox 是否完全包含在 outer_bbox 内"""
|
|
|
+ if len(inner_bbox) < 4 or len(outer_bbox) < 4:
|
|
|
+ return False
|
|
|
+ return (inner_bbox[0] >= outer_bbox[0] and
|
|
|
+ inner_bbox[1] >= outer_bbox[1] and
|
|
|
+ inner_bbox[2] <= outer_bbox[2] and
|
|
|
+ inner_bbox[3] <= outer_bbox[3])
|
|
|
+
|
|
|
+ def _format_element(idx: int, element: Dict[str, Any]) -> str:
|
|
|
+ """格式化元素信息用于日志"""
|
|
|
+ bbox = element.get('bbox', [])
|
|
|
+ category = element.get('category', '')
|
|
|
+ score = element.get('confidence', element.get('score', 0))
|
|
|
+ return f"[{idx}] {category}({score:.3f}) bbox={bbox}"
|
|
|
+
|
|
|
+ print("\n" + "="*80)
|
|
|
+ print("开始处理重叠框")
|
|
|
+ print("="*80)
|
|
|
+ print(f"\n原始元素数量: {len(results)}")
|
|
|
+ print("\n原始元素列表:")
|
|
|
+ for i, elem in enumerate(results):
|
|
|
+ print(f" {_format_element(i, elem)}")
|
|
|
+
|
|
|
+ comparison_count = 0
|
|
|
+
|
|
|
+ for i in range(len(results)):
|
|
|
+ if i in need_remove:
|
|
|
+ print(f"\n[跳过] 元素 {i} 已被标记为移除,跳过")
|
|
|
+ continue
|
|
|
+
|
|
|
+ for j in range(i + 1, len(results)):
|
|
|
+ if j in need_remove:
|
|
|
+ continue
|
|
|
+
|
|
|
+ comparison_count += 1
|
|
|
+ bbox1 = results[i].get('bbox', [0, 0, 0, 0])
|
|
|
+ bbox2 = results[j].get('bbox', [0, 0, 0, 0])
|
|
|
+ category1 = results[i].get('category', '')
|
|
|
+ category2 = results[j].get('category', '')
|
|
|
+
|
|
|
+ if len(bbox1) < 4 or len(bbox2) < 4:
|
|
|
+ continue
|
|
|
+
|
|
|
+ score1 = results[i].get('confidence', results[i].get('score', 0))
|
|
|
+ score2 = results[j].get('confidence', results[j].get('score', 0))
|
|
|
+
|
|
|
+ print(f"\n{'='*80}")
|
|
|
+ print(f"比较 {comparison_count}: 元素 {i} vs 元素 {j}")
|
|
|
+ print(f" 元素 {i}: {_format_element(i, results[i])}")
|
|
|
+ print(f" 元素 {j}: {_format_element(j, results[j])}")
|
|
|
+
|
|
|
+ # 计算 IoU
|
|
|
+ iou = coordinate_utils.calculate_iou(bbox1, bbox2)
|
|
|
+ print(f" IoU = {iou:.4f}")
|
|
|
+
|
|
|
+ if iou > iou_threshold:
|
|
|
+ print(f" → IoU > {iou_threshold} (高度重叠)")
|
|
|
+
|
|
|
+ # abandon 类别优先级最低,应该被其他类别移除
|
|
|
+ is_abandon = (category1 == 'abandon' or category2 == 'abandon')
|
|
|
+ if is_abandon:
|
|
|
+ print(f" → 检测到 abandon 类别")
|
|
|
+ # 如果一个是 abandon,另一个不是,保留非 abandon 的
|
|
|
+ if category1 == 'abandon':
|
|
|
+ print(f" → 决策: 移除元素 {i} (abandon), 保留元素 {j}")
|
|
|
+ need_remove.add(i)
|
|
|
+ removal_history[i].append({
|
|
|
+ 'reason': f'与元素 {j} ({category2}) 高度重叠,abandon 优先级最低',
|
|
|
+ 'compared_with': j,
|
|
|
+ 'iou': iou,
|
|
|
+ 'branch': 'high_iou_abandon'
|
|
|
+ })
|
|
|
+ break
|
|
|
+ else: # category2 == 'abandon'
|
|
|
+ print(f" → 决策: 移除元素 {j} (abandon), 保留元素 {i}")
|
|
|
+ need_remove.add(j)
|
|
|
+ removal_history[j].append({
|
|
|
+ 'reason': f'与元素 {i} ({category1}) 高度重叠,abandon 优先级最低',
|
|
|
+ 'compared_with': i,
|
|
|
+ 'iou': iou,
|
|
|
+ 'branch': 'high_iou_abandon'
|
|
|
+ })
|
|
|
+ continue
|
|
|
+
|
|
|
+ # 对于 table_body 与其他类型的重叠,使用特殊处理
|
|
|
+ is_table_body_vs_other_high_iou = (
|
|
|
+ (category1 == 'table_body' and category2 in ['text', 'image_body']) or
|
|
|
+ (category2 == 'table_body' and category1 in ['text', 'image_body'])
|
|
|
+ )
|
|
|
+
|
|
|
+ # 对于 table_body 与 table_body 的重叠,优先保留置信度高的
|
|
|
+ is_table_body_vs_table_body = (category1 == 'table_body' and category2 == 'table_body')
|
|
|
+
|
|
|
+ if is_table_body_vs_other_high_iou:
|
|
|
+ print(f" → table_body vs 其他类型 (高度重叠)")
|
|
|
+ # 确定哪个是 table_body (t1),哪个是其他类型 (o1)
|
|
|
+ if category1 == 'table_body':
|
|
|
+ t1_score, o1_score = score1, score2
|
|
|
+ t1_idx, o1_idx = i, j
|
|
|
+ t1_cat, o1_cat = category1, category2
|
|
|
+ else: # category2 == 'table_body'
|
|
|
+ t1_score, o1_score = score2, score1
|
|
|
+ t1_idx, o1_idx = j, i
|
|
|
+ t1_cat, o1_cat = category2, category1
|
|
|
+
|
|
|
+ print(f" table_body: 元素 {t1_idx} (置信度={t1_score:.3f})")
|
|
|
+ print(f" 其他类型: 元素 {o1_idx} (置信度={o1_score:.3f})")
|
|
|
+ print(f" 阈值: table_body_confidence_threshold={table_body_confidence_threshold}")
|
|
|
+
|
|
|
+ # 如果 t1 置信度 > o1 置信度 + 阈值,保留 t1
|
|
|
+ if t1_score > o1_score + table_body_confidence_threshold:
|
|
|
+ print(f" → 决策: t1 置信度足够高,保留 table_body (元素 {t1_idx}), 移除 {o1_cat} (元素 {o1_idx})")
|
|
|
+ need_remove.add(o1_idx)
|
|
|
+ removal_history[o1_idx].append({
|
|
|
+ 'reason': f'table_body (元素 {t1_idx}) 置信度 ({t1_score:.3f}) > {o1_cat} (元素 {o1_idx}) 置信度 ({o1_score:.3f}) + 阈值 ({table_body_confidence_threshold})',
|
|
|
+ 'compared_with': t1_idx,
|
|
|
+ 'iou': iou,
|
|
|
+ 'branch': 'high_iou_table_body_vs_other'
|
|
|
+ })
|
|
|
+ if o1_idx == i:
|
|
|
+ break
|
|
|
+ continue
|
|
|
+ # 如果 o1 置信度 > t1 置信度 + 阈值,且差值足够大(> 0.2),才保留 o1
|
|
|
+ elif o1_score > t1_score + max(table_body_confidence_threshold, 0.2):
|
|
|
+ print(f" → 决策: o1 置信度明显更高,保留 {o1_cat} (元素 {o1_idx}), 移除 table_body (元素 {t1_idx})")
|
|
|
+ need_remove.add(t1_idx)
|
|
|
+ removal_history[t1_idx].append({
|
|
|
+ 'reason': f'{o1_cat} (元素 {o1_idx}) 置信度 ({o1_score:.3f}) > table_body (元素 {t1_idx}) 置信度 ({t1_score:.3f}) + 阈值 ({max(table_body_confidence_threshold, 0.2)})',
|
|
|
+ 'compared_with': o1_idx,
|
|
|
+ 'iou': iou,
|
|
|
+ 'branch': 'high_iou_table_body_vs_other'
|
|
|
+ })
|
|
|
+ if t1_idx == i:
|
|
|
+ break
|
|
|
+ continue
|
|
|
+ # 否则置信度接近,优先保留 table_body(即使置信度稍低)
|
|
|
+ else:
|
|
|
+ print(f" → 决策: 置信度接近,优先保留 table_body (元素 {t1_idx}), 移除 {o1_cat} (元素 {o1_idx})")
|
|
|
+ need_remove.add(o1_idx)
|
|
|
+ removal_history[o1_idx].append({
|
|
|
+ 'reason': f'置信度接近,优先保留 table_body',
|
|
|
+ 'compared_with': t1_idx,
|
|
|
+ 'iou': iou,
|
|
|
+ 'branch': 'high_iou_table_body_vs_other'
|
|
|
+ })
|
|
|
+ if o1_idx == i:
|
|
|
+ break
|
|
|
+ continue
|
|
|
+ elif is_table_body_vs_table_body:
|
|
|
+ print(f" → table_body vs table_body (高度重叠)")
|
|
|
+ # table_body 与 table_body 的重叠,保留置信度高的
|
|
|
+ if score1 >= score2:
|
|
|
+ print(f" → 决策: 保留元素 {i} (置信度={score1:.3f}), 移除元素 {j} (置信度={score2:.3f})")
|
|
|
+ need_remove.add(j)
|
|
|
+ removal_history[j].append({
|
|
|
+ 'reason': f'与元素 {i} 重叠,置信度更低 ({score2:.3f} < {score1:.3f})',
|
|
|
+ 'compared_with': i,
|
|
|
+ 'iou': iou,
|
|
|
+ 'branch': 'high_iou_table_body_vs_table_body'
|
|
|
+ })
|
|
|
+ else:
|
|
|
+ print(f" → 决策: 保留元素 {j} (置信度={score2:.3f}), 移除元素 {i} (置信度={score1:.3f})")
|
|
|
+ need_remove.add(i)
|
|
|
+ removal_history[i].append({
|
|
|
+ 'reason': f'与元素 {j} 重叠,置信度更低 ({score1:.3f} < {score2:.3f})',
|
|
|
+ 'compared_with': j,
|
|
|
+ 'iou': iou,
|
|
|
+ 'branch': 'high_iou_table_body_vs_table_body'
|
|
|
+ })
|
|
|
+ break # i 被移除,跳出内层循环
|
|
|
+ else:
|
|
|
+ print(f" → 非 table_body 与其他类型的重叠")
|
|
|
+ # 非 table_body 与其他类型的重叠,保留置信度高的
|
|
|
+ if score1 >= score2:
|
|
|
+ print(f" → 决策: 保留元素 {i} (置信度={score1:.3f}), 移除元素 {j} (置信度={score2:.3f})")
|
|
|
+ need_remove.add(j)
|
|
|
+ removal_history[j].append({
|
|
|
+ 'reason': f'与元素 {i} 重叠,置信度更低 ({score2:.3f} < {score1:.3f})',
|
|
|
+ 'compared_with': i,
|
|
|
+ 'iou': iou,
|
|
|
+ 'branch': 'high_iou_other'
|
|
|
+ })
|
|
|
+ else:
|
|
|
+ print(f" → 决策: 保留元素 {j} (置信度={score2:.3f}), 移除元素 {i} (置信度={score1:.3f})")
|
|
|
+ need_remove.add(i)
|
|
|
+ removal_history[i].append({
|
|
|
+ 'reason': f'与元素 {j} 重叠,置信度更低 ({score1:.3f} < {score2:.3f})',
|
|
|
+ 'compared_with': j,
|
|
|
+ 'iou': iou,
|
|
|
+ 'branch': 'high_iou_other'
|
|
|
+ })
|
|
|
+ break # i 被移除,跳出内层循环
|
|
|
+ else:
|
|
|
+ # 检查包含关系
|
|
|
+ area1 = (bbox1[2] - bbox1[0]) * (bbox1[3] - bbox1[1])
|
|
|
+ area2 = (bbox2[2] - bbox2[0]) * (bbox2[3] - bbox2[1])
|
|
|
+
|
|
|
+ print(f" → IoU <= {iou_threshold}, 检查包含关系")
|
|
|
+ print(f" 元素 {i} 面积: {area1:.1f}")
|
|
|
+ print(f" 元素 {j} 面积: {area2:.1f}")
|
|
|
+
|
|
|
+ # 判断哪个框更小
|
|
|
+ if area1 <= area2:
|
|
|
+ small_bbox, large_bbox = bbox1, bbox2
|
|
|
+ small_idx, large_idx = i, j
|
|
|
+ small_score, large_score = score1, score2
|
|
|
+ small_category, large_category = category1, category2
|
|
|
+ small_area, large_area = area1, area2
|
|
|
+ else:
|
|
|
+ small_bbox, large_bbox = bbox2, bbox1
|
|
|
+ small_idx, large_idx = j, i
|
|
|
+ small_score, large_score = score2, score1
|
|
|
+ small_category, large_category = category2, category1
|
|
|
+ small_area, large_area = area2, area1
|
|
|
+
|
|
|
+ print(f" 小框: 元素 {small_idx} ({small_category}, 面积={small_area:.1f}, 置信度={small_score:.3f})")
|
|
|
+ print(f" 大框: 元素 {large_idx} ({large_category}, 面积={large_area:.1f}, 置信度={large_score:.3f})")
|
|
|
+
|
|
|
+ # 检查小框是否被大框包含
|
|
|
+ is_inside = _is_bbox_inside(small_bbox, large_bbox)
|
|
|
+ overlap_ratio = coordinate_utils.calculate_overlap_ratio(small_bbox, large_bbox)
|
|
|
+ print(f" 是否包含: {is_inside}")
|
|
|
+ print(f" 重叠比例: {overlap_ratio:.4f}")
|
|
|
+
|
|
|
+ # abandon 类别优先级最低,应该被其他类别移除
|
|
|
+ is_abandon = (small_category == 'abandon' or large_category == 'abandon')
|
|
|
+ if (is_inside or overlap_ratio > overlap_ratio_threshold) and is_abandon:
|
|
|
+ print(f" → 检测到 abandon 类别 (包含关系)")
|
|
|
+ # 如果一个是 abandon,另一个不是,保留非 abandon 的
|
|
|
+ if small_category == 'abandon':
|
|
|
+ print(f" → 决策: 移除元素 {small_idx} (abandon), 保留元素 {large_idx}")
|
|
|
+ need_remove.add(small_idx)
|
|
|
+ removal_history[small_idx].append({
|
|
|
+ 'reason': f'被元素 {large_idx} ({large_category}) 包含,abandon 优先级最低',
|
|
|
+ 'compared_with': large_idx,
|
|
|
+ 'overlap_ratio': overlap_ratio,
|
|
|
+ 'is_inside': is_inside,
|
|
|
+ 'branch': 'containment_abandon'
|
|
|
+ })
|
|
|
+ if small_idx == i:
|
|
|
+ break
|
|
|
+ continue
|
|
|
+ else: # large_category == 'abandon'
|
|
|
+ print(f" → 决策: 移除元素 {large_idx} (abandon), 保留元素 {small_idx}")
|
|
|
+ need_remove.add(large_idx)
|
|
|
+ removal_history[large_idx].append({
|
|
|
+ 'reason': f'包含元素 {small_idx} ({small_category}),abandon 优先级最低',
|
|
|
+ 'compared_with': small_idx,
|
|
|
+ 'overlap_ratio': overlap_ratio,
|
|
|
+ 'is_inside': is_inside,
|
|
|
+ 'branch': 'containment_abandon'
|
|
|
+ })
|
|
|
+ if large_idx == i:
|
|
|
+ break
|
|
|
+ continue
|
|
|
+
|
|
|
+ # 判断是否是 table_body 与其他类型的重叠
|
|
|
+ is_table_body_vs_other = (
|
|
|
+ (small_category == 'table_body' and large_category in ['text', 'image_body']) or
|
|
|
+ (large_category == 'table_body' and small_category in ['text', 'image_body'])
|
|
|
+ )
|
|
|
+
|
|
|
+ if is_inside or overlap_ratio > overlap_ratio_threshold:
|
|
|
+ print(f" → 包含或高度重叠 (overlap_ratio={overlap_ratio:.4f} > {overlap_ratio_threshold})")
|
|
|
+
|
|
|
+ # 对于 table_body 与其他类型的重叠,特殊处理
|
|
|
+ if is_table_body_vs_other:
|
|
|
+ print(f" → table_body vs 其他类型 (包含关系)")
|
|
|
+ # 确定哪个是 table_body (t1),哪个是其他类型 (o1)
|
|
|
+ if small_category == 'table_body':
|
|
|
+ t1_bbox, o1_bbox = small_bbox, large_bbox
|
|
|
+ t1_idx, o1_idx = small_idx, large_idx
|
|
|
+ t1_score, o1_score = small_score, large_score
|
|
|
+ t1_area, o1_area = small_area, large_area
|
|
|
+ # 这是情况B: o1 包含 t1 (o1 是大框,t1 是小框)
|
|
|
+ is_t1_inside_o1 = True
|
|
|
+ else: # large_category == 'table_body'
|
|
|
+ t1_bbox, o1_bbox = large_bbox, small_bbox
|
|
|
+ t1_idx, o1_idx = large_idx, small_idx
|
|
|
+ t1_score, o1_score = large_score, small_score
|
|
|
+ t1_area, o1_area = large_area, small_area
|
|
|
+ # 这是情况A: t1 包含 o1 (t1 是大框,o1 是小框)
|
|
|
+ is_t1_inside_o1 = False
|
|
|
+
|
|
|
+ print(f" table_body: 元素 {t1_idx} (面积={t1_area:.1f}, 置信度={t1_score:.3f})")
|
|
|
+ print(f" 其他类型: 元素 {o1_idx} (面积={o1_area:.1f}, 置信度={o1_score:.3f})")
|
|
|
+ print(f" t1 被 o1 包含: {is_t1_inside_o1}")
|
|
|
+
|
|
|
+ # 情况A: t1 包含 o1 (t1 是大框,o1 是小框)
|
|
|
+ if is_inside and not is_t1_inside_o1:
|
|
|
+ print(f" 情况A: table_body (大框) 包含 其他类型 (小框)")
|
|
|
+ if (t1_area > o1_area * table_body_area_threshold and
|
|
|
+ t1_score > o1_score + table_body_confidence_threshold):
|
|
|
+ print(f" → 决策: 保留 table_body (元素 {t1_idx}), 移除 {large_category} (元素 {o1_idx})")
|
|
|
+ need_remove.add(o1_idx)
|
|
|
+ removal_history[o1_idx].append({
|
|
|
+ 'reason': f'table_body 包含且满足面积和置信度条件',
|
|
|
+ 'compared_with': t1_idx,
|
|
|
+ 'overlap_ratio': overlap_ratio,
|
|
|
+ 'is_inside': is_inside,
|
|
|
+ 'branch': 'containment_table_body_vs_other_case_a'
|
|
|
+ })
|
|
|
+ if o1_idx == i:
|
|
|
+ break
|
|
|
+ continue
|
|
|
+
|
|
|
+ # 情况B: o1 包含 t1 (o1 是大框,t1 是小框)
|
|
|
+ elif is_inside and is_t1_inside_o1:
|
|
|
+ print(f" 情况B: 其他类型 (大框) 包含 table_body (小框)")
|
|
|
+ if (o1_area > t1_area * table_body_area_threshold and
|
|
|
+ t1_score > o1_score + table_body_confidence_threshold):
|
|
|
+ print(f" → 决策: 保护高置信度 table_body,保留 table_body (元素 {t1_idx}), 移除 {large_category} (元素 {o1_idx})")
|
|
|
+ need_remove.add(o1_idx)
|
|
|
+ removal_history[o1_idx].append({
|
|
|
+ 'reason': f'table_body 被包含但置信度足够高',
|
|
|
+ 'compared_with': t1_idx,
|
|
|
+ 'overlap_ratio': overlap_ratio,
|
|
|
+ 'is_inside': is_inside,
|
|
|
+ 'branch': 'containment_table_body_vs_other_case_b'
|
|
|
+ })
|
|
|
+ if o1_idx == i:
|
|
|
+ break
|
|
|
+ continue
|
|
|
+ elif t1_score > o1_score + table_body_confidence_threshold:
|
|
|
+ print(f" → 决策: table_body 置信度足够高,保留 table_body (元素 {t1_idx}), 移除 {large_category} (元素 {o1_idx})")
|
|
|
+ need_remove.add(o1_idx)
|
|
|
+ removal_history[o1_idx].append({
|
|
|
+ 'reason': f'table_body 置信度足够高 ({t1_score:.3f} > {o1_score:.3f} + {table_body_confidence_threshold})',
|
|
|
+ 'compared_with': t1_idx,
|
|
|
+ 'overlap_ratio': overlap_ratio,
|
|
|
+ 'is_inside': is_inside,
|
|
|
+ 'branch': 'containment_table_body_vs_other_case_b'
|
|
|
+ })
|
|
|
+ if o1_idx == i:
|
|
|
+ break
|
|
|
+ continue
|
|
|
+
|
|
|
+ # 情况C: 高度重叠(非包含关系),使用 t1 被覆盖的比例
|
|
|
+ elif not is_inside and overlap_ratio > overlap_ratio_threshold:
|
|
|
+ print(f" 情况C: 高度重叠(非包含关系)")
|
|
|
+ # 计算 t1 被 o1 覆盖的比例(交集 / t1 的面积)
|
|
|
+ try:
|
|
|
+ t1_coverage = coordinate_utils.calculate_overlap_in_bbox1_ratio(t1_bbox, o1_bbox)
|
|
|
+ except AttributeError:
|
|
|
+ # 备用实现:如果方法不存在,使用交集 / t1 面积
|
|
|
+ x_left = max(t1_bbox[0], o1_bbox[0])
|
|
|
+ y_top = max(t1_bbox[1], o1_bbox[1])
|
|
|
+ x_right = min(t1_bbox[2], o1_bbox[2])
|
|
|
+ y_bottom = min(t1_bbox[3], o1_bbox[3])
|
|
|
+ if x_right > x_left and y_bottom > y_top:
|
|
|
+ intersection = (x_right - x_left) * (y_bottom - y_top)
|
|
|
+ t1_coverage = intersection / t1_area if t1_area > 0 else 0.0
|
|
|
+ else:
|
|
|
+ t1_coverage = 0.0
|
|
|
+
|
|
|
+ print(f" t1 被覆盖比例: {t1_coverage:.4f}")
|
|
|
+ if (t1_coverage > table_body_overlap_threshold and
|
|
|
+ t1_score > o1_score + table_body_confidence_threshold):
|
|
|
+ print(f" → 决策: t1 被高度覆盖且置信度足够高,保留 table_body (元素 {t1_idx}), 移除 {large_category} (元素 {o1_idx})")
|
|
|
+ need_remove.add(o1_idx)
|
|
|
+ removal_history[o1_idx].append({
|
|
|
+ 'reason': f'table_body 被高度覆盖 ({t1_coverage:.4f} > {table_body_overlap_threshold}) 且置信度足够高',
|
|
|
+ 'compared_with': t1_idx,
|
|
|
+ 'overlap_ratio': overlap_ratio,
|
|
|
+ 't1_coverage': t1_coverage,
|
|
|
+ 'branch': 'containment_table_body_vs_other_case_c'
|
|
|
+ })
|
|
|
+ if o1_idx == i:
|
|
|
+ break
|
|
|
+ continue
|
|
|
+
|
|
|
+ # 对于 table_body 与 table_body 的重叠,优先保留置信度高的
|
|
|
+ if small_category == 'table_body' and large_category == 'table_body':
|
|
|
+ print(f" → table_body vs table_body (包含关系)")
|
|
|
+ # 如果大框置信度明显低于小框(差值 > 0.3),保留小框(高置信度的)
|
|
|
+ if large_score < small_score - 0.3:
|
|
|
+ print(f" → 决策: 大框置信度明显低于小框,保留小框 (元素 {small_idx}), 移除大框 (元素 {large_idx})")
|
|
|
+ need_remove.add(large_idx)
|
|
|
+ removal_history[large_idx].append({
|
|
|
+ 'reason': f'大框置信度明显低于小框 ({large_score:.3f} < {small_score:.3f} - 0.3)',
|
|
|
+ 'compared_with': small_idx,
|
|
|
+ 'overlap_ratio': overlap_ratio,
|
|
|
+ 'is_inside': is_inside,
|
|
|
+ 'branch': 'containment_table_body_vs_table_body'
|
|
|
+ })
|
|
|
+ if large_idx == i:
|
|
|
+ break
|
|
|
+ continue
|
|
|
+ # 如果小框置信度明显低于大框(差值 > 0.3),保留大框(高置信度的)
|
|
|
+ elif small_score < large_score - 0.3:
|
|
|
+ print(f" → 决策: 小框置信度明显低于大框,保留大框 (元素 {large_idx}), 移除小框 (元素 {small_idx})")
|
|
|
+ need_remove.add(small_idx)
|
|
|
+ removal_history[small_idx].append({
|
|
|
+ 'reason': f'小框置信度明显低于大框 ({small_score:.3f} < {large_score:.3f} - 0.3)',
|
|
|
+ 'compared_with': large_idx,
|
|
|
+ 'overlap_ratio': overlap_ratio,
|
|
|
+ 'is_inside': is_inside,
|
|
|
+ 'branch': 'containment_table_body_vs_table_body'
|
|
|
+ })
|
|
|
+ if small_idx == i:
|
|
|
+ break
|
|
|
+ continue
|
|
|
+ # 如果置信度接近(差值 <= 0.3),保留置信度高的
|
|
|
+ else:
|
|
|
+ if small_score >= large_score:
|
|
|
+ print(f" → 决策: 置信度接近,保留小框 (元素 {small_idx}, 置信度={small_score:.3f}), 移除大框 (元素 {large_idx})")
|
|
|
+ need_remove.add(large_idx)
|
|
|
+ removal_history[large_idx].append({
|
|
|
+ 'reason': f'置信度接近,保留高置信度小框',
|
|
|
+ 'compared_with': small_idx,
|
|
|
+ 'overlap_ratio': overlap_ratio,
|
|
|
+ 'is_inside': is_inside,
|
|
|
+ 'branch': 'containment_table_body_vs_table_body'
|
|
|
+ })
|
|
|
+ if large_idx == i:
|
|
|
+ break
|
|
|
+ else:
|
|
|
+ print(f" → 决策: 置信度接近,保留大框 (元素 {large_idx}, 置信度={large_score:.3f}), 移除小框 (元素 {small_idx})")
|
|
|
+ need_remove.add(small_idx)
|
|
|
+ removal_history[small_idx].append({
|
|
|
+ 'reason': f'置信度接近,保留高置信度大框',
|
|
|
+ 'compared_with': large_idx,
|
|
|
+ 'overlap_ratio': overlap_ratio,
|
|
|
+ 'is_inside': is_inside,
|
|
|
+ 'branch': 'containment_table_body_vs_table_body'
|
|
|
+ })
|
|
|
+ if small_idx == i:
|
|
|
+ break
|
|
|
+ continue
|
|
|
+
|
|
|
+ # 非表格类别,使用原有逻辑:保留大框并扩展边界
|
|
|
+ print(f" → 非表格类别,保留大框 (元素 {large_idx}), 移除小框 (元素 {small_idx})")
|
|
|
+ results[large_idx]['bbox'] = [
|
|
|
+ min(small_bbox[0], large_bbox[0]),
|
|
|
+ min(small_bbox[1], large_bbox[1]),
|
|
|
+ max(small_bbox[2], large_bbox[2]),
|
|
|
+ max(small_bbox[3], large_bbox[3])
|
|
|
+ ]
|
|
|
+ need_remove.add(small_idx)
|
|
|
+ removal_history[small_idx].append({
|
|
|
+ 'reason': f'非表格类别,保留大框',
|
|
|
+ 'compared_with': large_idx,
|
|
|
+ 'overlap_ratio': overlap_ratio,
|
|
|
+ 'is_inside': is_inside,
|
|
|
+ 'branch': 'containment_other'
|
|
|
+ })
|
|
|
+ if small_idx == i:
|
|
|
+ break # i 被移除,跳出内层循环
|
|
|
+
|
|
|
+ # 返回去重后的结果
|
|
|
+ final_results = [results[i] for i in range(len(results)) if i not in need_remove]
|
|
|
+
|
|
|
+ print(f"\n{'='*80}")
|
|
|
+ print("处理完成")
|
|
|
+ print("="*80)
|
|
|
+ print(f"\n移除的元素数量: {len(need_remove)}")
|
|
|
+ print(f"最终元素数量: {len(final_results)}")
|
|
|
+
|
|
|
+ print(f"\n{'='*80}")
|
|
|
+ print("移除历史")
|
|
|
+ print("="*80)
|
|
|
+ for idx in sorted(need_remove):
|
|
|
+ print(f"\n元素 {idx}: {_format_element(idx, results[idx])}")
|
|
|
+ for record in removal_history[idx]:
|
|
|
+ print(f" 原因: {record['reason']}")
|
|
|
+ print(f" 分支: {record['branch']}")
|
|
|
+
|
|
|
+ print(f"\n{'='*80}")
|
|
|
+ print("最终结果")
|
|
|
+ print("="*80)
|
|
|
+ for i, elem in enumerate(final_results):
|
|
|
+ print(f" {_format_element(i, elem)}")
|
|
|
+
|
|
|
+ # 统计 table_body 的保留情况
|
|
|
+ original_table_bodies = [i for i, elem in enumerate(results) if elem.get('category') == 'table_body']
|
|
|
+ final_table_bodies = [elem for elem in final_results if elem.get('category') == 'table_body']
|
|
|
+ removed_table_bodies = [i for i in original_table_bodies if i in need_remove]
|
|
|
+
|
|
|
+ print(f"\n{'='*80}")
|
|
|
+ print("table_body 统计")
|
|
|
+ print("="*80)
|
|
|
+ print(f"原始 table_body 数量: {len(original_table_bodies)}")
|
|
|
+ print(f"原始 table_body 索引: {original_table_bodies}")
|
|
|
+ print(f"被移除的 table_body 数量: {len(removed_table_bodies)}")
|
|
|
+ print(f"被移除的 table_body 索引: {removed_table_bodies}")
|
|
|
+ print(f"最终保留的 table_body 数量: {len(final_table_bodies)}")
|
|
|
+
|
|
|
+ if removed_table_bodies:
|
|
|
+ print(f"\n被移除的 table_body 详情:")
|
|
|
+ for idx in removed_table_bodies:
|
|
|
+ print(f"\n {_format_element(idx, results[idx])}")
|
|
|
+ for record in removal_history[idx]:
|
|
|
+ print(f" 原因: {record['reason']}")
|
|
|
+ print(f" 分支: {record['branch']}")
|
|
|
+
|
|
|
+ return final_results
|
|
|
+
|
|
|
+
|
|
|
+def main():
|
|
|
+ """主函数:示例用法"""
|
|
|
+ # 示例数据(可以根据实际数据替换)
|
|
|
+ # 这里使用计划中提到的数据:4个table_body(置信度:0.883, 0.584, 0.381, 0.304)和1个abandon(置信度0.313)
|
|
|
+ # 注意:这里使用示例bbox,实际使用时需要替换为真实数据
|
|
|
+
|
|
|
+ # 测试场景1:table_body 之间高度重叠
|
|
|
+ print("="*80)
|
|
|
+ print("测试场景1:table_body 之间高度重叠")
|
|
|
+ print("="*80)
|
|
|
+ layout_results = [
|
|
|
+ {'category': 'table_body', 'confidence': 0.883, 'bbox': [100, 100, 500, 400]},
|
|
|
+ {'category': 'table_body', 'confidence': 0.584, 'bbox': [120, 120, 480, 380]},
|
|
|
+ {'category': 'table_body', 'confidence': 0.381, 'bbox': [110, 110, 490, 390]},
|
|
|
+ {'category': 'table_body', 'confidence': 0.304, 'bbox': [130, 130, 470, 370]},
|
|
|
+ {'category': 'abandon', 'confidence': 0.313, 'bbox': [105, 105, 495, 395]},
|
|
|
+ ]
|
|
|
+
|
|
|
+ coordinate_utils = CoordinateUtils()
|
|
|
+
|
|
|
+ result = debug_remove_overlapping_boxes(
|
|
|
+ layout_results,
|
|
|
+ coordinate_utils,
|
|
|
+ iou_threshold=0.8,
|
|
|
+ overlap_ratio_threshold=0.8,
|
|
|
+ table_body_area_threshold=0.5,
|
|
|
+ table_body_confidence_threshold=0.1,
|
|
|
+ table_body_overlap_threshold=0.8
|
|
|
+ )
|
|
|
+
|
|
|
+ # 测试场景2:table_body 与其他类型(text)重叠,table_body 置信度较低
|
|
|
+ print("\n\n" + "="*80)
|
|
|
+ print("测试场景2:table_body 与其他类型(text)重叠,table_body 置信度较低")
|
|
|
+ print("="*80)
|
|
|
+ layout_results2 = [
|
|
|
+ {'category': 'table_body', 'confidence': 0.5, 'bbox': [100, 100, 500, 400]},
|
|
|
+ {'category': 'text', 'confidence': 0.7, 'bbox': [110, 110, 490, 390]}, # 高度重叠
|
|
|
+ ]
|
|
|
+
|
|
|
+ result2 = debug_remove_overlapping_boxes(
|
|
|
+ layout_results2,
|
|
|
+ coordinate_utils,
|
|
|
+ iou_threshold=0.8,
|
|
|
+ overlap_ratio_threshold=0.8,
|
|
|
+ table_body_area_threshold=0.5,
|
|
|
+ table_body_confidence_threshold=0.1,
|
|
|
+ table_body_overlap_threshold=0.8
|
|
|
+ )
|
|
|
+
|
|
|
+ # 测试场景3:abandon 与其他类型低重叠
|
|
|
+ print("\n\n" + "="*80)
|
|
|
+ print("测试场景3:abandon 与其他类型低重叠(应该被移除)")
|
|
|
+ print("="*80)
|
|
|
+ layout_results3 = [
|
|
|
+ {'category': 'text', 'confidence': 0.8, 'bbox': [100, 100, 500, 400]},
|
|
|
+ {'category': 'abandon', 'confidence': 0.3, 'bbox': [200, 200, 400, 300]}, # 低重叠
|
|
|
+ ]
|
|
|
+
|
|
|
+ result3 = debug_remove_overlapping_boxes(
|
|
|
+ layout_results3,
|
|
|
+ coordinate_utils,
|
|
|
+ iou_threshold=0.8,
|
|
|
+ overlap_ratio_threshold=0.8,
|
|
|
+ table_body_area_threshold=0.5,
|
|
|
+ table_body_confidence_threshold=0.1,
|
|
|
+ table_body_overlap_threshold=0.8
|
|
|
+ )
|
|
|
+
|
|
|
+ coordinate_utils = CoordinateUtils()
|
|
|
+
|
|
|
+ print("使用示例数据进行调试...")
|
|
|
+ print("注意:如果使用实际数据,请修改 layout_results 变量")
|
|
|
+
|
|
|
+ result = debug_remove_overlapping_boxes(
|
|
|
+ layout_results,
|
|
|
+ coordinate_utils,
|
|
|
+ iou_threshold=0.8,
|
|
|
+ overlap_ratio_threshold=0.8,
|
|
|
+ table_body_area_threshold=0.5,
|
|
|
+ table_body_confidence_threshold=0.1,
|
|
|
+ table_body_overlap_threshold=0.8
|
|
|
+ )
|
|
|
+
|
|
|
+ print(f"\n调试完成!")
|
|
|
+
|
|
|
+
|
|
|
+if __name__ == '__main__':
|
|
|
+ main()
|