|
|
@@ -0,0 +1,436 @@
|
|
|
+# MinerU 印章(Seal)识别处理流程
|
|
|
+
|
|
|
+## 概览
|
|
|
+
|
|
|
+MinerU 从 v2.5 版本开始在 pipeline 后端中支持印章(seal)识别。印章识别不依赖单独的专有检测模型,而是分两步完成:
|
|
|
+
|
|
|
+1. **布局检测**:PP-DocLayoutV2 模型在 25 个布局类别中包含了 `seal`(第 20 类),一次性完成印章区域的定位。
|
|
|
+2. **印章文字 OCR**:使用专门针对印章场景调优的 PaddleOCR 变体模型,对检测到的印章区域进行文字检测与识别。
|
|
|
+
|
|
|
+## 使用的模型
|
|
|
+
|
|
|
+| 步骤 | 模型 | 说明 |
|
|
|
+|------|------|------|
|
|
|
+| **Step 1: 印章区域检测** | PP-DocLayoutV2(基于 RT-DETR) | 在页面上定位印章区域,输出 bbox 坐标 |
|
|
|
+| **Step 2: 印章文字检测** | `seal_PP-OCRv4_det_server_infer.pth` | 专用 DB 文字检测器,使用 PP-HGNet_small 骨干网络,多边形检测框 |
|
|
|
+| **Step 2: 印章文字识别** | `ch_PP-OCRv4_rec_server_infer.pth` | 通用中文文字识别模型 |
|
|
|
+| **轻量版** | `seal_PP-OCRv4_det_infer.pth` + `ch_PP-OCRv4_rec_infer.pth` | CPU 环境下自动切换的轻量版 |
|
|
|
+
|
|
|
+**关键点**:不需要单独的"印章检测模型"。印章检测已整合在 PP-DocLayoutV2 中,共享同一个视觉编码器。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 一、Step 1: PP-DocLayoutV2 布局检测
|
|
|
+
|
|
|
+### 1.1 标签定义
|
|
|
+
|
|
|
+PP-DocLayoutV2 定义了 25 个布局类别,`seal` 是第 20 类(索引 20):
|
|
|
+
|
|
|
+```python
|
|
|
+# mineru/model/layout/pp_doclayoutv2.py
|
|
|
+PP_DOCLAYOUT_V2_LABELS = [
|
|
|
+ "abstract", # 0
|
|
|
+ "algorithm", # 1
|
|
|
+ "aside_text", # 2
|
|
|
+ "chart", # 3
|
|
|
+ "content", # 4
|
|
|
+ "display_formula", # 5
|
|
|
+ "doc_title", # 6
|
|
|
+ "figure_title", # 7
|
|
|
+ "footer", # 8
|
|
|
+ "footer_image", # 9
|
|
|
+ "footnote", # 10
|
|
|
+ "formula_number", # 11
|
|
|
+ "header", # 12
|
|
|
+ "header_image", # 13
|
|
|
+ "image", # 14
|
|
|
+ "inline_formula", # 15
|
|
|
+ "number", # 16
|
|
|
+ "paragraph_title", # 17
|
|
|
+ "reference", # 18
|
|
|
+ "reference_content", # 19
|
|
|
+ "seal", # 20 印章 ←
|
|
|
+ "table", # 21
|
|
|
+ "text", # 22
|
|
|
+ "vertical_text", # 23
|
|
|
+ "vision_footnote", # 24
|
|
|
+]
|
|
|
+```
|
|
|
+
|
|
|
+### 1.2 置信度阈值
|
|
|
+
|
|
|
+印章的置信度阈值设为 **0.45**,低于大多数其他类别,以确保印章不被漏检:
|
|
|
+
|
|
|
+```python
|
|
|
+DEFAULT_CLASS_THRESHOLDS = [
|
|
|
+ # ... 其他类别 ...
|
|
|
+ 0.45, # 20 seal ← 较低的阈值
|
|
|
+ # ...
|
|
|
+]
|
|
|
+```
|
|
|
+
|
|
|
+### 1.3 MagicModel 中的映射
|
|
|
+
|
|
|
+布局检测结果进入 `MagicModel` 后,`seal` 标签被映射为 `BlockType.SEAL`:
|
|
|
+
|
|
|
+```python
|
|
|
+# mineru/backend/pipeline/pipeline_magic_model.py
|
|
|
+PP_DOCLAYOUT_V2_LABELS_TO_BLOCK_TYPES = {
|
|
|
+ # ...
|
|
|
+ "seal": BlockType.SEAL,
|
|
|
+ # ...
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+`BlockType.SEAL` 是 MinerU 内部枚举类中定义的类型:
|
|
|
+
|
|
|
+```python
|
|
|
+# mineru/utils/enum_class.py
|
|
|
+class BlockType:
|
|
|
+ # ...
|
|
|
+ SEAL = "seal"
|
|
|
+ # ...
|
|
|
+```
|
|
|
+
|
|
|
+### 1.4 印章块的 span 构造
|
|
|
+
|
|
|
+在 `MagicModel.__build_page_blocks()` 中,`BlockType.SEAL` 类型的块被构造为纯图的 span(类似 image/table/chart):
|
|
|
+
|
|
|
+```python
|
|
|
+# mineru/backend/pipeline/pipeline_magic_model.py (__build_page_blocks)
|
|
|
+elif block["type"] in [BlockType.SEAL]:
|
|
|
+ span_type = ContentType.SEAL
|
|
|
+
|
|
|
+if span_type in [
|
|
|
+ ContentType.IMAGE,
|
|
|
+ ContentType.TABLE,
|
|
|
+ ContentType.CHART,
|
|
|
+ ContentType.INTERLINE_EQUATION,
|
|
|
+ ContentType.SEAL # ← 印章走纯图路径
|
|
|
+]:
|
|
|
+ span = {
|
|
|
+ "bbox": block["bbox"],
|
|
|
+ "type": span_type,
|
|
|
+ }
|
|
|
+ if span_type == ContentType.SEAL:
|
|
|
+ span["content"] = block.get("text") # 印章 OCR 识别出的文字
|
|
|
+```
|
|
|
+
|
|
|
+印章块被当作视觉块处理,不会被送入常规的文本 OCR 流。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 二、Step 2: 印章文字 OCR
|
|
|
+
|
|
|
+### 2.1 入口:batch_analyze.py
|
|
|
+
|
|
|
+在 `batch_analyze.py` 中,所有页面的布局检测完成后,专门有一段代码处理印章 OCR(约第 873-918 行)。
|
|
|
+
|
|
|
+#### 流程:
|
|
|
+
|
|
|
+**a) 收集印章块**
|
|
|
+
|
|
|
+遍历所有页面的布局结果,收集 `label == "seal"` 的块:
|
|
|
+
|
|
|
+```python
|
|
|
+# mineru/backend/pipeline/batch_analyze.py
|
|
|
+seal_ocr_items = []
|
|
|
+for ocr_res_list_dict in ocr_res_list_all_page:
|
|
|
+ for layout_res_item in ocr_res_list_dict['layout_res']:
|
|
|
+ if layout_res_item.get("label") == "seal":
|
|
|
+ seal_ocr_items.append((ocr_res_list_dict, layout_res_item))
|
|
|
+```
|
|
|
+
|
|
|
+**b) 裁剪印章子图**
|
|
|
+
|
|
|
+根据布局检测输出的 bbox 从原图中裁剪出印章区域:
|
|
|
+
|
|
|
+```python
|
|
|
+seal_bbox = normalize_to_int_bbox(
|
|
|
+ layout_res_item.get("bbox"),
|
|
|
+ image_size=(image_h, image_w),
|
|
|
+)
|
|
|
+x0, y0, x1, y1 = seal_bbox
|
|
|
+seal_crop_rgb = np_img[y0:y1, x0:x1] # 裁剪印章子图
|
|
|
+```
|
|
|
+
|
|
|
+**c) 加载 Seal OCR 模型**
|
|
|
+
|
|
|
+通过 `atom_model_manager.get_atom_model()` 获取 `lang="seal"` 的 `PytorchPaddleOCR` 实例:
|
|
|
+
|
|
|
+```python
|
|
|
+if seal_ocr_model is None:
|
|
|
+ seal_ocr_model = atom_model_manager.get_atom_model(
|
|
|
+ atom_model_name=AtomicModel.OCR,
|
|
|
+ lang="seal", # ← 关键:指定印章模式
|
|
|
+ )
|
|
|
+```
|
|
|
+
|
|
|
+**d) 运行 OCR**
|
|
|
+
|
|
|
+对裁剪出的印章图片执行检测+识别:
|
|
|
+
|
|
|
+```python
|
|
|
+seal_crop_bgr = cv2.cvtColor(seal_crop_rgb, cv2.COLOR_RGB2BGR)
|
|
|
+seal_ocr_res = seal_ocr_model.ocr(seal_crop_bgr, det=True, rec=True)[0]
|
|
|
+```
|
|
|
+
|
|
|
+**e) 汇总文字**
|
|
|
+
|
|
|
+将识别出的所有文本段拼接为列表,存入 `layout_res_item["text"]`:
|
|
|
+
|
|
|
+```python
|
|
|
+seal_texts = []
|
|
|
+for seal_item in seal_ocr_res:
|
|
|
+ rec_result = seal_item[1] # (text, score) 元组
|
|
|
+ rec_text = rec_result[0]
|
|
|
+ if rec_text:
|
|
|
+ seal_texts.append(rec_text)
|
|
|
+layout_res_item["text"] = seal_texts
|
|
|
+```
|
|
|
+
|
|
|
+### 2.2 Seal OCR 模型配置
|
|
|
+
|
|
|
+#### 模型文件
|
|
|
+
|
|
|
+定义在 `mineru/model/utils/pytorchocr/utils/resources/models_config.yml`:
|
|
|
+
|
|
|
+```yaml
|
|
|
+seal:
|
|
|
+ det: seal_PP-OCRv4_det_server_infer.pth # 印章文字检测模型
|
|
|
+ rec: ch_PP-OCRv4_rec_server_infer.pth # 中文文字识别模型
|
|
|
+ dict: ppocr_keys_v1.txt # 字符字典
|
|
|
+
|
|
|
+seal_lite: # CPU 环境自动降级为 lite 版
|
|
|
+ det: seal_PP-OCRv4_det_infer.pth
|
|
|
+ rec: ch_PP-OCRv4_rec_infer.pth
|
|
|
+ dict: ppocr_keys_v1.txt
|
|
|
+```
|
|
|
+
|
|
|
+#### 检测模型架构
|
|
|
+
|
|
|
+`seal_PP-OCRv4_det_server_infer` 使用 PP-HGNet_small 骨干 + DB Head(`mineru/model/utils/pytorchocr/utils/resources/arch_config.yaml`):
|
|
|
+
|
|
|
+```yaml
|
|
|
+seal_PP-OCRv4_det_server_infer:
|
|
|
+ model_type: det
|
|
|
+ algorithm: DB
|
|
|
+ Backbone:
|
|
|
+ name: PPHGNet_small
|
|
|
+ Head:
|
|
|
+ name: DBHead
|
|
|
+ k: 50
|
|
|
+```
|
|
|
+
|
|
|
+#### 特殊参数(针对印章场景调优)
|
|
|
+
|
|
|
+在 `PytorchPaddleOCR.__init__()` 中,当 `lang == "seal"` 时,会覆盖默认的 OCR 参数:
|
|
|
+
|
|
|
+```python
|
|
|
+# mineru/model/ocr/pytorch_paddle.py
|
|
|
+if self.is_seal:
|
|
|
+ kwargs['det_limit_side_len'] = 736 # 最小边长限制(确保小印章不被过度缩放)
|
|
|
+ kwargs['det_limit_type'] = 'min'
|
|
|
+ kwargs['det_max_side_limit'] = 4000 # 最大边长限制
|
|
|
+ kwargs['det_db_thresh'] = 0.2 # 极低检测阈值(弧形细小文字不易漏检)
|
|
|
+ kwargs['det_db_box_thresh'] = 0.6 # 检测框阈值
|
|
|
+ kwargs['det_db_unclip_ratio'] = 0.5 # 文本框扩展比例
|
|
|
+ kwargs['det_box_type'] = 'poly' # 使用多边形检测框(印章文字常沿弧形分布)
|
|
|
+ kwargs['use_dilation'] = False # 不进行膨胀操作
|
|
|
+ kwargs['enable_merge_det_boxes'] = False # 不合并检测框
|
|
|
+ kwargs['drop_score'] = 0 # 不丢弃任何低置信度结果
|
|
|
+```
|
|
|
+
|
|
|
+关键区别总结:
|
|
|
+
|
|
|
+| 参数 | seal 模式 | 普通 OCR 模式 | 原因 |
|
|
|
+|------|-----------|---------------|------|
|
|
|
+| `det_box_type` | `'poly'`(多边形) | `'quad'`(四边形) | 印章文字沿弧形分布,矩形框效果差 |
|
|
|
+| `det_db_thresh` | `0.2` | `0.3` | 印章文字细小,需要更低阈值 |
|
|
|
+| `drop_score` | `0` | `0.5` | 印章文字模糊,不丢弃低置信度 |
|
|
|
+| `enable_merge_det_boxes` | `False` | `True` | 印章文字排列稀疏,不合并检测框 |
|
|
|
+| `det_limit_side_len` | `736`(`min`模式) | `960`(`max`模式) | 确保小印章有足够分辨率 |
|
|
|
+
|
|
|
+### 2.3 多边形裁剪与校正
|
|
|
+
|
|
|
+印章文字检测后,使用专用的多边形裁剪和校正管线:
|
|
|
+
|
|
|
+```python
|
|
|
+# mineru/model/ocr/pytorch_paddle.py (ocr 方法)
|
|
|
+if self.is_seal:
|
|
|
+ dt_boxes = self._seal_sort_boxes(dt_boxes) # 排序检测框
|
|
|
+ img_crop_list = self._seal_crop_by_polys(ori_im, dt_boxes) # 多边形裁剪
|
|
|
+```
|
|
|
+
|
|
|
+`CropByPolys` 类(`mineru/model/ocr/seal_crop.py`)负责:
|
|
|
+- 对检测到的多边形框进行排序
|
|
|
+- 使用 `cv2.minAreaRect` + 透视变换进行旋转裁剪
|
|
|
+- 对不规则多边形区域,通过 `AutoRectifier` 进行仿射/单应性校正,将弧形文字展平为水平文字
|
|
|
+- 当多边形简化失败时,回退到外接矩形裁剪以保证流程不会中断
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 三、输出格式
|
|
|
+
|
|
|
+印章识别结果在多个输出文件中体现:
|
|
|
+
|
|
|
+### 3.1 content_list.json
|
|
|
+
|
|
|
+印章作为独立的内容类型 `seal` 输出:
|
|
|
+
|
|
|
+```json
|
|
|
+{
|
|
|
+ "type": "seal",
|
|
|
+ "img_path": "images/xxx.jpg",
|
|
|
+ "text": ["识别出的印章文字"],
|
|
|
+ "bbox": [x0, y0, x1, y1],
|
|
|
+ "page_idx": 0
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+生成代码位于 `mineru/backend/pipeline/pipeline_middle_json_mkcontent.py` 的 `_get_seal_span` 和 `_get_seal_text` 函数。
|
|
|
+
|
|
|
+### 3.2 content_list_v2.json (3.0+)
|
|
|
+
|
|
|
+采用新版结构化格式:
|
|
|
+
|
|
|
+```json
|
|
|
+{
|
|
|
+ "type": "seal",
|
|
|
+ "content": {
|
|
|
+ "image_source": {
|
|
|
+ "path": "images/xxx.jpg"
|
|
|
+ },
|
|
|
+ "seal_content": [
|
|
|
+ {"type": "text", "content": "识别出的印章文字"}
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ "bbox": [x0, y0, x1, y1]
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### 3.3 middle.json
|
|
|
+
|
|
|
+印章块出现在 `preproc_blocks` 中,类型为 `seal`,包含 `lines[].spans[]` 结构,span 类型为 `seal`。
|
|
|
+
|
|
|
+### 3.4 model.json
|
|
|
+
|
|
|
+印章检测结果在布局检测阶段输出,包含 `cls_id: 20`、`label: "seal"`、`score` 和 `bbox` 信息。
|
|
|
+
|
|
|
+### 3.5 layout.pdf
|
|
|
+
|
|
|
+印章区域在布局可视化 PDF 中以独立颜色块标注,与其他布局类别一同展示。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 四、seal 内容类型的枚举定义
|
|
|
+
|
|
|
+```python
|
|
|
+# mineru/utils/enum_class.py
|
|
|
+
|
|
|
+class BlockType:
|
|
|
+ SEAL = "seal" # 块级类型
|
|
|
+
|
|
|
+class ContentType:
|
|
|
+ SEAL = 'seal' # content_list.json 中的类型
|
|
|
+
|
|
|
+class ContentTypeV2:
|
|
|
+ SEAL = 'seal' # content_list_v2.json 中的类型
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 五、完整流程图
|
|
|
+
|
|
|
+```
|
|
|
+输入 PDF 页面
|
|
|
+ │
|
|
|
+ ▼
|
|
|
+┌─────────────────────────────────┐
|
|
|
+│ PP-DocLayoutV2 布局检测 │
|
|
|
+│ (基于 RT-DETR) │
|
|
|
+│ 检测 25 个类别,含 seal │
|
|
|
+│ cls_id=20, threshold=0.45 │
|
|
|
+└───────────────┬─────────────────┘
|
|
|
+ │
|
|
|
+ ┌───────────▼───────────┐
|
|
|
+ │ 过滤 label=="seal" │
|
|
|
+ │ 的布局检测结果 │
|
|
|
+ └───────────┬───────────┘
|
|
|
+ │
|
|
|
+ ▼
|
|
|
+ ┌───────────────────────────┐
|
|
|
+ │ 从原图按 bbox 裁剪 │
|
|
|
+ │ seal_crop = img[y0:y1, x0:x1] │
|
|
|
+ └───────────┬───────────────┘
|
|
|
+ │
|
|
|
+ ▼
|
|
|
+ ┌───────────────────────────┐
|
|
|
+ │ PytorchPaddleOCR(lang="seal") │
|
|
|
+ │ │
|
|
|
+ │ ┌─ 检测 (det): │
|
|
|
+ │ │ seal_PP-OCRv4_det │
|
|
|
+ │ │ DB + PP-HGNet_small │
|
|
|
+ │ │ 多边形框检测 │
|
|
|
+ │ └───────────────────────│
|
|
|
+ │ │
|
|
|
+ │ ┌─ 识别 (rec): │
|
|
|
+ │ │ ch_PP-OCRv4_rec │
|
|
|
+ │ │ 中文文字识别 │
|
|
|
+ │ └───────────────────────│
|
|
|
+ └───────────┬───────────────┘
|
|
|
+ │
|
|
|
+ ▼
|
|
|
+ ┌───────────────────────────┐
|
|
|
+ │ CropByPolys + │
|
|
|
+ │ AutoRectifier │
|
|
|
+ │ 多边形裁剪 + 仿射校正 │
|
|
|
+ │ 弧形文字 → 水平文字 │
|
|
|
+ └───────────┬───────────────┘
|
|
|
+ │
|
|
|
+ ▼
|
|
|
+ ┌───────────────────────────┐
|
|
|
+ │ 输出结果 │
|
|
|
+ │ • seal_text (文字列表) │
|
|
|
+ │ • seal_image (截图) │
|
|
|
+ │ → content_list.json │
|
|
|
+ │ → middle.json │
|
|
|
+ │ → model.json │
|
|
|
+ │ → layout.pdf │
|
|
|
+ └───────────────────────────┘
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 六、调试功能
|
|
|
+
|
|
|
+Seal OCR 支持环境变量控制的调试输出:
|
|
|
+
|
|
|
+| 环境变量 | 说明 |
|
|
|
+|----------|------|
|
|
|
+| `MINERU_SEAL_OCR_DEBUG=1` | 启用调试模式 |
|
|
|
+| `MINERU_SEAL_OCR_DEBUG_DIR=/path/to/dir` | 指定调试输出目录(默认 `output_images/seal_ocr_debug/`) |
|
|
|
+
|
|
|
+调试模式下,每个印章识别样本会输出:
|
|
|
+- `input.png` — 输入图像
|
|
|
+- `det_vis.png` — 检测框可视化
|
|
|
+- `crop_NN.png` — 每个裁剪后的文字区域
|
|
|
+- `meta.json` — 元数据(文字、置信度)
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 七、关键文件索引
|
|
|
+
|
|
|
+| 文件 | 作用 |
|
|
|
+|------|------|
|
|
|
+| `mineru/model/layout/pp_doclayoutv2.py` | PP-DocLayoutV2 模型定义,包含 seal 类别(索引 20)|
|
|
|
+| `mineru/backend/pipeline/batch_analyze.py` | 印章 OCR 主流程(第 873-918 行)|
|
|
|
+| `mineru/backend/pipeline/pipeline_magic_model.py` | MagicModel 中的 seal 块映射与 span 构造 |
|
|
|
+| `mineru/backend/pipeline/model_json_to_middle_json.py` | 中间 JSON 拼装,印章图片截图 |
|
|
|
+| `mineru/model/ocr/pytorch_paddle.py` | PytorchPaddleOCR 类,seal 模式的参数与 OCR 流程 |
|
|
|
+| `mineru/model/ocr/seal_crop.py` | 印章多边形裁剪(`CropByPolys`)与仿射校正 |
|
|
|
+| `mineru/model/ocr/seal_det_warp.py` | 印章检测框仿射校正器(`AutoRectifier`) |
|
|
|
+| `mineru/model/utils/pytorchocr/utils/resources/models_config.yml` | seal/seal_lite 模型文件配置 |
|
|
|
+| `mineru/model/utils/pytorchocr/utils/resources/arch_config.yaml` | seal 检测模型架构配置 |
|
|
|
+| `mineru/utils/enum_class.py` | BlockType.SEAL / ContentType.SEAL 枚举定义 |
|
|
|
+| `mineru/backend/pipeline/pipeline_middle_json_mkcontent.py` | content_list.json / content_list_v2.json 的 seal 输出格式化 |
|