Преглед изворни кода

feat: 更新MinerU处理流程文档,优化流程图并增加VLM两阶段识别解析

zhch158_admin пре 3 недеља
родитељ
комит
02400a9fbd
1 измењених фајлова са 373 додато и 70 уклоњено
  1. 373 70
      zhch/mineru_处理流程.md

+ 373 - 70
zhch/mineru_处理流程.md

@@ -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的效果。