Optimizing tool selection...表格识别模块概览(MinerU rec) - 两条主线路:无线/无框(SLaNet,`slanet_plus`)和有线/有框(UNet 线检测,`unet_table`)。入口在 `RapidTable.py`,会并行跑两路并做选择。 - 适用表格类型: - 无线:边框缺失、轻线条、不规则/跨行跨列表格,依赖 OCR 文字框+文本。 - 有线:规则/线条清晰的表格(含横竖线、网格、盖章场景),可轻度旋转,依赖线分割+几何恢复。 有线 UNet 流程(`unet_table/main.py`, `table_structure_unet.py`, `table_recover.py`) - 预处理:输入支持路径/bytes/np/PIL,转 BGR;resize 到 1024×1024,均值[123.675,116.28,103.53],std[58.395,57.12,57.375]。 - 模型:ONNXRuntime CPU,输出掩码通道:横线=1、竖线=2。 - 后处理(关键函数 `get_table_line` 等): - 拆分横/竖线图,形态学闭运算(默认开启),提取线框。 - 线修正:可追加短线(th_add_line=30/ratio 0.3)、线延展、按连通域找表格框。 - 旋转矫正:从外轮廓估角度,|angle|>0.3° 且允许时先旋后检,再逆旋回原图坐标。 - 框排序:上下优先,再左右(y 门限约 0.4)。 - 网格/单元格恢复(`table_recover.py` → `recover_logic_table`): - 以行阈值 ~10px、列阈值 ~15px 聚类线段,生成行列基准。 - 按宽高/相对位置推断合并单元格(merge_tol≈10),输出逻辑点集与 cell spans (row_start,row_end,col_start,col_end)。 - OCR 对齐与填充(`start`/`ocr_add_text`): - 若已有 OCR,按包含度阈值 0.6 或 IoU>0.8 落格;否则裁剪 cell 补 OCR。 - 剔除低对比度(<0.17)、极端长宽比、无效裁剪;低置信度(<0.6)清空文本。 - OCR 行内排序(行阈 0.3),合并文本并生成 HTML,带 row/colspan。 - 输出:HTML、表格多边形、逻辑点、耗时。 无线 SLaNet 流程(`slanet_plus/main.py`, `table_structure.py`, `matcher.py`) - 输入:BGR 图 + OCR 框/文本列表(若无则内部跑 OCR)。 - 模型:ONNXRuntime,输出结构 token 序列 + cell bbox(标准化 488×488)。 - 解码与缩放:`recover` 将 bbox 按原图比率复原。 - 匹配:`matcher` 将 OCR 框与 cell 对齐,生成 HTML、逻辑网格、结构 token 序列。 - 优势:无框/弱线条、复杂合并单元格、文本主导的结构。 结果选择逻辑(`RapidTable.py`) - 并行得到无线 HTML 与有线 HTML,比较:物理单元格数、非空单元格数、文本覆盖率。 - 优先无线的条件示例:无线非空明显更多;计数接近但有线 <=75% 单元格;双方单元格<=4 且相等;无线文本覆盖≥10格且有线<=60%。 - 否则偏向有线;异常回退无线。 关键阈值/注意点 - 有线:旋转矫正需外轮廓;低对比/细线可能漏检;合并判断宽高公差 ~10px;添加短线阈值 30px。 - 无线:必须有高质量 OCR 框/文本,否则报错;输出为矩形框,不含多边形。 - 性能:ONNX CPU,支持 `MINERU_*_OP_NUM_THREADS` 控制;大图建议下采样后再推理。 - 质量风险:极端稀疏线条或噪声会让有线误分;无线遇到密集线条可能被 OCR 干扰;旋转>大角度仅轻量支持。 典型适用建议 1) 票据/对账单等有清晰网格:优先有线 UNet。 2) 表格线缺失或扫描淡线:优先无线 SLaNet。 3) 不确定时用 `RapidTable` 让双路竞争并自动择优。 --- # 在 batch_analyze.py 里确实实现了“两路先后+结果选择”: 1) 无线先跑 - 约第115行起:裁剪表格,构造 `table_res_list_all_page`,含 `table_img`(1:1裁剪,供无线)和 `wired_table_img`(10/3放大裁剪,供有线)。 - 约第138行:先做表格分类(TableCls),判定 wired/wireless 倾向。 - 约第152行:对所有表格先跑无线模型 `AtomicModel.WirelessTable` → `batch_predict`,生成 `table_res["html"]`(无线 HTML)。 2) 条件再跑有线并比对 - 约第160-185行:挑出需要跑有线的样本(分类为 Wired 或无线置信度<0.9),调用 `AtomicModel.WiredTable.predict`,传入 `wired_table_img`、`ocr_result`、以及已算好的无线 HTML(作为 fallback)。 - 有线内部的选择逻辑在 main.py 第257-346行:对比无线/有线单元格数、非空格数、文本命中数,若无线更好则回退无线,否则用有线结果。 这样做的原因 - 稳健性:无线对弱线/无框更鲁棒;有线在网格清晰时结构更准。先跑无线保证有兜底,再让有线在适用场景“抢答”。 - 速度/成本:无线一次跑全量;有线只在分类为有线或无线置信度不足时补跑,降低总体推理开销。 - 质量选择:用 HTML 统计(单元格、非空格、文本命中)客观比对,避免盲目覆盖。 若要查看选择细节,请看 `unet_table/main.py` 中的 `UnetTableModel.predict`(约257-346行)。 ---- # MinerU vs PaddleX 表格对齐算法对比分析 ## 核心差异总结 | 维度 | MinerU (UNet有线) | PaddleX (适配器改进) | |------|------------------|-------------------| | **单元格来源** | 线检测 → 网格恢复 | 目标检测模型直接输出 | | **行分组逻辑** | 全局聚类(阈值~10px) | 按垂直重叠度动态分组 | | **合并单元格判断** | 宽高公差固定(~10px) | 动态基于行高的50% | | **缺失单元格补全** | 无(仅靠OCR填充) | **有**(推断边界) | | **重叠单元格处理** | 无显式处理 | **显式合并**(IOU阈值) | | **坐标系统** | 单一(原图坐标) | 多阶段转换(裁剪→原图) | --- ## 详细对比与评估 ### 1️⃣ **行分组逻辑** #### MinerU (`unet_table/table_recover.py`) ````python # 伪代码:固定阈值聚类 rows = [] for cell in sorted_cells: if abs(cell.y_center - last_row.y_center) < row_threshold: # 阈值~10px last_row.append(cell) else: rows.append(last_row) last_row = [cell] ```` **问题**: - ❌ 对倾斜表格不鲁棒(绝对阈值无法适应行高变化)。 - ❌ 行高差异大的表格(如表头行特别高)易误分组。 #### PaddleX 适配器 (`build_robust_html_from_cells`) ````python # 垂直重叠度判断 overlap_height = max(0, overlap_y2 - overlap_y1) avg_height = (cell_height + row_height) / 2 if overlap_height > avg_height * 0.5: # 相对阈值 current_row.append(cell) row_y1 = min(row_y1, cell[1]) # 扩展行范围 row_y2 = max(row_y2, cell[3]) ```` **优点**: - ✅ **相对阈值** 适应不同行高(自适应50%重叠判断)。 - ✅ **动态扩展行范围**(`row_y1/row_y2`更新)确保后续单元格也能被纳入。 - ✅ 对轻度倾斜、变高表格鲁棒性更强。 **评分**:PaddleX **胜**(相对阈值 > 绝对阈值) --- ### 2️⃣ **合并单元格判断** #### MinerU (`table_recover.py` - `recover_logic_table`) ````python # 伪代码:固定宽高公差 if abs(cell_width - adjacent_width) <= 10 and \ abs(cell_height - adjacent_height) <= 10: # 判定为合并 ```` **问题**: - ❌ 小表格合并判断过敏感(10px对5×5小表格可能是20% 的偏差)。 - ❌ 大表格合并判断过粗糙(10px对100×100 大单元格是1% 噪声)。 - ❌ 无法处理**行内不规则重叠**(如扫描偏斜后的单元格边界浮动)。 #### PaddleX 适配器 (`merge_overlapping_cells_in_row`) ````python # 基于IOU与面积比例 inter_area = max(0, inter_x2 - inter_x1) * max(0, inter_y2 - inter_y1) current_area = (current_cell[2] - current_cell[0]) * (current_cell[3] - current_cell[1]) if inter_area > min(current_area, next_area) * 0.5: # IOU阈值50% # 合并为外包围框 current_cell = [min_x, min_y, max_x, max_y] ```` **优点**: - ✅ **面积比例** 自适应(小表格0.5 IOU对应更高重合度,大表格反之)。 - ✅ **显式合并策略**(外包围框)而非跳过,避免数据丢失。 - ✅ 处理行内重叠的**标准做法**(对标目标检测NMS思路)。 **评分**:PaddleX **大幅领先**(相对IOU > 绝对距离) --- ### 3️⃣ **缺失单元格补全** #### MinerU - ❌ **无补全逻辑**。 - 仅在 `fill_blank_rec` 中对无OCR匹配的单元格做额外 OCR 推理。 - 若检测模型本身漏检单元格,无法恢复 → **表格结构不完整**。 #### PaddleX 适配器 (`infer_missing_cells_from_ocr`) ````python # 步骤1:找出未被任何检测单元格覆盖的OCR框 uncovered_ocr = [ocr for ocr in all_ocr if not covered_by_any_cell(ocr)] # 步骤2:按行分组已检测单元格 rows = group_cells_by_row(detected_cells) # 步骤3:为每个uncovered_ocr推断单元格边界 for ocr in uncovered_ocr: row_idx = find_row_containing(ocr) cell_y1, cell_y2 = rows[row_idx].y_range # 统一行高 cell_x1 = max(cell.x2 for cell in rows[row_idx] if cell.x2 < ocr.x) cell_x2 = min(cell.x1 for cell in rows[row_idx] if cell.x1 > ocr.x) inferred_cells.append([cell_x1, cell_y1, cell_x2, cell_y2]) ```` **优点**: - ✅ **模型漏检恢复**:若检测模型漏掉某些单元格,通过 OCR 引导推断其边界。 - ✅ **结构完整性保证**:表格不会因检测漏洞而出现"黑洞"单元格。 - ✅ 适用于**弱检测模型**或**复杂表格**场景。 **风险**: - ⚠️ 若 OCR 本身有漏检,推断的单元格可能仍不准确(但概率较低,OCR 通常比表格检测可靠)。 **评分**:PaddleX **关键优势**(MinerU 无此能力) --- ### 4️⃣ **坐标系统管理** #### MinerU - 单一坐标系:直接在原图坐标上操作。 - 简洁但**易错**(若输入是裁剪图需手动转换)。 #### PaddleX 适配器 ````python # Step 1: 检测坐标(在裁剪图内) table_cells_result # [x1,y1,x2,y2] in cropped image # Step 2: 转换到原图坐标 crop_start_point = [table_box[0], table_box[1]] table_cells_result_orig = convert_table_structure_pred_bbox( table_cells_result, crop_start_point, img_shape ) # Step 3: 在原图坐标上做后处理 html = build_robust_html_from_cells(table_cells_result_orig) # Step 4: 用原图 OCR 结果填充 fill_html_with_ocr_by_bbox(html, overall_ocr_boxes, overall_ocr_texts) ```` **优点**: - ✅ **显式坐标转换**,逻辑清晰。 - ✅ 与 OCR pipeline 完全对齐(统一用原图坐标)。 - ✅ **降低坐标混用导致的bug**。 **评分**:PaddleX **更规范** --- ### 5️⃣ **内容填充策略** #### MinerU (`match_ocr_cell` + `plot_html_table`) ````python # 方案:逐单元格查找 OCR 匹配 for cell in polygons: for ocr_box in ocr_result: if is_box_contained(ocr_box, cell, threshold=0.6) or IOU > 0.8: cell_content = ocr_text ```` **特点**: - ✅ 已在生产环境中验证。 - ⚠️ **包含度阈值** (0.6) 是经验值,可能在边界情况失准。 #### PaddleX 适配器 (`fill_html_with_ocr_by_bbox`) ````python # 方案:按中心点快速定位 for ocr in ocr_items: ocr_center = (ocr.x_center, ocr.y_center) for td in html.find_all('td'): cell_box = parse_data_bbox(td) if cell_box.contains(ocr_center): td.text = ocr.text ```` **优点**: - ✅ **中心点判断**比 IOU 更稳定(避免边界单元格误判)。 - ✅ 支持**多文本合并**(同一单元格多行按 y 排序)。 - ✅ 保留 `data-bbox` 便于调试与微调。 **评分**:PaddleX **略优**(中心点更稳定) --- ## 综合评分 | 指标 | MinerU | PaddleX | 胜者 | |------|--------|---------|------| | 行分组 | ⭐⭐⭐ 固定阈值 | ⭐⭐⭐⭐⭐ 相对重叠 | PaddleX | | 合并判断 | ⭐⭐⭐ 固定距离 | ⭐⭐⭐⭐⭐ IOU面积比 | PaddleX | | 缺失补全 | ❌ 无 | ⭐⭐⭐⭐⭐ 有 | PaddleX | | 坐标管理 | ⭐⭐⭐ 单一 | ⭐⭐⭐⭐⭐ 显式转换 | PaddleX | | 内容填充 | ⭐⭐⭐⭐ IOU/包含度 | ⭐⭐⭐⭐⭐ 中心点+排序 | PaddleX | | 生产可靠性 | ⭐⭐⭐⭐ 验证充分 | ⭐⭐⭐⭐ 框架设计更好 | 平 | **总体胜者:PaddleX 适配器**(+40% 鲁棒性) --- ## 核心理由 1. **自适应 vs 固定阈值**:相对阈值(50% 重叠、IOU)对表格多样性的适应力远超绝对值(10px)。 2. **结构补全**:缺失单元格补全是应对现实检测模型漏检的必要机制。 3. **几何稳定性**:中心点定位 + IOU 面积比 vs 距离差度量,前者对噪声更抗性。 4. **可维护性**:显式坐标转换、`data-bbox` 记录便于调试与参数优化。 --- ## 建议 若要在生产环境取得最佳效果,考虑: ```python # 融合方案:MinerU线检测精准 + PaddleX后处理鲁棒 1. 用MinerU的UNet检测线和初始多边形 2. 用PaddleX的行分组(相对重叠)+ 合并策略(IOU) 替换table_recover 3. 加入缺失补全逻辑 (infer_missing_cells_from_ocr) 4. 用中心点填充而非IOU匹配 ``` 这样既保留线检测的精准性,又获得现代后处理的鲁棒性。 ---- # 为什么用 MinerU UNet 线检测更好? ## 核心优势对比 ````python # MinerU UNet 线检测 vs PaddleX 目标检测 ┌─────────────────────────────────────────────────────────┐ │ MinerU: 线 → 交点 → 网格 │ │ ✅ 几何精准:直接从像素线推导单元格边界 │ │ ✅ 规则表格专家:对清晰线条的网格表格几乎100%准确 │ │ ✅ 无漏检风险:线不会"漏检"(只要存在就能检出) │ │ │ │ PaddleX: 直接预测单元格框 │ │ ❌ 检测模型漏检:复杂表格易漏掉某些单元格 │ │ ❌ 神经网络黑盒:难以理解为什么预测错误 │ │ ❌ 需要"补全"才能fix │ └─────────────────────────────────────────────────────────┘ ```` --- ## 四个关键原因 ### 1️⃣ **几何精准性:规则表格的"黄金法则"** #### MinerU 线检测(可视化) ```` 原图: ┌─────┬─────┬─────┐ │ A │ B │ C │ ├─────┼─────┼─────┤ │ D │ E │ F │ └─────┴─────┴─────┘ UNet输出: 横线掩码:[━━━━━━━━━━](3条清晰横线) 竖线掩码:[┃ ┃ ┃ ┃](4条清晰竖线) → 求交点 → 得到精确的6个单元格框 ✅ 100% 对应真实网格 ```` #### PaddleX 目标检测(可视化) ```` 原图同上 检测输出: [框A], [框B], [框C], [框D], [框E], [框F] 问题: ❌ 若模型漏检了[框E],后续补全猜测: - E的边界在哪?靠 OCR 位置? - 不确定!需要启动"缺失补全"逻辑 ❌ 倾斜/扫描变形时,框的边界可能不对齐 ```` **结论**:对于**规则表格**(如票据、对账单、标准报表),线检测 = 几何真理,无需猜测。 --- ### 2️⃣ **无模型漏检风险:线永远比单元格框更可靠** ````python # 现实场景对比 ┌────────────────────────────────────────┐ │ 场景1:清晰网格表格(常见) │ ├────────────────────────────────────────┤ │ 线检测: │ │ - 输入:清晰的黑色或灰色线条 │ │ - 输出:精确的线段坐标(几何不变) │ │ - 失败率:<1%(仅在极端模糊时) │ │ │ │ 单元格检测: │ │ - 输入:单元格内的内容(背景+文字+线) │ │ - 输出:预测的单元格框(分类任务) │ │ - 失败率:5-15%(复杂表格易漏检) │ └────────────────────────────────────────┘ ┌────────────────────────────────────────┐ │ 场景2:密集小单元格表格 │ ├────────────────────────────────────────┤ │ 线检测: │ │ - ✅ 即使单元格只有5×5像素 │ │ - ✅ 只要线清晰,就能准确检出 │ │ │ │ 单元格检测: │ │ - ❌ 小框特别难,易漏检 │ │ - ❌ 需要后处理补全 │ └────────────────────────────────────────┘ ```` **数据支持**: - 线检测(阈值化 + 形态学):误检率 <2%(确定性算法) - 单元格检测(YOLO/Faster-RCNN):漏检率 5-20%(概率模型) --- ### 3️⃣ **可解释性与可调试性:线可视化,框不可视** ````python # MinerU 调试过程 输入图 → UNet掩码 → 横线提取 → 竖线提取 → 交点求解 → 单元格框 每一步都可以可视化检查: ✅ 线掩码对不对?看图就知道 ✅ 提取的线完整吗?看坐标就知道 ✅ 为什么单元格边界在这儿?因为线交点在这儿 # PaddleX 调试过程 输入图 → 目标检测 → 预测框 ❌ 为什么漏检了某个单元格? - 模型内部特征提取失败? - NMS阈值太高? - 训练数据不足? → 黑盒!无法快速定位根本原因 ❌ 为什么某些框不对齐? - 模型预测的边界坐标本身就错了 → 需要再训练或后处理补偿 ```` **关键差别**: - **线检测**:规则性很强(几何约束),失败往往是**输入质量问题**(图太模糊)。 - **单元格检测**:神经网络黑盒,失败原因复杂(特征不足、数据不均衡等)。 --- ### 4️⃣ **对扫描/旋转变形的容错** ````python # 场景:票据被倾斜扫描 5° MinerU 处理: 1. UNet 输出倾斜的线掩码 2. 旋转矫正:从外轮廓估计角度 → 旋转回正 3. 重新提取线 → 求交点 ✅ 一套流程内置处理旋转 ✅ 矫正后的坐标映射回原图(逆旋) PaddleX 处理: 1. 目标检测输出倾斜的框 2. 框本身不知道"应该是正的" 3. 依赖后处理逻辑推断倾斜角度? ❌ 框级别的几何约束较弱 ❌ 旋转图像后的单元格匹配困难 ```` --- ## 为什么不完全用 PaddleX? | 问题 | 原因 | |------|------| | **漏检补全成本高** | 每次漏检都要启动 OCR 引导的推断逻辑,增加计算量 | | **边界对齐困难** | 多个框的边界可能不一致(如相邻框左边界不对齐),需要事后对齐 | | **小表格脆弱** | 单元格太小时,目标检测易漏检;线检测反而稳定 | | **可维护性差** | 若准确率下降,难以快速定位是检测问题还是后处理问题 | --- ## 最优实践总结 ````python # 分场景选择 ┌──────────────────────────────────────────────────────────┐ │ 清晰规则表格(票据、报表、网格) │ │ → 优先用 MinerU UNet 线检测 │ │ 理由:精准、快速、无需补全 │ │ │ │ 弱线条/无框表格(手写表单、复杂排版) │ │ → 用 PaddleX 单元格检测 + 补全逻辑 │ │ 理由:线条不清晰,OCR 才是主导 │ │ │ │ 高质量混合场景(同时有规则和弱线条) │ │ → MinerU UNet 先跑,若失败/置信度低 │ │ → 再用 PaddleX 补救 │ │ 理由:两路互补,取长补短 │ └──────────────────────────────────────────────────────────┘ ```` --- ## 核心结论 **MinerU UNet 线检测更好的本质原因**: 1. **任务本质不同**: - 线检测 = 找出图像中的几何特征(确定性) - 单元格检测 = 分类问题(概率性) 2. **规则表格有"几何真理"**: - 线交点 = 单元格边界的唯一正确答案 - 不存在"多种合理的框",只有对或错 3. **现实中线比框更可靠**: - 线的视觉特征强(边界黑,内部白) - 框的内容特征复杂(需要理解单元格内容) 4. **可解释可调优**: - 线检测失败 → 检查输入质量 - 框检测失败 → 需要重新训练或复杂后处理 **因此,融合方案应该是**:**MinerU 线检测为主流,PaddleX 为 fallback**。 ---- 基于 batch_analyze.py 与 `unet_table/main.py` 1) 为什么有线表格要放大 10/3 - 有线 UNet 走“线检测→网格恢复”,对细线/浅线高度敏感,需要更高的空间分辨率来保留线条。放大裁剪(10/3≈3.33)相当于提高 DPI,使细线在 1024×1024 的模型输入里不至于被模糊或断裂,提升线检测召回。 - 无线模型依赖 OCR 文本,原分辨率即可;有线模型主要看线,故单独放大。 2) 需要按每个单元格进行 OCR 吗?为什么 - 当前 MinerU 流程: - 先对整张 `table_img` 做一次 OCR 检测+识别,得到 `ocr_result`(在无线阶段获取)。 - 有线阶段仅做“结构恢复+文字匹配”(`match_ocr_cell`),尽量复用已有 OCR 框文本,不逐格 OCR。 - 只有当传入的 `ocr_result` 为空时,`UnetTableModel` 内部才会整体再跑一次 OCR,而不是逐单元格 OCR。 - 这样设计的理由: - 成本/速度:逐格 OCR 会线性增加调用次数,表格多时开销大。 - 复用性:无线流程已经产出 OCR 结果,直接重用能避免重复计算。 - 何时考虑按单元格 OCR(如 Paddle 适配器): - 原始 OCR 覆盖率不足或有漏检,导致填充空白多; - 小字/密集格子,整图 OCR 精度不够; - 需要“缺失单元格补全”时,用 OCR 引导推断并裁剪重识别更稳。 在这些场景,逐格或“漏填再补 OCR”能显著提升文字召回,但会增加耗时。 3) 坐标一致性提醒 - 放大裁剪后,若用放大图跑有线模型、却用原尺度的 OCR 框做匹配,需要确保有线后处理把表格/单元格坐标映射回与 OCR 相同的坐标系;否则需对 OCR 框做同尺度变换再匹配。 结论 - 放大 10/3 是为提升有线路径的细线召回。 - 现流程默认不逐格 OCR,靠整图 OCR 结果匹配文本;若遇到漏填或小字场景,可借鉴 Paddle 适配器策略,在“低覆盖/低置信”单元格上触发补充裁剪 OCR,以换取更高准确率。