table_recognition_v2-说明.md 12 KB

交易流水,单栏列表,有线表格

使用table_recognition_v2产线,关键参数use_wired_table_cells_trans_to_html: use_wired_table_cells_trans_to_html:启用有线表单元格直转HTML模式,启用时直接基于有线表单元格检测模型预测的各单元格位置排布与几何关系构建HTML结构。此模式在不同场景下效果不同,请根据实际情况选择。一般来说,建议在有线表单元格检测非常准确的场景下开启此模式,以获得更加准确的端到端表格识别结果。

不启用表格结构识别模块预测得到表格的HTML结构

去掉其他干扰模型

outputs = pipeline.predict(
	img_path,
	use_doc_orientation_classify=False,
	use_doc_unwarping=False,
	use_layout_detection=True,
	use_ocr_results_with_table_cells=True,
	use_table_orientation_classify=False,
	use_wired_table_cells_trans_to_html=True,
	# 新增:关闭单元格内拆分,整格识别以保留折行文本, 
	# 修改paddlex/inference/pipelines/table_recognition/pipeline_v2.py
	# get_table_recognition_res传入参数self.cells_split_ocr=False,保证单元格内换行不被拆分
	use_table_cells_split_ocr=False,
)

折行文本被丢弃

正在收集工作区信息正在筛选到最相关的信息看起来不是“表格结构定位”问题,而是表格内容填充阶段把同一单元格里的折行文本做了“分段重识别/分裂”,只保留了其中一段(常见为最后一行“款”),导致“扫二维码付”被丢弃。

原因定位

  • 使用管线 table_recognition_v2,且传了 use_ocr_results_with_table_cells=True、use_wired_table_cells_trans_to_html=True。
  • 在 v2 管线中,默认启用了“单元格内再切分并重识别”的策略,见函数:_TableRecognitionPipelineV2.predict 和单元格内容合成逻辑:get_table_recognition_res
  • 该切分策略在遇到“纵向换行”的短词时,可能只选择到其中一个分段,从而丢字。

关闭“单元格内拆分重识别”,直接对整格做一次OCR,再把同格内多行按阅读顺序拼接。为此给 table_recognition_v2 暴露一个开关 use_table_cells_split_ocr,并传到后处理。

修改1:在管线里增加开关并传给后处理

# ...existing code...
class _TableRecognitionPipelineV2(BasePipeline):
    # ...existing code...
    def predict(
        self,
        input: Union[str, List[str], np.ndarray, List[np.ndarray]],
        use_doc_orientation_classify: Optional[bool] = None,
        use_doc_unwarping: Optional[bool] = None,
        use_layout_detection: Optional[bool] = None,
        use_ocr_model: Optional[bool] = None,
        overall_ocr_res: Optional[OCRResult] = None,
        layout_det_res: Optional[DetResult] = None,
        text_det_limit_side_len: Optional[int] = None,
        text_det_limit_type: Optional[str] = None,
        text_det_thresh: Optional[float] = None,
        text_det_box_thresh: Optional[float] = None,
        text_det_unclip_ratio: Optional[float] = None,
        text_rec_score_thresh: Optional[float] = None,
        use_e2e_wired_table_rec_model: bool = False,
        use_e2e_wireless_table_rec_model: bool = False,
        use_wired_table_cells_trans_to_html: bool = False,
        use_wireless_table_cells_trans_to_html: bool = False,
        use_table_orientation_classify: bool = True,
        use_ocr_results_with_table_cells: bool = True,
        **kwargs,
    ) -> TableRecognitionResult:
        # 新增:允许外部控制单元格内是否拆分重识别(默认保持兼容 True)
        use_table_cells_split_ocr = kwargs.get("use_table_cells_split_ocr", None)
        self.cells_split_ocr = (
            bool(use_table_cells_split_ocr) if use_table_cells_split_ocr is not None else True
        )
        # ...existing code...

    def predict_single_table_recognition_res(
        self,
        image_array: np.ndarray,
        overall_ocr_res: OCRResult,
        table_box: list,
        use_e2e_wired_table_rec_model: bool = False,
        use_e2e_wireless_table_rec_model: bool = False,
        use_wired_table_cells_trans_to_html: bool = False,
        use_wireless_table_cells_trans_to_html: bool = False,
        use_ocr_results_with_table_cells: bool = True,
        flag_find_nei_text: bool = True,
    ) -> SingleTableRecognitionResult:
        # ...existing code,完成 table_structure_result / table_cells_result / table_ocr_pred / cells_texts_list 的产生后...
        single_table_recognition_res = get_table_recognition_res(
            table_box,
            table_structure_result,
            table_cells_result,
            overall_ocr_res,
            table_ocr_pred,
            cells_texts_list,
            use_ocr_results_with_table_cells,
            self.cells_split_ocr,  # 传递新增开关
        )
        # ...existing code...

说明

  • get_table_recognition_res 已有 use_table_cells_split_ocr 参数,传 True 时走“拆分重识别”,传 False 时用整格OCR结果,不会丢掉折行。
  • 以上改动保持默认行为不变(兼容老调用)。

修改2:调用处传入开关(你的脚本)

# ...existing code...
                outputs = pipeline.predict(
                    img_path,
                    use_doc_orientation_classify=False,
                    use_doc_unwarping=False,
                    use_layout_detection=True,
                    use_ocr_results_with_table_cells=True,
                    use_table_orientation_classify=False,
                    use_wired_table_cells_trans_to_html=True,
                    # 新增:关闭单元格内拆分,整格识别以保留折行文本
                    use_table_cells_split_ocr=False,
                )
# ...existing code...

BeautifulSoup 生成HTML表格

正在收集工作区信息正在筛选到最相关的信息基于你的想法,使用 BeautifulSoup 生成表格树然后填充内容确实是一个很好的改进方案。让我比较一下两种方式:

现有 get_html_result 的问题

  1. 字符串拼接:逐个拼接 HTML 标签,容易出错
  2. 索引对齐复杂:需要手动维护 td_globalrow_idxcol_idx 等指针
  3. 结构不一致时易崩溃:当检测列数与结构列数不匹配时需要复杂的回退逻辑
  4. 难以处理合并单元格:如果后续需要支持 colspan/rowspan 会很困难

BeautifulSoup 方案的优势

  1. 结构清晰:直接基于检测到的单元格坐标生成规整的表格结构
  2. 索引简单:通过行列坐标直接定位单元格,无需复杂的指针推进
  3. 易于扩展:天然支持合并单元格、样式添加等高级功能
  4. 更稳定:不依赖于结构识别模型的输出质量

推荐的 BeautifulSoup 实现

def generate_table_from_cells(table_cells_result, ocr_texts_res, table_cells_flag):
    """
    基于检测到的单元格坐标生成 BeautifulSoup 表格树并填充内容
    """
    from bs4 import BeautifulSoup
    
    # 计算表格行列数
    num_rows = len(table_cells_flag) - 1
    max_cols = max(table_cells_flag[i+1] - table_cells_flag[i] for i in range(num_rows)) if num_rows > 0 else 0
    
    # 创建基础表格结构
    html_template = f"""
    <html>
    <body>
    <table>
    {''.join(f'<tr>{"<td></td>" * max_cols}</tr>' for _ in range(num_rows))}
    </table>
    </body>
    </html>
    """
    
    soup = BeautifulSoup(html_template, 'html.parser')
    table = soup.find('table')
    rows = table.find_all('tr')
    
    # 填充单元格内容
    for row_idx in range(num_rows):
        row = rows[row_idx]
        cells = row.find_all('td')
        
        # 获取当前行的单元格范围
        start_cell = table_cells_flag[row_idx]
        end_cell = table_cells_flag[row_idx + 1]
        
        for col_idx in range(end_cell - start_cell):
            if col_idx < len(cells):
                cell_global_idx = start_cell + col_idx
                
                # 查找匹配的 OCR 内容
                cell_content = ""
                if cell_global_idx < len(ocr_texts_res):
                    cell_content = ocr_texts_res[cell_global_idx] or ""
                
                cells[col_idx].string = cell_content
    
    return str(soup)

更优化的实现方案

# ...existing code...

def generate_table_with_beautifulsoup(
    table_cells_result: list,
    ocr_texts_res: list, 
    matched_index: dict,
    table_cells_flag: list
) -> str:
    """
    使用 BeautifulSoup 生成表格并填充 OCR 内容
    
    Args:
        table_cells_result: 单元格坐标列表
        ocr_texts_res: OCR 识别文本列表
        matched_index: 单元格与 OCR 的匹配关系
        table_cells_flag: 行边界标记
    
    Returns:
        str: 生成的 HTML 表格字符串
    """
    try:
        from bs4 import BeautifulSoup, Tag
    except ImportError:
        # 如果没有 BeautifulSoup,回退到原方法
        return get_html_result_fallback(matched_index, ocr_texts_res, table_cells_flag)
    
    # 计算表格维度
    num_rows = len(table_cells_flag) - 1
    if num_rows <= 0:
        return "<html><body><table></table></body></html>"
    
    # 创建表格结构
    soup = BeautifulSoup("<html><body><table></table></body></html>", 'html.parser')
    table = soup.find('table')
    
    # 为每行添加单元格
    for row_idx in range(num_rows):
        tr = soup.new_tag('tr')
        table.append(tr)
        
        # 当前行的单元格数量
        num_cols = table_cells_flag[row_idx + 1] - table_cells_flag[row_idx]
        
        for col_idx in range(num_cols):
            td = soup.new_tag('td')
            tr.append(td)
            
            # 填充单元格内容
            if row_idx < len(matched_index) and col_idx in matched_index[row_idx]:
                content_parts = []
                for ocr_idx in matched_index[row_idx][col_idx]:
                    if ocr_idx < len(ocr_texts_res):
                        content = ocr_texts_res[ocr_idx].strip()
                        if content:
                            content_parts.append(content)
                
                # 合并多个 OCR 结果
                if content_parts:
                    td.string = " ".join(content_parts)
    
    return str(soup)

def get_table_recognition_res(
    table_box: list,
    table_structure_result: list,
    table_cells_result: list,
    overall_ocr_res: OCRResult,
    table_ocr_pred: dict,
    cells_texts_list: list,
    use_table_cells_ocr_results: bool,
    use_table_cells_split_ocr: bool,
) -> SingleTableRecognitionResult:
    # ...existing code above...
    
    # 使用 BeautifulSoup 生成表格(推荐)
    pred_html = generate_table_with_beautifulsoup(
        table_cells_result, ocr_texts_res, matched_index, table_cells_flag
    )
    
    # ...existing code below...

结论

推荐使用 BeautifulSoup 方案,原因:

  1. 更稳定:不受结构识别模型输出质量影响
  2. 更简洁:代码逻辑更清晰,易于维护
  3. 更灵活:容易扩展支持合并单元格、样式等功能
  4. 更可靠:避免了复杂的指针对齐问题

但需要:

  • 添加 BeautifulSoup 依赖检查
  • 保留原方法作为回退方案
  • 充分测试确保兼容性

这种方案特别适合有线表格场景,因为单元格检测通常很准确,可以直接基于检测结果构建表格结构。

单元格内文字丢失

上期发生额”ocr后的文字bbox是在表格检测单元框内,为什么没有显示出来 还需查找原因

执行脚本

# A用户_单元格扫描流水
python table_recognition_v2_single_process.py \
    --input_dir "/Users/zhch158/workspace/data/流水分析/A用户_单元格扫描流水.img" \
    --output_dir "/Users/zhch158/workspace/data/流水分析/A用户_单元格扫描流水/table_recognition_v2_Results" \
    --pipeline "./my_config/table_recognition_v2.yaml" \
    --device "cpu"

# 至远彩色印刷工业有限公司_2023年度报告母公司
python table_recognition_v2_single_process.py \
    --input_dir "/Users/zhch158/workspace/data/至远彩色印刷工业有限公司/2023年度报告母公司.img" \
    --output_dir "/Users/zhch158/workspace/data/至远彩色印刷工业有限公司/table_recognition_v2_Results" \
    --pipeline "./my_config/table_recognition_v2.yaml" \
    --device "cpu"