MinerU 从 v2.5 版本开始在 pipeline 后端中支持印章(seal)识别。印章识别不依赖单独的专有检测模型,而是分两步完成:
seal(第 20 类),一次性完成印章区域的定位。| 步骤 | 模型 | 说明 |
|---|---|---|
| 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 中,共享同一个视觉编码器。
PP-DocLayoutV2 定义了 25 个布局类别,seal 是第 20 类(索引 20):
# 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
]
印章的置信度阈值设为 0.45,低于大多数其他类别,以确保印章不被漏检:
DEFAULT_CLASS_THRESHOLDS = [
# ... 其他类别 ...
0.45, # 20 seal ← 较低的阈值
# ...
]
布局检测结果进入 MagicModel 后,seal 标签被映射为 BlockType.SEAL:
# mineru/backend/pipeline/pipeline_magic_model.py
PP_DOCLAYOUT_V2_LABELS_TO_BLOCK_TYPES = {
# ...
"seal": BlockType.SEAL,
# ...
}
BlockType.SEAL 是 MinerU 内部枚举类中定义的类型:
# mineru/utils/enum_class.py
class BlockType:
# ...
SEAL = "seal"
# ...
在 MagicModel.__build_page_blocks() 中,BlockType.SEAL 类型的块被构造为纯图的 span(类似 image/table/chart):
# 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 流。
在 batch_analyze.py 中,所有页面的布局检测完成后,专门有一段代码处理印章 OCR(约第 873-918 行)。
a) 收集印章块
遍历所有页面的布局结果,收集 label == "seal" 的块:
# 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 从原图中裁剪出印章区域:
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 实例:
if seal_ocr_model is None:
seal_ocr_model = atom_model_manager.get_atom_model(
atom_model_name=AtomicModel.OCR,
lang="seal", # ← 关键:指定印章模式
)
d) 运行 OCR
对裁剪出的印章图片执行检测+识别:
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"]:
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
定义在 mineru/model/utils/pytorchocr/utils/resources/models_config.yml:
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):
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 参数:
# 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模式) |
确保小印章有足够分辨率 |
印章文字检测后,使用专用的多边形裁剪和校正管线:
# 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 进行仿射/单应性校正,将弧形文字展平为水平文字印章识别结果在多个输出文件中体现:
印章作为独立的内容类型 seal 输出:
{
"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 函数。
采用新版结构化格式:
{
"type": "seal",
"content": {
"image_source": {
"path": "images/xxx.jpg"
},
"seal_content": [
{"type": "text", "content": "识别出的印章文字"}
]
},
"bbox": [x0, y0, x1, y1]
}
印章块出现在 preproc_blocks 中,类型为 seal,包含 lines[].spans[] 结构,span 类型为 seal。
印章检测结果在布局检测阶段输出,包含 cls_id: 20、label: "seal"、score 和 bbox 信息。
印章区域在布局可视化 PDF 中以独立颜色块标注,与其他布局类别一同展示。
# 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 输出格式化 |