# 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 输出格式化 |