zhch158_admin 979d73759e feat: 调整长度比例检查逻辑,修改阈值以提高匹配准确性 1 nedēļu atpakaļ
..
README.md f0d3236884 feat: 修改README文档,将输出格式参数名称从format更改为output-type 3 nedēļas atpakaļ
__init__.py d0c46034b0 feat: 添加UnifiedOutputConverter到合并工具包,更新__all__导出列表 3 nedēļas atpakaļ
bbox_extractor.py 2ec53f5194 feat: 添加旋转角度处理和原始图像尺寸获取功能,支持坐标反向旋转 1 nedēļu atpakaļ
data_processor.py 7930c6cd71 feat: 添加对 PaddleOCR_VL 数据的旋转角度和原始图像尺寸处理,优化 bbox 坐标转换 1 nedēļu atpakaļ
dotsocr_merger.py 6e82eedf30 feat: 添加 DotsOCR 和 PaddleOCR 合并模块,支持 JSON 数据合并和 Markdown 生成 1 nedēļu atpakaļ
markdown_generator.py 84e3ccaf99 feat: 添加MinerU格式化方法,支持页眉、页脚和页码的Markdown生成 3 nedēļas atpakaļ
merge_dotsocr_paddleocr.py d451e66d4c feat: 添加 DotsOCR 和 PaddleOCR 合并程序,支持单文件和批量处理,输出为统一的MinerU格式 1 nedēļu atpakaļ
merge_mineru_paddle_ocr.py 4383d618f7 feat: 修改合并函数参数名称,将output_format更改为output_type,统一输出格式参数 3 nedēļas atpakaļ
merge_paddleocr_vl_paddleocr.py d813017609 feat: 更新默认配置文件路径,指向新的 PaddleOCR_VL 和 PaddleOCR 文件 1 nedēļu atpakaļ
merger_core.py d161a5c493 feat: 更新生成Markdown的方法,改为使用私有方法_generate_mineru_markdown 3 nedēļas atpakaļ
paddleocr_vl_merger.py 038666f9ed feat: 优化 PaddleOCR_VL 数据处理逻辑,移除不必要的格式转换 1 nedēļu atpakaļ
text_matcher.py 979d73759e feat: 调整长度比例检查逻辑,修改阈值以提高匹配准确性 1 nedēļu atpakaļ
unified_output_converter.py 7cf744a8dc feat: 添加UnifiedOutputConverter类,实现不同OCR工具结果转换为MinerU格式 3 nedēļas atpakaļ

README.md

OCR 结果合并工具说明

概述

本工具包用于合并不同 OCR 工具的识别结果,主要功能是将结构化识别结果(如 MinerU、PaddleOCR_VL)与精确文字框坐标(PaddleOCR)进行合并,生成包含完整 bbox 信息的增强版 Markdown 和 JSON 文件。

支持的合并方式

1. MinerU + PaddleOCR

  • 输入
    • MinerU 的 JSON 文件(包含表格结构、段落等)
    • PaddleOCR 的 JSON 文件(包含精确的文字框坐标)
  • 输出
    • 增强的 Markdown(包含 bbox 注释)
    • 合并的 JSON(MinerU 格式 + bbox 信息)

2. PaddleOCR_VL + PaddleOCR

  • 输入
    • PaddleOCR_VL 的 JSON 文件(包含版面分析、表格识别等)
    • PaddleOCR 的 JSON 文件(包含精确的文字框坐标)
  • 输出
    • 增强的 Markdown(包含 bbox 注释)
    • 合并的 JSON(转换为 MinerU 格式 + bbox 信息)

目录结构

merger/
├── __init__.py                      # 包初始化文件
├── merger_core.py                   # MinerU 合并器核心类
├── paddleocr_vl_merger.py          # PaddleOCR_VL 合并器核心类
├── merge_mineru_paddle_ocr.py      # MinerU + PaddleOCR 主程序
├── merge_paddleocr_vl_paddleocr.py # PaddleOCR_VL + PaddleOCR 主程序
├── text_matcher.py                  # 文本匹配模块(共用)
├── bbox_extractor.py               # Bbox 提取模块(共用)
├── data_processor.py               # 数据处理模块(共用)
├── markdown_generator.py           # Markdown 生成模块(共用)
└── README.md                  # 本说明文档

核心模块说明

1. 合并器类

MinerUPaddleOCRMerger (merger_core.py)

class MinerUPaddleOCRMerger:
    """MinerU 和 PaddleOCR 结果合并器"""
    
    def __init__(self, look_ahead_window: int = 10, 
                 similarity_threshold: int = 90):
        """
        Args:
            look_ahead_window: 向前查找的窗口大小
            similarity_threshold: 文本相似度阈值(0-100)
        """
    
    def merge_table_with_bbox(self, mineru_json_path: str, 
                             paddle_json_path: str) -> List[Dict]:
        """合并 MinerU 和 PaddleOCR 的结果"""
    
    def generate_enhanced_markdown(self, merged_data: List[Dict], 
                                   output_path: str = None) -> str:
        """生成增强的 Markdown"""

PaddleOCRVLMerger (paddleocr_vl_merger.py)

class PaddleOCRVLMerger:
    """PaddleOCR_VL 和 PaddleOCR 结果合并器"""
    
    def __init__(self, look_ahead_window: int = 10, 
                 similarity_threshold: int = 90):
        """
        Args:
            look_ahead_window: 向前查找的窗口大小
            similarity_threshold: 文本相似度阈值(0-100)
        """
    
    def merge_table_with_bbox(self, paddleocr_vl_json_path: str, 
                             paddle_json_path: str) -> List[Dict]:
        """合并 PaddleOCR_VL 和 PaddleOCR 的结果"""
    
    def generate_enhanced_markdown(self, merged_data: List[Dict], 
                                   output_path: str = None,
                                   data_format: str = None) -> str:
        """生成增强的 Markdown"""

2. 共用模块

TextMatcher (text_matcher.py)

负责文本匹配,找到 MinerU/PaddleOCR_VL 文本在 PaddleOCR 结果中的对应位置。

核心方法

def find_matching_bbox(self, target_text: str, 
                      text_boxes: List[Dict],
                      start_index: int = 0,
                      last_match_index: int = 0,
                      look_ahead_window: int = 10) -> Tuple[Optional[Dict], int, int]:
    """
    查找匹配的 bbox
    
    Args:
        target_text: 目标文本
        text_boxes: PaddleOCR 文字框列表
        start_index: 开始搜索的位置
        last_match_index: 上一次匹配的索引
        look_ahead_window: 向前查找窗口大小
    
    Returns:
        (匹配的文字框, 新的开始位置, 新的last_match_index)
    """

匹配策略

  1. 顺序匹配:优先从 start_index 开始顺序查找
  2. 窗口回溯:在 [last_match_index - look_ahead_window, last_match_index + look_ahead_window] 范围内查找
  3. 相似度计算:使用 fuzzywuzzy 计算文本相似度
  4. 指针更新:匹配成功后更新指针,避免重复匹配

BBoxExtractor (bbox_extractor.py)

负责从 PaddleOCR 结果中提取文字框信息。

核心方法

def extract_paddle_text_boxes(self, paddle_data: Dict) -> List[Dict]:
    """
    提取 PaddleOCR 的文字框信息
    
    Returns:
        [
            {
                'text': '文本内容',
                'bbox': [x1, y1, x2, y2],
                'score': 0.99,
                'used': False
            },
            ...
        ]
    """

def extract_table_cells_with_bbox(self, merged_data: List[Dict]) -> List[Dict]:
    """
    提取所有表格单元格及其 bbox 信息
    
    Returns:
        [
            {
                'text': '单元格文本',
                'bbox': [x1, y1, x2, y2],
                'table_index': 0,
                'row': 1,
                'col': 2
            },
            ...
        ]
    """

DataProcessor (data_processor.py)

负责处理 MinerU/PaddleOCR_VL 数据,添加 bbox 信息。

核心方法

def process_mineru_data(self, mineru_data: List[Dict], 
                       paddle_text_boxes: List[Dict]) -> List[Dict]:
    """
    处理 MinerU 数据,添加 bbox 信息
    
    处理类型:
    - text: 普通文本
    - title: 标题
    - table: 表格
    - list: 列表
    - image: 图片
    - equation: 公式
    """

def process_paddleocr_vl_data(self, paddleocr_vl_data: Dict,
                              paddle_text_boxes: List[Dict]) -> List[Dict]:
    """
    处理 PaddleOCR_VL 数据,添加 bbox 信息
    
    处理类型:
    - paragraph_title: 段落标题
    - figure_title: 图片标题
    - text: 文本
    - table: 表格
    - figure: 图片
    - equation: 公式
    - reference: 参考文献
    """

表格处理逻辑

  1. 解析 HTML 表格结构
  2. 逐个单元格匹配 PaddleOCR 文字框
  3. 为每个 <td> 添加 data-bboxdata-paddle-indexdata-score 属性
  4. 处理合并单元格(colspan、rowspan)

MarkdownGenerator (markdown_generator.py)

负责将合并后的数据生成 Markdown 文件。

核心特性

  • 自动格式检测:自动识别 MinerU 或 PaddleOCR_VL 格式
  • bbox 注释:为每个元素添加 <!-- bbox: [x1, y1, x2, y2] --> 注释
  • 表格增强:保留表格中的 bbox 属性
  • 图片处理:自动复制图片文件

核心方法

def detect_data_format(merged_data: List[Dict]) -> str:
    """
    检测数据格式
    
    Returns:
        'mineru' 或 'paddleocr_vl'
    """

def generate_enhanced_markdown(merged_data: List[Dict], 
                               output_path: Optional[str] = None,
                               source_file: Optional[str] = None,
                               data_format: Optional[str] = None) -> str:
    """
    生成增强的 Markdown
    
    Args:
        merged_data: 合并后的数据
        output_path: 输出路径
        source_file: 源文件路径(用于复制图片)
        data_format: 数据格式,None 则自动检测
    """

格式化方法

  • MinerU 格式_format_mineru_*() 系列方法
  • PaddleOCR_VL 格式_format_paddleocr_vl_*() 系列方法
  • 通用方法_format_equation(), _format_metadata()

使用方法

1. MinerU + PaddleOCR 合并

命令行使用

# 单文件处理
python merger/merge_mineru_paddle_ocr.py \
  --mineru-file /path/to/mineru_page_001.json \
  --paddle-file /path/to/paddle_page_001.json \
  --output-dir /path/to/output \
  --output-type both

# 批量处理
python merger/merge_mineru_paddle_ocr.py \
  --mineru-dir /path/to/mineru_results \
  --paddle-dir /path/to/paddle_results \
  --output-dir /path/to/output \
  --output-type both \
  --window 15 \
  --threshold 85

参数说明

参数 说明 默认值
--mineru-file MinerU JSON 文件路径(单文件模式) -
--paddle-file PaddleOCR JSON 文件路径(单文件模式) -
--mineru-dir MinerU 结果目录(批量模式) -
--paddle-dir PaddleOCR 结果目录(批量模式) -
-o, --output-dir 输出目录(必需) -
-f, --output-type 输出格式:json/markdown/both both
-w, --window 向前查找窗口大小 15
-t, --threshold 文本相似度阈值(0-100) 80

2. PaddleOCR_VL + PaddleOCR 合并

命令行使用

# 单文件处理
python merger/merge_paddleocr_vl_paddleocr.py \
  --paddleocr-vl-file /path/to/paddleocr_vl_page_001.json \
  --paddle-file /path/to/paddle_page_001.json \
  --output-dir /path/to/output \
  --output-type both

# 批量处理
python merger/merge_paddleocr_vl_paddleocr.py \
  --paddleocr-vl-dir /path/to/paddleocr_vl_results \
  --paddle-dir /path/to/paddle_results \
  --output-dir /path/to/output \
  --output-type both \
  --window 15 \
  --threshold 85

参数说明

参数 说明 默认值
--paddleocr-vl-file PaddleOCR_VL JSON 文件路径(单文件模式) -
--paddle-file PaddleOCR JSON 文件路径(单文件模式) -
--paddleocr-vl-dir PaddleOCR_VL 结果目录(批量模式) -
--paddle-dir PaddleOCR 结果目录(批量模式) -
-o, --output-dir 输出目录(必需) -
-f, --output-type 输出格式:json/markdown/both both
-w, --window 向前查找窗口大小 15
-t, --threshold 文本相似度阈值(0-100) 80

输出格式

1. 增强的 Markdown

<!-- bbox: [717, 191, 917, 229] -->
# 账务明细清单

<!-- bbox: [721, 233, 921, 254] -->
# Statement Of Account

<!-- bbox: [181, 257, 517, 283] -->
开户银行:呼和浩特成吉思汗大街

<!-- bbox: [176, 406, 1468, 1920] -->
<table>
  <tr>
    <td data-bbox="[183,413,293,438]" data-paddle-index="10" data-score="0.9995">日期Date</td>
    <td data-bbox="[296,413,486,438]" data-paddle-index="11" data-score="0.9988">业务类型Business Type</td>
    <td data-bbox="[489,413,642,438]" data-paddle-index="12" data-score="0.9994">票据号Bill No.</td>
  </tr>
  ...
</table>

2. 合并的 JSON

MinerU 格式

[
  {
    "type": "text",
    "text": "账务明细清单",
    "text_level": 1,
    "bbox": [717, 191, 917, 229],
    "page_idx": 0
  },
  {
    "type": "table",
    "table_body": "<table>...</table>",
    "table_body_with_bbox": "<table>...</table>",
    "bbox": [176, 406, 1468, 1920],
    "bbox_mapping": "merged_from_paddle_ocr",
    "table_cells": [
      {
        "text": "日期Date",
        "bbox": [183, 413, 293, 438],
        "paddle_index": 10,
        "score": 0.9995,
        "row": 0,
        "col": 0
      }
    ],
    "page_idx": 0
  }
]

PaddleOCR_VL 格式(转换为 MinerU 格式)

[
  {
    "type": "text",
    "text": "账务明细清单",
    "text_level": 1,
    "bbox": [719, 194, 924, 264],
    "page_idx": 0
  },
  {
    "type": "table",
    "table_body": "<table>...</table>",
    "table_body_with_bbox": "<table>...</table>",
    "bbox": [177, 256, 1464, 393],
    "bbox_mapping": "merged_from_paddle_ocr",
    "table_cells": [...],
    "page_idx": 0
  }
]

核心算法

1. 文本匹配算法

目标:在 PaddleOCR 的文字框列表中找到与 MinerU/PaddleOCR_VL 文本最匹配的 bbox。

策略

  1. 顺序匹配优先:从 start_index 开始顺序查找
  2. 窗口回溯:如果顺序匹配失败,在上一次匹配位置附近的窗口内查找
  3. 相似度筛选:使用 fuzzywuzzy.partial_ratio 计算相似度,阈值默认 80%
  4. 指针管理:维护两个指针
    • start_index:下次搜索的起始位置
    • last_match_index:上一次匹配的位置(用于窗口回溯)

伪代码

def find_matching_bbox(target_text, text_boxes, start_index, last_match_index, window):
    # 第一阶段:顺序查找
    for i in range(start_index, len(text_boxes)):
        if similarity(target_text, text_boxes[i].text) >= threshold:
            return text_boxes[i], i + 1, i
    
    # 第二阶段:窗口回溯
    window_start = max(0, last_match_index - window)
    window_end = min(len(text_boxes), last_match_index + window)
    
    best_match = None
    best_score = 0
    best_index = -1
    
    for i in range(window_start, window_end):
        score = similarity(target_text, text_boxes[i].text)
        if score > best_score and score >= threshold:
            best_match = text_boxes[i]
            best_score = score
            best_index = i
    
    if best_match:
        return best_match, start_index, best_index
    
    return None, start_index, last_match_index

2. 表格单元格匹配算法

目标:为表格中的每个单元格找到对应的 PaddleOCR 文字框。

步骤

  1. 解析表格 HTML:使用 BeautifulSoup 解析 <table> 结构
  2. 逐行逐列处理

    for row in table.find_all('tr'):
       for cell in row.find_all(['td', 'th']):
           cell_text = cell.get_text()
           matched_bbox = find_matching_bbox(cell_text, ...)
               
           # 添加属性
           cell['data-bbox'] = str(matched_bbox['bbox'])
           cell['data-paddle-index'] = matched_bbox['index']
           cell['data-score'] = matched_bbox['score']
    
  3. 处理合并单元格

    • 检测 colspanrowspan 属性
    • 为每个展开的单元格使用相同的 bbox
  4. 指针更新:每次匹配成功后更新 paddle_pointer,避免重复匹配

参数调优建议

1. look_ahead_window (向前查找窗口)

作用:当顺序匹配失败时,在上一次匹配位置附近的窗口内查找。

推荐值

  • 顺序文档(如流水记录):10-15
  • 复杂版面(如多列排版):15-25
  • 表格密集型5-10

调优方法

# 如果发现匹配错位,可以适当增大窗口
merger = MinerUPaddleOCRMerger(look_ahead_window=20)

# 如果发现误匹配,可以适当减小窗口
merger = MinerUPaddleOCRMerger(look_ahead_window=8)

2. similarity_threshold (相似度阈值)

作用:控制文本匹配的严格程度。

推荐值

  • 高质量 OCR(如扫描件):85-90
  • 一般质量(如拍照):75-85
  • 低质量(如模糊图片):70-80

调优方法

# 如果发现漏匹配,可以降低阈值
merger = MinerUPaddleOCRMerger(similarity_threshold=75)

# 如果发现误匹配,可以提高阈值
merger = MinerUPaddleOCRMerger(similarity_threshold=90)

开发指南

扩展新的合并器

  1. 创建新的合并器类

    # merger/my_custom_merger.py
    from .text_matcher import TextMatcher
    from .data_processor import DataProcessor
    
    class MyCustomMerger:
    def __init__(self):
        self.text_matcher = TextMatcher()
        self.data_processor = DataProcessor(self.text_matcher)
        
    def merge_data(self, custom_json, paddle_json):
        # 实现自定义合并逻辑
        pass
    
  2. 在 DataProcessor 中添加处理方法

    # merger/data_processor.py
    def process_my_custom_data(self, custom_data, paddle_text_boxes):
    # 处理自定义格式数据
    pass
    
  3. 在 MarkdownGenerator 中添加格式化方法

    # merger/markdown_generator.py
    @staticmethod
    def _format_my_custom_element(item: Dict) -> List[str]:
    # 格式化自定义元素
    pass
    

单元测试

# tests/test_merger.py
import pytest
from merger import MinerUPaddleOCRMerger

def test_text_matching():
    merger = MinerUPaddleOCRMerger()
    # 测试文本匹配逻辑
    
def test_table_processing():
    merger = MinerUPaddleOCRMerger()
    # 测试表格处理逻辑

版本历史

v1.2.0 (当前版本)

  • ✅ 新增 PaddleOCR_VL 合并支持
  • ✅ 重构为模块化架构,提高代码复用率
  • ✅ 新增自动格式检测功能
  • ✅ 优化文本匹配算法
  • ✅ 改进表格单元格 bbox 匹配

v1.1.0

  • ✅ 优化表格合并单元格处理
  • ✅ 新增批量处理模式
  • ✅ 改进窗口回溯算法

v1.0.0

  • ✅ 初始版本
  • ✅ 支持 MinerU + PaddleOCR 合并
  • ✅ 基本的文本匹配功能

运行试验数据

1. mineru-vlm-2.5.3

echo "A用户_单元格扫描流水"
python merge_mineru_paddle_ocr.py \
  --mineru-dir "/Users/zhch158/workspace/data/流水分析/A用户_单元格扫描流水/mineru-vlm-2.5.3_Results" \
  --paddle-dir "/Users/zhch158/workspace/data/流水分析/A用户_单元格扫描流水/data_PPStructureV3_Results" \
  --output-dir "/Users/zhch158/workspace/data/流水分析/A用户_单元格扫描流水/merged_results" \
  --output-type "both"

echo "B用户_扫描流水"
python merge_mineru_paddle_ocr.py \
  --mineru-dir "/Users/zhch158/workspace/data/流水分析/B用户_扫描流水/mineru-vlm-2.5.3_Results" \
  --paddle-dir "/Users/zhch158/workspace/data/流水分析/B用户_扫描流水/data_PPStructureV3_Results" \
  --output-dir "/Users/zhch158/workspace/data/流水分析/B用户_扫描流水/merged_results" \
  --output-type "both"

echo "德_内蒙古银行照"
python merge_mineru_paddle_ocr.py \
  --mineru-dir "/Users/zhch158/workspace/data/流水分析/德_内蒙古银行照/mineru-vlm-2.5.3_Results" \
  --paddle-dir "/Users/zhch158/workspace/data/流水分析/德_内蒙古银行照/data_PPStructureV3_Results" \
  --output-dir "/Users/zhch158/workspace/data/流水分析/德_内蒙古银行照/merged_results" \
  --output-type "both"

echo "对公_招商银行图"
python merge_mineru_paddle_ocr.py \
  --mineru-dir "/Users/zhch158/workspace/data/流水分析/对公_招商银行图/mineru-vlm-2.5.3_Results" \
  --paddle-dir "/Users/zhch158/workspace/data/流水分析/对公_招商银行图/data_PPStructureV3_Results" \
  --output-dir "/Users/zhch158/workspace/data/流水分析/对公_招商银行图/merged_results" \
  --output-type "both"

echo "至远彩色印刷工业有限公司"
python merge_mineru_paddle_ocr.py \
  --mineru-dir "/Users/zhch158/workspace/data/至远彩色印刷工业有限公司/mineru-vlm-2.5.3_Results" \
  --paddle-dir "/Users/zhch158/workspace/data/至远彩色印刷工业有限公司/data_PPStructureV3_Results" \
  --output-dir "/Users/zhch158/workspace/data/至远彩色印刷工业有限公司/merged_results" \
  --output-type "both"

2. PaddleOCR_VL_Results

echo "A用户_单元格扫描流水"
python merge_paddleocr_vl_paddleocr.py \
  --paddleocr-vl-dir "/Users/zhch158/workspace/data/流水分析/A用户_单元格扫描流水/PaddleOCR_VL_Results" \
  --paddle-dir "/Users/zhch158/workspace/data/流水分析/A用户_单元格扫描流水/data_PPStructureV3_Results" \
  --output-dir "/Users/zhch158/workspace/data/流水分析/A用户_单元格扫描流水/PaddleOCR_VL_Results_cell_bbox" \
  --output-type "both"

echo "B用户_扫描流水"
python merge_paddleocr_vl_paddleocr.py \
  --paddleocr-vl-dir "/Users/zhch158/workspace/data/流水分析/B用户_扫描流水/PaddleOCR_VL_Results" \
  --paddle-dir "/Users/zhch158/workspace/data/流水分析/B用户_扫描流水/data_PPStructureV3_Results" \
  --output-dir "/Users/zhch158/workspace/data/流水分析/B用户_扫描流水/PaddleOCR_VL_Results_cell_bbox" \
  --output-type "both"

echo "德_内蒙古银行照"
python merge_paddleocr_vl_paddleocr.py \
  --paddleocr-vl-dir "/Users/zhch158/workspace/data/流水分析/德_内蒙古银行照/PaddleOCR_VL_Results" \
  --paddle-dir "/Users/zhch158/workspace/data/流水分析/德_内蒙古银行照/data_PPStructureV3_Results" \
  --output-dir "/Users/zhch158/workspace/data/流水分析/德_内蒙古银行照/PaddleOCR_VL_Results_cell_bbox" \
  --output-type "both"

echo "对公_招商银行图"
python merge_paddleocr_vl_paddleocr.py \
  --paddleocr-vl-dir "/Users/zhch158/workspace/data/流水分析/对公_招商银行图/PaddleOCR_VL_Results" \
  --paddle-dir "/Users/zhch158/workspace/data/流水分析/对公_招商银行图/data_PPStructureV3_Results" \
  --output-dir "/Users/zhch158/workspace/data/流水分析/对公_招商银行图/PaddleOCR_VL_Results_cell_bbox" \
  --output-type "both"

echo "至远彩色印刷工业有限公司"
python merge_paddleocr_vl_paddleocr.py \
  --paddleocr-vl-dir "/Users/zhch158/workspace/data/至远彩色印刷工业有限公司/PaddleOCR_VL_Results" \
  --paddle-dir "/Users/zhch158/workspace/data/至远彩色印刷工业有限公司/data_PPStructureV3_Results" \
  --output-dir "/Users/zhch158/workspace/data/至远彩色印刷工业有限公司/PaddleOCR_VL_Results_cell_bbox" \
  --output-type "both"