|
|
@@ -331,3 +331,100 @@ DP 找的是“主干路径”,可能会漏掉一些没能进入最优路径
|
|
|
3. **天然处理合并**:
|
|
|
* DP 的内层循环 `candidate_boxes = row_boxes[prev_j:j]` 天然支持将多个 Box 合并给一个 Cell,不需要单独写 `merged_bboxes` 逻辑。
|
|
|
|
|
|
+# 全局动态规划与行级动态规划详解
|
|
|
+
|
|
|
+结合 `ocr_verify/merger/table_cell_matcher.py` 代码与提供的流水分析数据,为您讲解这两种动态规划在表格匹配中的应用。
|
|
|
+
|
|
|
+## 核心背景
|
|
|
+
|
|
|
+表格匹配的任务是将 **HTML 表格结构**(来自 PP-Structure)与 **OCR 文字框**(来自 PaddleOCR)进行对齐。由于 OCR 结果可能存在漏检、误检、多行被识别为单行或单行被切分为多行的情况,简单的顺序匹配容易出错。因此,代码采用了**两级动态规划(DP)**策略。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 1. 全局动态规划 (Global DP):行级匹配
|
|
|
+
|
|
|
+**目标**:将 HTML 的 `<tr>` 行与 OCR 的“行组”(Grouped Boxes)进行对齐。
|
|
|
+
|
|
|
+### 代码位置
|
|
|
+`TableCellMatcher._match_html_rows_to_paddle_groups` (Line 548)
|
|
|
+
|
|
|
+### 算法原理
|
|
|
+* **状态定义**:`dp[i][j]` 表示 HTML 前 `i` 行与 OCR 前 `j` 个行组匹配的最大得分。
|
|
|
+* **核心挑战**:HTML 行数与 OCR 行组数往往不一致(例如 OCR 将一行文字拆成了两行,或者漏掉了某一行)。
|
|
|
+* **转移方程**:
|
|
|
+ 1. **匹配 (Match)**:HTML 第 `i` 行与 OCR 第 `j` 组(或合并 `j` 到 `j+k` 组)匹配。
|
|
|
+ * `score = similarity(html_text, ocr_text) - penalty`
|
|
|
+ 2. **跳过 HTML (Skip HTML)**:HTML 第 `i` 行在 OCR 中没找到对应(可能是 OCR 漏检)。
|
|
|
+ * `dp[i][j] = dp[i-1][j] - SKIP_HTML_PENALTY`
|
|
|
+ 3. **跳过 OCR (Skip Paddle)**:OCR 第 `j` 组是噪音,不匹配任何 HTML 行(通过 `gap` 参数实现)。
|
|
|
+
|
|
|
+### 实例演示
|
|
|
+**数据来源**:`A用户_单元格扫描流水_page_007.json`
|
|
|
+
|
|
|
+假设 HTML 结构如下(简化):
|
|
|
+* **HTML Row 0**: `1000107... 2024-07-21...` (交易记录1)
|
|
|
+* **HTML Row 1**: `1000107... 2024-07-21...` (交易记录2)
|
|
|
+
|
|
|
+OCR 分组结果(按 Y 坐标聚类):
|
|
|
+* **OCR Group 0**: `1000107... 2024-07-21...` (对应 HTML Row 0)
|
|
|
+* **OCR Group 1**: `1000107... 2024-07-21...` (对应 HTML Row 1)
|
|
|
+* **OCR Group 2**: `(噪音/水印)`
|
|
|
+
|
|
|
+**DP 过程**:
|
|
|
+1. `dp[0][0]` 计算 HTML Row 0 与 OCR Group 0 的相似度,得分高。
|
|
|
+2. `dp[1][1]` 基于 `dp[0][0]`,计算 HTML Row 1 与 OCR Group 1 的相似度,得分高。
|
|
|
+3. 如果 OCR Group 2 是噪音,算法会发现 HTML Row 2 与 OCR Group 2 匹配度极低,可能会选择跳过 OCR Group 2,或者如果 HTML 结束了,OCR Group 2 就成为“未匹配组”。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 2. 行级动态规划 (Row-level DP):单元格匹配
|
|
|
+
|
|
|
+**目标**:在已对齐的某一行内,将 HTML 的 `<td>` 单元格与该行的 OCR Boxes 进行对齐。
|
|
|
+
|
|
|
+### 代码位置
|
|
|
+`TableCellMatcher._match_cells_in_row_dp` (Line 171)
|
|
|
+
|
|
|
+### 算法原理
|
|
|
+* **状态定义**:`dp[i][j]` 表示该行前 `i` 个 HTML 单元格消耗了前 `j` 个 OCR Boxes 的最大得分。
|
|
|
+* **核心挑战**:一个单元格可能对应多个 OCR Box(例如长文本被切断),或者某个单元格内容 OCR 漏检。
|
|
|
+* **转移方程**:
|
|
|
+ 1. **匹配 (Match)**:HTML 单元格 `i` 匹配 OCR Boxes `prev_j` 到 `j` 的合并内容。
|
|
|
+ * `score = similarity(cell_text, merged_box_text)`
|
|
|
+ * 这里允许合并最多 `MAX_MERGE` (5) 个 Box。
|
|
|
+ 2. **单元格缺失 (Cell Missing)**:当前 HTML 单元格为空或 OCR 没识别到。
|
|
|
+ * `dp[i][j] = dp[i-1][j]` (继承上一个状态,相当于当前单元格不消耗 Box)
|
|
|
+
|
|
|
+### 实例演示
|
|
|
+**数据来源**:`A用户_单元格扫描流水_page_007.json` (HTML Row 0)
|
|
|
+
|
|
|
+**HTML 单元格**:
|
|
|
+1. Cell 0: `100010710124072100059119291975743003` (长数字)
|
|
|
+2. Cell 1: `2024-07-2107:50:38` (时间)
|
|
|
+3. Cell 2: `扫二维码付款`
|
|
|
+
|
|
|
+**OCR Boxes (按 X 排序)**:
|
|
|
+1. Box 0: `100010710124072100059119291975743003`
|
|
|
+2. Box 1: `2024-07-21`
|
|
|
+3. Box 2: `07:50:38` (假设时间被切分为两个 Box)
|
|
|
+4. Box 3: `扫二维码付款`
|
|
|
+
|
|
|
+**DP 过程**:
|
|
|
+1. **i=1 (Cell 0)**:
|
|
|
+ * 尝试匹配 Box 0: 相似度 100%,得分高。
|
|
|
+ * `dp[1][1]` 更新为高分。
|
|
|
+2. **i=2 (Cell 1)**:
|
|
|
+ * 尝试匹配 Box 1: `2024-07-21` vs `2024-07-2107:50:38` -> 相似度一般。
|
|
|
+ * 尝试匹配 Box 1+2: `2024-07-21 07:50:38` vs `2024-07-2107:50:38` -> 相似度极高。
|
|
|
+ * 算法选择合并 Box 1 和 Box 2 赋给 Cell 1。
|
|
|
+ * `dp[2][3]` 更新为 `dp[1][1] + score`。
|
|
|
+3. **i=3 (Cell 2)**:
|
|
|
+ * 尝试匹配 Box 3: 相似度 100%。
|
|
|
+ * `dp[3][4]` 更新。
|
|
|
+
|
|
|
+最终回溯路径:Cell 0 <- [Box 0], Cell 1 <- [Box 1, Box 2], Cell 2 <- [Box 3]。
|
|
|
+
|
|
|
+## 总结
|
|
|
+
|
|
|
+* **全局 DP** 解决了“行对齐”问题,处理行层面的漏检和错位。
|
|
|
+* **行级 DP** 解决了“单元格对齐”问题,处理单元格内的碎片化识别和内容匹配。
|
|
|
+* 两者结合,实现了从整页到细节的高精度表格还原。
|