|
|
@@ -6,112 +6,112 @@
|
|
|
graph TB
|
|
|
Start([开始: 输入PDF/图片文件]) --> ReadFile[read_fn: 读取文件字节]
|
|
|
ReadFile --> CheckBackend{选择后端?}
|
|
|
-
|
|
|
+
|
|
|
%% Pipeline 后端流程
|
|
|
CheckBackend -->|backend=pipeline| ConvertPDF1[convert_pdf_bytes_to_bytes_by_pypdfium2<br/>转换PDF字节流]
|
|
|
ConvertPDF1 --> PipelineAnalyze[pipeline_doc_analyze<br/>批量分析文档]
|
|
|
-
|
|
|
+
|
|
|
PipelineAnalyze --> CheckMethod{检查解析方法}
|
|
|
CheckMethod -->|parse_method=auto| Classify[classify模型<br/>PDF分类: txt/ocr]
|
|
|
CheckMethod -->|parse_method=txt| SetTxt[设置txt模式]
|
|
|
CheckMethod -->|parse_method=ocr| SetOcr[设置ocr模式]
|
|
|
-
|
|
|
+
|
|
|
Classify --> PDFType{PDF类型?}
|
|
|
SetTxt --> LoadImages1
|
|
|
SetOcr --> LoadImages1
|
|
|
-
|
|
|
+
|
|
|
PDFType -->|文字PDF txt模式| LoadImages1[load_images_from_pdf<br/>加载PDF页面图像<br/>data: images_list, pdf_doc]
|
|
|
PDFType -->|图片PDF ocr模式| LoadImages1
|
|
|
-
|
|
|
+
|
|
|
LoadImages1 --> BatchInfer[batch_image_analyze<br/>批量推理]
|
|
|
-
|
|
|
+
|
|
|
BatchInfer --> LayoutModel[布局检测模型<br/>MinerU Layout Detection]
|
|
|
LayoutModel --> LayoutResult[layout_det结果<br/>检测文本/表格/图片区域]
|
|
|
-
|
|
|
+
|
|
|
LayoutResult --> OCRBranch{需要OCR?}
|
|
|
OCRBranch -->|txt模式: 文字PDF| ExtractText[提取嵌入文本<br/>直接读取PDF文本层]
|
|
|
OCRBranch -->|ocr模式: 图片PDF| OCRModel[OCR识别模型<br/>PaddleOCR/Surya]
|
|
|
-
|
|
|
+
|
|
|
ExtractText --> MergeText[文本合并]
|
|
|
OCRModel --> OCRResult[OCR识别结果<br/>文本块坐标+内容]
|
|
|
OCRResult --> MergeText
|
|
|
-
|
|
|
+
|
|
|
MergeText --> FormulaCheck{公式识别?}
|
|
|
FormulaCheck -->|formula_enable=True| FormulaModel[公式识别模型<br/>UniMERNet]
|
|
|
FormulaCheck -->|formula_enable=False| TableCheck
|
|
|
FormulaModel --> FormulaResult[公式LaTeX结果]
|
|
|
FormulaResult --> TableCheck
|
|
|
-
|
|
|
+
|
|
|
TableCheck{表格识别?}
|
|
|
TableCheck -->|table_enable=True| TableModel[表格识别模型<br/>StructEqTable]
|
|
|
TableCheck -->|table_enable=False| ModelList
|
|
|
TableModel --> TableResult[表格HTML结果]
|
|
|
TableResult --> ModelList
|
|
|
-
|
|
|
+
|
|
|
ModelList[生成model_list<br/>原始模型输出JSON] --> ToMiddleJson[pipeline_result_to_middle_json<br/>转换为中间JSON格式]
|
|
|
-
|
|
|
+
|
|
|
ToMiddleJson --> MiddleJson[middle_json<br/>包含pdf_info数组]
|
|
|
MiddleJson --> ProcessOutput1[_process_output<br/>处理输出]
|
|
|
-
|
|
|
+
|
|
|
%% VLM 后端流程
|
|
|
CheckBackend -->|backend=vlm-xxx| ConvertPDF2[convert_pdf_bytes_to_bytes_by_pypdfium2<br/>转换PDF字节流]
|
|
|
ConvertPDF2 --> VLMAnalyze[vlm_doc_analyze<br/>VLM端到端分析]
|
|
|
-
|
|
|
+
|
|
|
VLMAnalyze --> CheckVLMBackend{VLM后端类型?}
|
|
|
CheckVLMBackend -->|transformers| VLMTransformers[加载本地VLM模型<br/>MinerU-VLM/MinerU2.5-2509-1.2B]
|
|
|
CheckVLMBackend -->|vllm-engine| VLMEngine[vLLM引擎<br/>本地GPU推理]
|
|
|
CheckVLMBackend -->|http-client| VLMClient[HTTP客户端<br/>连接远程vLLM服务器]
|
|
|
-
|
|
|
+
|
|
|
VLMTransformers --> VLMInfer[VLM模型推理<br/>输入: PDF图像<br/>输出: 结构化JSON]
|
|
|
VLMEngine --> VLMInfer
|
|
|
VLMClient --> HTTPRequest[发送HTTP请求<br/>server_url]
|
|
|
HTTPRequest --> VLMInfer
|
|
|
-
|
|
|
+
|
|
|
VLMInfer --> VLMResult[VLM输出结果<br/>直接生成结构化内容]
|
|
|
VLMResult --> VLMMiddleJson[result_to_middle_json<br/>转换为middle_json格式]
|
|
|
VLMMiddleJson --> ProcessOutput2[_process_output<br/>处理输出]
|
|
|
-
|
|
|
+
|
|
|
%% 输出处理流程
|
|
|
ProcessOutput1 --> DrawLayout{绘制布局框?}
|
|
|
ProcessOutput2 --> DrawLayout
|
|
|
-
|
|
|
+
|
|
|
DrawLayout -->|f_draw_layout_bbox=True| DrawLayoutPDF[draw_layout_bbox<br/>生成layout.pdf]
|
|
|
DrawLayout -->|False| DrawSpan
|
|
|
DrawLayoutPDF --> DrawSpan
|
|
|
-
|
|
|
+
|
|
|
DrawSpan{绘制span框?}
|
|
|
DrawSpan -->|f_draw_span_bbox=True| DrawSpanPDF[draw_span_bbox<br/>生成span.pdf]
|
|
|
DrawSpan -->|False| MakeMarkdown
|
|
|
DrawSpanPDF --> MakeMarkdown
|
|
|
-
|
|
|
+
|
|
|
MakeMarkdown{生成Markdown?}
|
|
|
MakeMarkdown -->|f_dump_md=True| UnionMake1[pipeline_union_make 或<br/>vlm_union_make]
|
|
|
MakeMarkdown -->|False| ContentList
|
|
|
-
|
|
|
+
|
|
|
UnionMake1 --> MarkdownFile[输出: xxx.md<br/>包含文本/公式/表格/图片]
|
|
|
MarkdownFile --> ContentList
|
|
|
-
|
|
|
+
|
|
|
ContentList{生成内容列表?}
|
|
|
ContentList -->|f_dump_content_list=True| UnionMake2[union_make<br/>MakeMode.CONTENT_LIST]
|
|
|
ContentList -->|False| MiddleJsonOut
|
|
|
UnionMake2 --> ContentListFile[输出: xxx_content_list.json<br/>结构化内容列表]
|
|
|
ContentListFile --> MiddleJsonOut
|
|
|
-
|
|
|
+
|
|
|
MiddleJsonOut{保存中间JSON?}
|
|
|
MiddleJsonOut -->|f_dump_middle_json=True| MiddleJsonFile[输出: xxx_middle.json<br/>中间处理结果]
|
|
|
MiddleJsonOut -->|False| ModelOut
|
|
|
MiddleJsonFile --> ModelOut
|
|
|
-
|
|
|
+
|
|
|
ModelOut{保存模型输出?}
|
|
|
ModelOut -->|f_dump_model_output=True| ModelFile[输出: xxx_model.json<br/>原始模型输出]
|
|
|
ModelOut -->|False| End
|
|
|
ModelFile --> End([结束: 所有文件保存到output_dir])
|
|
|
-
|
|
|
+
|
|
|
%% 样式定义
|
|
|
classDef modelClass fill:#e1f5ff,stroke:#01579b,stroke-width:2px
|
|
|
classDef dataClass fill:#fff3e0,stroke:#e65100,stroke-width:2px
|
|
|
classDef processClass fill:#f3e5f5,stroke:#4a148c,stroke-width:2px
|
|
|
-
|
|
|
+
|
|
|
class LayoutModel,OCRModel,FormulaModel,TableModel,VLMTransformers,VLMEngine,Classify modelClass
|
|
|
class LayoutResult,OCRResult,FormulaResult,TableResult,ModelList,MiddleJson,VLMResult dataClass
|
|
|
class PipelineAnalyze,VLMAnalyze,ToMiddleJson,VLMMiddleJson,ProcessOutput1,ProcessOutput2 processClass
|
|
|
@@ -121,23 +121,23 @@ graph TB
|
|
|
|
|
|
### 1. **文字PDF (txt模式) vs 图片PDF (ocr模式)**
|
|
|
|
|
|
-| 维度 | 文字PDF (txt模式) | 图片PDF (ocr模式) |
|
|
|
-|------|------------------|------------------|
|
|
|
-| **检测方式** | `classify` 模型自动判断 | 手动指定或分类识别 |
|
|
|
-| **文本提取** | 直接读取PDF嵌入的文本层 | 使用OCR模型识别图像中的文字 |
|
|
|
-| **处理速度** | 快速(无需OCR) | 较慢(需要OCR推理) |
|
|
|
-| **准确性** | 高(原生文本) | 取决于OCR模型质量 |
|
|
|
-| **典型场景** | Word转PDF、电子书 | 扫描件、截图PDF |
|
|
|
+| 维度 | 文字PDF (txt模式) | 图片PDF (ocr模式) |
|
|
|
+| ------------------ | ------------------------- | --------------------------- |
|
|
|
+| **检测方式** | `classify` 模型自动判断 | 手动指定或分类识别 |
|
|
|
+| **文本提取** | 直接读取PDF嵌入的文本层 | 使用OCR模型识别图像中的文字 |
|
|
|
+| **处理速度** | 快速(无需OCR) | 较慢(需要OCR推理) |
|
|
|
+| **准确性** | 高(原生文本) | 取决于OCR模型质量 |
|
|
|
+| **典型场景** | Word转PDF、电子书 | 扫描件、截图PDF |
|
|
|
|
|
|
### 2. **Pipeline vs VLM 后端差异**
|
|
|
|
|
|
-| 特性 | Pipeline后端 | VLM后端 |
|
|
|
-|------|-------------|---------|
|
|
|
-| **模型架构** | 多个专用模型(Layout+OCR+公式+表格) | 单一端到端VLM模型 |
|
|
|
-| **处理流程** | 分步骤处理,每步调用不同模型 | 一次性输出结构化结果 |
|
|
|
-| **优势** | 可控性强,每步可优化 | 简化流程,理解能力强 |
|
|
|
-| **劣势** | 流程复杂,错误累积 | 需要大模型支持 |
|
|
|
-| **适用场景** | 通用文档解析 | 复杂布局/多语言混排 |
|
|
|
+| 特性 | Pipeline后端 | VLM后端 |
|
|
|
+| ------------------ | ------------------------------------ | -------------------- |
|
|
|
+| **模型架构** | 多个专用模型(Layout+OCR+公式+表格) | 单一端到端VLM模型 |
|
|
|
+| **处理流程** | 分步骤处理,每步调用不同模型 | 一次性输出结构化结果 |
|
|
|
+| **优势** | 可控性强,每步可优化 | 简化流程,理解能力强 |
|
|
|
+| **劣势** | 流程复杂,错误累积 | 需要大模型支持 |
|
|
|
+| **适用场景** | 通用文档解析 | 复杂布局/多语言混排 |
|
|
|
|
|
|
### 3. **关键数据流**
|
|
|
|
|
|
@@ -153,13 +153,15 @@ PDF字节流 → 图像列表 → VLM推理(infer_result) → 中间JSON(middle_
|
|
|
|
|
|
- **xxx.md**: 最终Markdown文档
|
|
|
- **xxx_content_list.json**: 结构化内容列表(用于程序化处理)
|
|
|
-- **xxx_middle.json**: 中间处理结果(包含完整的`pdf_info`数组)
|
|
|
+- **xxx_middle.json**: 中间处理结果(包含完整的 `pdf_info`数组)
|
|
|
- **xxx_model.json**: 原始模型输出(用于调试)
|
|
|
- **xxx_layout.pdf**: 布局可视化(标注文本/表格/图片区域)
|
|
|
- **xxx_span.pdf**: span级别可视化(标注每个文本块)
|
|
|
|
|
|
------------
|
|
|
+---
|
|
|
+
|
|
|
## 2. PaddleOCR 模型架构分析
|
|
|
+
|
|
|
```bash
|
|
|
[10.192.72.11:~/models/modelscope_cache/models/OpenDataLab/PDF-Extract-Kit-1___0/models]$ find .
|
|
|
.
|
|
|
@@ -238,6 +240,7 @@ PDF字节流 → 图像列表 → VLM推理(infer_result) → 中间JSON(middle_
|
|
|
```
|
|
|
|
|
|
**关键发现**:
|
|
|
+
|
|
|
- ✅ 文件扩展名是 `.pth` (PyTorch 格式)
|
|
|
- ✅ 目录名是 `paddleocr_torch`(明确标注了 torch)
|
|
|
- ✅ 这些是**已经转换**为 PyTorch 格式的模型
|
|
|
@@ -261,19 +264,19 @@ MinerU 使用的是 **Paddle2ONNX → PyTorch** 或 **直接转换到 PyTorch**
|
|
|
```mermaid
|
|
|
graph LR
|
|
|
A[原始 PaddlePaddle 模型<br/>.pdparams + .pdmodel] --> B{转换方式}
|
|
|
-
|
|
|
+
|
|
|
B -->|方式1| C[Paddle2ONNX<br/>转换为 .onnx]
|
|
|
B -->|方式2| D[paddle2torch<br/>直接转 PyTorch]
|
|
|
-
|
|
|
+
|
|
|
C --> E[ONNX Runtime<br/>推理]
|
|
|
- C --> F[ONNX → PyTorch<br/>转换为 .pth]
|
|
|
-
|
|
|
+ C --> F[ONNX -> PyTorch<br/>转换为 .pth]
|
|
|
+
|
|
|
D --> G[PyTorch 模型<br/>.pth 文件]
|
|
|
F --> G
|
|
|
-
|
|
|
+
|
|
|
G --> H[MinerU 使用<br/>torch.load/jit.load]
|
|
|
E --> I[MinerU 使用<br/>onnxruntime]
|
|
|
-
|
|
|
+
|
|
|
style A fill:#ffe0b2
|
|
|
style G fill:#c8e6c9
|
|
|
style H fill:#bbdefb
|
|
|
@@ -281,17 +284,17 @@ graph LR
|
|
|
|
|
|
### 4. **实际使用的模型格式对比**
|
|
|
|
|
|
-| 模型类型 | 原始格式 | MinerU 使用格式 | 推理引擎 |
|
|
|
-|---------|---------|----------------|---------|
|
|
|
-| **OCR** | `.pdparams` | `.pth` (PyTorch) | PyTorch |
|
|
|
-| **表格分类** | `.pdparams` | `.onnx` | ONNX Runtime |
|
|
|
-| **方向分类** | `.pdparams` | `.onnx` | ONNX Runtime |
|
|
|
-| **Layout检测** | - | `.pt` (YOLO原生) | PyTorch |
|
|
|
-| **公式识别** | - | `.safetensors` | PyTorch |
|
|
|
+| 模型类型 | 原始格式 | MinerU 使用格式 | 推理引擎 |
|
|
|
+| -------------------- | ------------- | ------------------ | ------------ |
|
|
|
+| **OCR** | `.pdparams` | `.pth` (PyTorch) | PyTorch |
|
|
|
+| **表格分类** | `.pdparams` | `.onnx` | ONNX Runtime |
|
|
|
+| **方向分类** | `.pdparams` | `.onnx` | ONNX Runtime |
|
|
|
+| **Layout检测** | - | `.pt` (YOLO原生) | PyTorch |
|
|
|
+| **公式识别** | - | `.safetensors` | PyTorch |
|
|
|
|
|
|
### 5. **代码验证**
|
|
|
|
|
|
-查看 [`paddleocr_rec.py`](mineru/model/ocr_rec/paddleocr_rec/paddleocr_rec.py ):
|
|
|
+查看 [`paddleocr_rec.py`](mineru/model/ocr_rec/paddleocr_rec/paddleocr_rec.py):
|
|
|
|
|
|
```python
|
|
|
class RecPaddleOCRX:
|
|
|
@@ -306,7 +309,7 @@ class RecPaddleOCRX:
|
|
|
self.model.load_state_dict(checkpoint)
|
|
|
```
|
|
|
|
|
|
-查看 [`paddleocr_det.py`](mineru/model/ocr_det/paddleocr_det/paddleocr_det.py ):
|
|
|
+查看 [`paddleocr_det.py`](mineru/model/ocr_det/paddleocr_det/paddleocr_det.py):
|
|
|
|
|
|
```python
|
|
|
class DetPaddleOCRX:
|
|
|
@@ -344,14 +347,15 @@ class RapidTableModel:
|
|
|
MinerU 团队使用的转换工具可能包括:
|
|
|
|
|
|
1. **Paddle2ONNX** - 官方转换工具
|
|
|
+
|
|
|
```bash
|
|
|
paddle2onnx --model_dir paddle_model \
|
|
|
--model_filename model.pdmodel \
|
|
|
--params_filename model.pdiparams \
|
|
|
--save_file model.onnx
|
|
|
```
|
|
|
-
|
|
|
2. **X2Paddle** - Paddle 到其他框架的转换
|
|
|
+
|
|
|
```bash
|
|
|
x2paddle --framework=paddle \
|
|
|
--model=model.pdmodel \
|
|
|
@@ -359,18 +363,18 @@ MinerU 团队使用的转换工具可能包括:
|
|
|
--save_dir=pytorch_model \
|
|
|
--to_framework=pytorch
|
|
|
```
|
|
|
-
|
|
|
3. **自定义转换脚本** - 直接映射权重
|
|
|
+
|
|
|
```python
|
|
|
import paddle
|
|
|
import torch
|
|
|
-
|
|
|
+
|
|
|
# 加载 Paddle 模型
|
|
|
paddle_state_dict = paddle.load('model.pdparams')
|
|
|
-
|
|
|
+
|
|
|
# 转换为 PyTorch 格式
|
|
|
torch_state_dict = convert_paddle_to_torch(paddle_state_dict)
|
|
|
-
|
|
|
+
|
|
|
# 保存为 .pth
|
|
|
torch.save(torch_state_dict, 'model.pth')
|
|
|
```
|
|
|
@@ -379,13 +383,13 @@ MinerU 团队使用的转换工具可能包括:
|
|
|
|
|
|
### **MinerU 中 PaddleOCR 模型的实际情况**:
|
|
|
|
|
|
-| 特性 | 说明 |
|
|
|
-|------|------|
|
|
|
-| **原始格式** | PaddlePaddle (`.pdparams` + `.pdmodel`) |
|
|
|
-| **转换后格式** | PyTorch (`.pth`) 或 ONNX (`.onnx`) |
|
|
|
-| **推理引擎** | PyTorch / ONNX Runtime |
|
|
|
-| **是否转换** | ✅ **已转换**,不是原生 Paddle 格式 |
|
|
|
-| **优势** | 统一推理引擎,无需安装 PaddlePaddle |
|
|
|
+| 特性 | 说明 |
|
|
|
+| -------------------- | ------------------------------------------- |
|
|
|
+| **原始格式** | PaddlePaddle (`.pdparams` + `.pdmodel`) |
|
|
|
+| **转换后格式** | PyTorch (`.pth`) 或 ONNX (`.onnx`) |
|
|
|
+| **推理引擎** | PyTorch / ONNX Runtime |
|
|
|
+| **是否转换** | ✅**已转换**,不是原生 Paddle 格式 |
|
|
|
+| **优势** | 统一推理引擎,无需安装 PaddlePaddle |
|
|
|
|
|
|
### **为什么要转换**:
|
|
|
|
|
|
@@ -394,4 +398,303 @@ MinerU 团队使用的转换工具可能包括:
|
|
|
3. ✅ **部署方便**:PyTorch 生态更成熟
|
|
|
4. ✅ **统一架构**:其他模型都是 PyTorch,统一管理
|
|
|
|
|
|
-所以答案是:**MinerU 使用的是已经转换为 PyTorch (`.pth`) 或 ONNX (`.onnx`) 格式的 PaddleOCR 模型,不是原生 Paddle 格式**。
|
|
|
+所以答案是:**MinerU 使用的是已经转换为 PyTorch (`.pth`) 或 ONNX (`.onnx`) 格式的 PaddleOCR 模型,不是原生 Paddle 格式**。
|
|
|
+
|
|
|
+# Two Step Extract 流程解析
|
|
|
+
|
|
|
+## 1. VLM 是否调用版式识别模型?
|
|
|
+
|
|
|
+**是的**,`vlm_doc_analyze` 会调用版式识别模型。从代码可以看到:
|
|
|
+
|
|
|
+```python
|
|
|
+# vlm_analyze.py
|
|
|
+results = predictor.batch_two_step_extract(images=images_pil_list)
|
|
|
+```
|
|
|
+
|
|
|
+这里的 `batch_two_step_extract` 内部**第一阶段**就是版式识别(Layout Detection)。
|
|
|
+
|
|
|
+## 2. 两阶段识别详细流程
|
|
|
+
|
|
|
+```mermaid
|
|
|
+graph TB
|
|
|
+ Start([输入: PDF页面图像列表]) --> BatchExtract[batch_two_step_extract]
|
|
|
+
|
|
|
+ BatchExtract --> Stage1[第一阶段<br/>Layout Detection 版式识别]
|
|
|
+
|
|
|
+ Stage1 --> Step1[步骤1: prepare_for_layout<br/>调整图像到1036x1036]
|
|
|
+ Step1 --> Step2[步骤2: VLM推理<br/>prompt: Layout Detection]
|
|
|
+ Step2 --> Step3[步骤3: parse_layout_output<br/>解析输出获取blocks]
|
|
|
+
|
|
|
+ Step3 --> Example[输出示例<br/>box_start 100 200 300 400 box_end<br/>ref_start table ref_end<br/>type=table bbox=归一化坐标]
|
|
|
+
|
|
|
+ Example --> Stage2[第二阶段<br/>Content Extraction 内容提取]
|
|
|
+
|
|
|
+ Stage2 --> Step4{步骤4: FilterBlocks<br/>过滤blocks}
|
|
|
+ Step4 -->|跳过| Skip[SkipTypes<br/>image/list/equation_block]
|
|
|
+ Step4 -->|处理| Process[ProcessTypes<br/>text/title/table/equation]
|
|
|
+
|
|
|
+ Process --> Step5[步骤5: CropBlocks<br/>裁剪每个block区域]
|
|
|
+ Step5 --> Step6[步骤6: RotateBlocks<br/>根据angle旋转<br/>0/90/180/270度]
|
|
|
+ Step6 --> Step7[步骤7: ResizeBlocks<br/>最小边大于等于28px<br/>宽高比小于等于50:1]
|
|
|
+
|
|
|
+ Step7 --> Step8[步骤8: PreparePrompts<br/>准备每个block的prompt]
|
|
|
+ Step8 --> Prompts[Prompt类型<br/>table: Table Recognition<br/>equation: Formula Recognition<br/>text: Text Recognition]
|
|
|
+
|
|
|
+ Prompts --> Step9[步骤9: BatchInfer<br/>VLM批量推理]
|
|
|
+ Step9 --> Step10[步骤10: FillContent<br/>填充blocks的content字段]
|
|
|
+
|
|
|
+ Step10 --> Step11[步骤11: PostProcess<br/>后处理]
|
|
|
+ Step11 --> Actions[处理动作<br/>1.合并相邻文本块<br/>2.处理公式块<br/>3.清理列表项]
|
|
|
+
|
|
|
+ Actions --> Final([最终输出<br/>list of ContentBlock<br/>type + bbox + content])
|
|
|
+ Skip --> Final
|
|
|
+
|
|
|
+ classDef stage1Class fill:#e1f5ff,stroke:#01579b,stroke-width:3px
|
|
|
+ classDef stage2Class fill:#fff3e0,stroke:#e65100,stroke-width:3px
|
|
|
+ classDef processClass fill:#f3e5f5,stroke:#4a148c,stroke-width:2px
|
|
|
+ classDef skipClass fill:#ffebee,stroke:#c62828,stroke-width:2px
|
|
|
+ classDef exampleClass fill:#e8f5e9,stroke:#388e3c,stroke-width:2px
|
|
|
+
|
|
|
+ class Stage1,Step1,Step2,Step3 stage1Class
|
|
|
+ class Stage2,Step5,Step6,Step7,Step9 stage2Class
|
|
|
+ class Step8,Step10,Step11,Actions processClass
|
|
|
+ class Skip skipClass
|
|
|
+ class Example,Prompts exampleClass
|
|
|
+```
|
|
|
+
|
|
|
+## 3. 两阶段详细代码分析
|
|
|
+
|
|
|
+### **阶段1: Layout Detection(版式识别)**
|
|
|
+
|
|
|
+```python
|
|
|
+# mineru_client.py - batch_layout_detect
|
|
|
+def batch_layout_detect(self, images: list[Image.Image]) -> list[list[ContentBlock]]:
|
|
|
+ # 1. 准备图像:resize到1036x1036
|
|
|
+ layout_images = self.helper.batch_prepare_for_layout(self.executor, images)
|
|
|
+
|
|
|
+ # 2. 使用固定的Layout Detection prompt
|
|
|
+ prompt = "[layout]" # 实际为: "\nLayout Detection:"
|
|
|
+ params = DEFAULT_SAMPLING_PARAMS["[layout]"] # temperature=0.0, top_k=1
|
|
|
+
|
|
|
+ # 3. VLM推理
|
|
|
+ outputs = self.client.batch_predict(layout_images, prompt, params)
|
|
|
+
|
|
|
+ # 4. 解析输出
|
|
|
+ return self.helper.batch_parse_layout_output(self.executor, outputs)
|
|
|
+```
|
|
|
+
|
|
|
+**输出格式示例**:
|
|
|
+
|
|
|
+```
|
|
|
+<|box_start|>100 200 300 400<|box_end|><|ref_start|>table<|ref_end|><|rotate_up|>
|
|
|
+<|box_start|>50 100 200 150<|box_end|><|ref_start|>text<|ref_end|>
|
|
|
+<|box_start|>250 300 450 500<|box_end|><|ref_start|>equation<|ref_end|>
|
|
|
+```
|
|
|
+
|
|
|
+**解析后的 ContentBlock**:
|
|
|
+
|
|
|
+```python
|
|
|
+ContentBlock(
|
|
|
+ type="table",
|
|
|
+ bbox=[0.1, 0.2, 0.3, 0.4], # 归一化坐标 (0-1)
|
|
|
+ angle=0, # 从<|rotate_up|>解析
|
|
|
+ content=None # 第一阶段不填充
|
|
|
+)
|
|
|
+```
|
|
|
+
|
|
|
+### **阶段2: Content Extraction(内容提取)**
|
|
|
+
|
|
|
+```python
|
|
|
+# mineru_client.py - stepping_two_step_extract
|
|
|
+def stepping_two_step_extract(self, images: list[Image.Image]) -> list[list[ContentBlock]]:
|
|
|
+ # 1. 获取第一阶段的blocks
|
|
|
+ blocks_list = self.batch_layout_detect(images)
|
|
|
+
|
|
|
+ # 2. 准备提取内容
|
|
|
+ all_images: list[Image.Image | bytes] = []
|
|
|
+ all_prompts: list[str] = []
|
|
|
+ all_params: list[SamplingParams | None] = []
|
|
|
+ all_indices: list[tuple[int, int]] = []
|
|
|
+
|
|
|
+ for img_idx, (image, blocks) in enumerate(zip(images, blocks_list)):
|
|
|
+ # 3. 准备每个block
|
|
|
+ for idx, block in enumerate(blocks):
|
|
|
+ # 跳过不需要提取的类型
|
|
|
+ if block.type in ("image", "list", "equation_block"):
|
|
|
+ continue
|
|
|
+
|
|
|
+ # 4. 裁剪block区域
|
|
|
+ x1, y1, x2, y2 = block.bbox
|
|
|
+ scaled_bbox = (x1 * width, y1 * height, x2 * width, y2 * height)
|
|
|
+ block_image = image.crop(scaled_bbox)
|
|
|
+
|
|
|
+ # 5. 根据角度旋转
|
|
|
+ if block.angle in [90, 180, 270]:
|
|
|
+ block_image = block_image.rotate(block.angle, expand=True)
|
|
|
+
|
|
|
+ # 6. resize处理
|
|
|
+ block_image = self.resize_by_need(block_image)
|
|
|
+
|
|
|
+ # 7. 准备prompt(根据block类型)
|
|
|
+ if block.type == "table":
|
|
|
+ prompt = "\nTable Recognition:"
|
|
|
+ params = MinerUSamplingParams(presence_penalty=1.0, frequency_penalty=0.005)
|
|
|
+ elif block.type == "equation":
|
|
|
+ prompt = "\nFormula Recognition:"
|
|
|
+ params = MinerUSamplingParams(presence_penalty=1.0, frequency_penalty=0.05)
|
|
|
+ else: # text, title等
|
|
|
+ prompt = "\nText Recognition:"
|
|
|
+ params = MinerUSamplingParams(presence_penalty=1.0, frequency_penalty=0.05)
|
|
|
+
|
|
|
+ all_images.append(block_image)
|
|
|
+ all_prompts.append(prompt)
|
|
|
+ all_params.append(params)
|
|
|
+ all_indices.append((img_idx, idx))
|
|
|
+
|
|
|
+ # 8. 批量推理
|
|
|
+ outputs = self.client.batch_predict(all_images, all_prompts, all_params)
|
|
|
+
|
|
|
+ # 9. 填充content
|
|
|
+ for (img_idx, idx), output in zip(all_indices, outputs):
|
|
|
+ blocks_list[img_idx][idx].content = output
|
|
|
+
|
|
|
+ # 10. 后处理
|
|
|
+ return self.helper.batch_post_process(self.executor, blocks_list)
|
|
|
+```
|
|
|
+
|
|
|
+## 4. 为什么要两阶段?
|
|
|
+
|
|
|
+### **设计原因**:
|
|
|
+
|
|
|
+#### 1. **任务分离,提高准确性**
|
|
|
+
|
|
|
+```python
|
|
|
+# 第一阶段:专注于"在哪里" (Where)
|
|
|
+# - 输入:整页图像 (1036x1036)
|
|
|
+# - 输出:所有元素的位置和类型
|
|
|
+# - 优势:全局视野,准确定位
|
|
|
+
|
|
|
+# 第二阶段:专注于"是什么" (What)
|
|
|
+# - 输入:单个元素的裁剪图像
|
|
|
+# - 输出:该元素的具体内容
|
|
|
+# - 优势:局部细节,精确识别
|
|
|
+```
|
|
|
+
|
|
|
+#### 2. **不同的优化目标**
|
|
|
+
|
|
|
+| 特性 | 第一阶段 (Layout) | 第二阶段 (Extract) |
|
|
|
+| ---------------------- | ------------------------- | ---------------------------- |
|
|
|
+| **图像尺寸** | 固定 1036x1036 | 动态(根据block大小) |
|
|
|
+| **Sampling参数** | temperature=0.0(确定性) | 根据类型调整 |
|
|
|
+| **Prompt** | 统一的Layout Detection | 针对性(Table/Formula/Text) |
|
|
|
+| **推理目标** | 结构化输出(bbox+type) | 内容识别 |
|
|
|
+| **批处理方式** | 按页批处理 | 按block批处理 |
|
|
|
+
|
|
|
+#### 3. **处理效率**
|
|
|
+
|
|
|
+```python
|
|
|
+# 示例:10页文档,每页5个blocks
|
|
|
+# 方法1:单阶段(每个block独立识别)
|
|
|
+total_infer = 10 * 5 = 50次推理
|
|
|
+
|
|
|
+# 方法2:两阶段
|
|
|
+total_infer = 10 (layout) + 50 (extract) = 60次推理
|
|
|
+# 但是:
|
|
|
+# - Layout阶段可以大batch(固定尺寸)
|
|
|
+# - Extract阶段可以按类型分组(table一批,text一批)
|
|
|
+# - 跳过image/list等不需要提取的block(实际<50次)
|
|
|
+```
|
|
|
+
|
|
|
+#### 4. **不同类型的特殊处理**
|
|
|
+
|
|
|
+```python
|
|
|
+# mineru_client.py - prepare_for_extract
|
|
|
+def prepare_for_extract(self, image, blocks):
|
|
|
+ for block in blocks:
|
|
|
+ # 跳过不需要提取内容的类型
|
|
|
+ if block.type in ("image", "list", "equation_block"):
|
|
|
+ continue # 节省推理成本
|
|
|
+
|
|
|
+ # 表格:需要高precision
|
|
|
+ if block.type == "table":
|
|
|
+ params = MinerUSamplingParams(
|
|
|
+ presence_penalty=1.0,
|
|
|
+ frequency_penalty=0.005 # 允许重复(表格单元格)
|
|
|
+ )
|
|
|
+
|
|
|
+ # 公式:需要严格格式
|
|
|
+ elif block.type == "equation":
|
|
|
+ params = MinerUSamplingParams(
|
|
|
+ presence_penalty=1.0,
|
|
|
+ frequency_penalty=0.05 # 限制重复
|
|
|
+ )
|
|
|
+
|
|
|
+ # 文本:平衡准确性和流畅性
|
|
|
+ else:
|
|
|
+ params = MinerUSamplingParams(
|
|
|
+ presence_penalty=1.0,
|
|
|
+ frequency_penalty=0.05
|
|
|
+ )
|
|
|
+```
|
|
|
+
|
|
|
+### **对比 Pipeline 后端**
|
|
|
+
|
|
|
+| 维度 | VLM (两阶段) | Pipeline (多模型) |
|
|
|
+| ------------------ | -------------------- | --------------------- |
|
|
|
+| **模型数量** | 1个VLM模型 | 6+个专用模型 |
|
|
|
+| **第一阶段** | VLM Layout Detection | DocLayout-YOLO |
|
|
|
+| **第二阶段** | VLM Content Extract | OCR/公式/表格各自模型 |
|
|
|
+| **优势** | 端到端,理解力强 | 各环节可控,速度快 |
|
|
|
+| **劣势** | 推理成本高 | 错误累积,流程复杂 |
|
|
|
+
|
|
|
+## 5. 实际执行流程示例
|
|
|
+
|
|
|
+```python
|
|
|
+# 输入:1页PDF
|
|
|
+image = load_pdf_page()
|
|
|
+
|
|
|
+# ========== 第一阶段 ==========
|
|
|
+layout_output = """
|
|
|
+<|box_start|>100 200 900 300<|box_end|><|ref_start|>title<|ref_end|>
|
|
|
+<|box_start|>100 320 900 500<|box_end|><|ref_start|>text<|ref_end|>
|
|
|
+<|box_start|>100 520 900 900<|box_end|><|ref_start|>table<|ref_end|>
|
|
|
+"""
|
|
|
+
|
|
|
+blocks = [
|
|
|
+ ContentBlock(type="title", bbox=[0.1, 0.2, 0.9, 0.3], content=None),
|
|
|
+ ContentBlock(type="text", bbox=[0.1, 0.32, 0.9, 0.5], content=None),
|
|
|
+ ContentBlock(type="table", bbox=[0.1, 0.52, 0.9, 0.9], content=None),
|
|
|
+]
|
|
|
+
|
|
|
+# ========== 第二阶段 ==========
|
|
|
+# 准备3个裁剪图像
|
|
|
+crop1 = image.crop([100, 200, 900, 300]) # title
|
|
|
+crop2 = image.crop([100, 320, 900, 500]) # text
|
|
|
+crop3 = image.crop([100, 520, 900, 900]) # table
|
|
|
+
|
|
|
+# 批量推理
|
|
|
+outputs = vlm_infer([
|
|
|
+ (crop1, "\nText Recognition:"),
|
|
|
+ (crop2, "\nText Recognition:"),
|
|
|
+ (crop3, "\nTable Recognition:"),
|
|
|
+])
|
|
|
+
|
|
|
+# 填充结果
|
|
|
+blocks[0].content = "审计报告"
|
|
|
+blocks[1].content = "我们审计了至远彩色印刷工业..."
|
|
|
+blocks[2].content = "<table><tr><td>项目</td><td>金额</td></tr>...</table>"
|
|
|
+
|
|
|
+# 后处理
|
|
|
+final_blocks = post_process(blocks)
|
|
|
+```
|
|
|
+
|
|
|
+## 总结
|
|
|
+
|
|
|
+两阶段设计的核心思想:
|
|
|
+
|
|
|
+1. **分而治之**:第一阶段找位置,第二阶段识内容
|
|
|
+2. **专业化**:每个阶段使用最适合的参数和prompt
|
|
|
+3. **效率优化**:跳过不必要的处理,合理批处理
|
|
|
+4. **质量保证**:全局定位+局部细节,双重保障
|
|
|
+
|
|
|
+这种设计使得单个VLM模型能够达到甚至超越多模型Pipeline的效果。
|