关联文档:
PaddleOCR-VL表格文字丢失-OTSL补丁.md(同类 monkey-patch 模式)
升级 MinerU(OCR 升到 PP-OCRv6,并引入动态推理精度)后,银行流水场景
(如 bank_statement_yusys_local)中:
type: "seal",bbox 正常);"text": "", "confidence": 0.0。样例:彭_广东兴宁农村商业银行_page_001.json 中印章框
[325, 124, 538, 325] 可见红章文字,但识别结果为空。
ocr_platform 印章识别走 SealOCRRecognizer → MinerU
PytorchPaddleOCR(lang="seal"):
| 阶段 | 模型 |
|---|---|
| 检测 det | seal_PP-OCRv4_det_server_infer.pth(仍是 v4) |
| 识别 rec | ch_PP-OCRv6_medium_rec_infer.safetensors(升级后换成 v6) |
检测失败时不会进入识别,直接得到空文本。
MinerU 在 mineru/model/utils/pytorchocr/base_ocr_v20.py 引入:
# OCR 推理精度开关:auto 表示 CPU 使用 fp32,非 CPU 自动使用 fp16。
OCR_INFERENCE_PRECISION = "auto"
Mac 上 get_device() 默认选 mps → auto 切到 fp16 →
seal_PP-OCRv4_det(PFHeadLocal)概率图输出 全 NaN → 检测框数 = 0 →
text: ""。
证据:
| 条件 | 结果 |
|---|---|
默认 mps + fp16 |
det 概率图 nan,boxes=0,FULL=[None] |
MINERU_DEVICE_MODE=cpu(fp32) |
正常识别出「广东兴宁…」「电子回单专用章」 |
mps + 强制 fp32 |
同样正常 |
重要结论:问题不是「v6 rec 认不出字」,也不是 layout 漏检。 是 seal det 在 MPS/fp16 数值不稳定。普通
chOCR 在同一裁剪图上 仍能认出横排「电子回单专用章」,进一步说明图本身可读。
本仓库需要持续同步上游 MinerU。直接改 MinerU 源码会导致:
git pull / 升级都要手工重放改动;| 方案 | 说明 | 结论 |
|---|---|---|
改 MinerU base_ocr_v20.py |
同步升级成本高 | ❌ 不采用 |
跑批前 export MINERU_DEVICE_MODE=cpu |
有效但全局强制 CPU,拖慢其它 OCR | ⚠️ 仅临时 |
全局把 OCR_INFERENCE_PRECISION="fp32" |
cuda 也会失去 fp16 加速 | ⚠️ 过粗 |
| 运行时 monkey-patch:仅 mps→fp32 | 不改 MinerU、随本仓库版本化、cuda 仍走上游 auto | ✅ 采用 |
_resolve_inference_dtype精度决策集中在 BaseOCRV20._resolve_inference_dtype(device):
_apply_inference_precision 在模型加载时调用它,再决定是否
self.net.to(dtype=torch.float16)。
因此只要在 OCR 模型初始化之前 替换该方法:
mps → 固定返回 torch.float32;即可在不改 MinerU 文件的前提下修正错误行为。
新增:
ocr_tools/universal_doc_parser/models/adapters/_mineru_ocr_patches.py
(对齐已有的 _mineru_vl_patches.py 模式)
核心逻辑:
def _patch_mps_ocr_fp32() -> None:
import torch
import mineru.model.utils.pytorchocr.base_ocr_v20 as base
if not hasattr(base.BaseOCRV20, "_resolve_inference_dtype"):
raise RuntimeError(
"mineru 接口已变更:找不到 BaseOCRV20._resolve_inference_dtype,"
"请检查 MinerU 版本并更新 ocr_platform OCR 精度补丁。"
)
# 可选:环境变量整库覆盖(auto/fp32/fp16)
override = os.getenv("OCR_PLATFORM_OCR_INFERENCE_PRECISION") or \
os.getenv("MINERU_OCR_INFERENCE_PRECISION")
if override:
base.OCR_INFERENCE_PRECISION = override.strip().lower()
return
orig = base.BaseOCRV20._resolve_inference_dtype
def _resolve_inference_dtype(self, device):
if str(device).lower().startswith("mps"):
return torch.float32
return orig(self, device)
_resolve_inference_dtype.__wrapped__ = orig
base.BaseOCRV20._resolve_inference_dtype = _resolve_inference_dtype
通过 apply_once() 应用,特性:
_applied,仅首次真正打补丁。_resolve_inference_dtype 时抛 RuntimeError,
避免上游改名后补丁静默失效、印章再次变空。fp32 / fp16 / auto(调试用)。__wrapped__ 指向原始实现,便于排查与还原。补丁必须在 AtomModelSingleton.get_atom_model(AtomicModel.OCR, ...) /
PytorchPaddleOCR(...) 之前执行,因为精度在构造时写入网络 dtype。
当前挂载点:
seal_ocr_adapter.py → SealOCRRecognizer.initialize()mineru_adapter.py → MinerUOCRRecognizer.initialize()
# seal_ocr_adapter.py / mineru_adapter.py 的 initialize() 内
from ._mineru_ocr_patches import apply_once as _apply_mineru_ocr_patches
_apply_mineru_ocr_patches()
self.xxx_model = self.atom_model_manager.get_atom_model(
atom_model_name=AtomicModel.OCR,
...
)
印章路径与整页 OCR 路径都会创建 OCR 原子模型;两处都挂是为了 「无论先初始化哪条路径,补丁都已生效」。
apply_once()幂等,重复调用安全。
| 变量 | 作用 |
|---|---|
| (不设) | 默认:mps→fp32,其它设备走 MinerU 原逻辑 |
OCR_PLATFORM_OCR_INFERENCE_PRECISION=fp32\|fp16\|auto |
本仓库优先覆盖 |
MINERU_OCR_INFERENCE_PRECISION=... |
兼容通用名 |
MINERU_DEVICE_MODE=cpu |
整进程强制 CPU(临时绕过,非推荐长期方案) |
在 mineru 环境下:
conda run -n mineru python - <<'PY'
import os, sys, cv2
from pathlib import Path
os.environ.pop("MINERU_DEVICE_MODE", None)
os.environ.pop("OCR_PLATFORM_OCR_INFERENCE_PRECISION", None)
root = Path("/Users/zhch158/workspace/repository.git/ocr_platform")
sys.path[:0] = [str(root), str(root / "ocr_tools/universal_doc_parser")]
from models.adapters._mineru_ocr_patches import apply_once
from mineru.backend.pipeline.model_init import AtomModelSingleton
from mineru.backend.pipeline.model_list import AtomicModel
from mineru.utils.config_reader import get_device
print("device", get_device())
print("apply_once", apply_once()) # True
print("apply_once again", apply_once()) # False
page = ("/Users/zhch158/workspace/data/流水分析/"
"彭_广东兴宁农村商业银行/bank_statement_yusys_local/"
"彭_广东兴宁农村商业银行/彭_广东兴宁农村商业银行_page_001.png")
crop = cv2.imread(page)[124:325, 325:538]
model = AtomModelSingleton().get_atom_model(
atom_model_name=AtomicModel.OCR, lang="seal")
print("dtype", next(model.text_detector.net.parameters()).dtype) # float32 on mps
full = model.ocr(crop, det=True, rec=True)
texts = [it[1][0] for it in (full[0] or [])]
print(texts)
assert any("兴宁" in t or "电子回单" in t for t in texts)
PY
期望输出(节选):
device mps
已应用 MinerU OCR 补丁:mps 默认使用 fp32(避免 seal det NaN)
apply_once True
dtype torch.float32
['广东兴宁农村商业银行股份有限公司', '电子回单专用章']
_mineru_ocr_patches.py,
随 ocr_platform 版本化,方便同步升级 MinerU。_resolve_inference_dtype 被改名或删除,
apply_once() 会抛 RuntimeError 提示更新补丁。_mineru_ocr_patches.py,
由 apply_once() 串联,保持「补丁集中、可开关、可追溯」。
(VL / OTSL 类补丁仍放在 _mineru_vl_patches.py。)| 文件 | 变更 |
|---|---|
models/adapters/_mineru_ocr_patches.py |
新增:MinerU OCR 运行时补丁(mps→fp32) |
models/adapters/seal_ocr_adapter.py |
initialize() 在创建 seal OCR 前调用 apply_once() |
models/adapters/mineru_adapter.py |
MinerUOCRRecognizer.initialize() 同样接入 |
| MinerU 仓库 | 无改动 |