-
> 发布日期: 2026-10-25 · 上一版本:v1.2.3(2026-09-12)
这一版把预处理内存砍掉一半、检测提速 2.6 倍,顺手清掉了两类「写了却不生效」「关了却关不掉」的问题:PDF 双通道的配置终于传到了引擎,mica.ai.ppocr.enabled=false之后应用也能正常启动;海报类版面也有了声明检测参数的口子。
变更总览
|
模块 |
变更 |
类型 |
|---|---|---|
|
|
预处理内存融合(堆 -50%、检测提速 2.6×); |
perf / feat / fix |
|
|
热路径分配优化;新增 |
perf / feat |
|
|
总开关闸门补齐;PDF 配置默认值对齐 |
fix |
|
|
总开关闸门对齐;补注册 |
fix |
一、预处理内存砍半,检测提速 2.6 倍
三处预处理(det / rec / doc_ori)改成「归一化结果直接写入 CHW / NCHW 输出数组」,不再 materialize 中间 HWC 数组,也不再生成 CV_32F 中间 Mat。
|
场景 |
堆分配 |
耗时 |
|---|---|---|
|
det 预处理(3000×4000 图) |
275 MB → 137 MB(-50%) |
110 ms → 42 ms(2.6×) |
|
rec 预处理(50 框 × 1200px 宽) |
79.1 MB → 39.8 MB(-50%) |
21.1 ms → 15.9 ms(-25%) |
|
另省约 40 MB native 内存。 |
-
对使用者的实际影响: 预处理一度是大图 OCR 的内存大头 —— 一张 3000×4000 的图,光归一化就吃掉 275 MB 堆。砍掉一半之后,Docker 里给容器的内存配额、或者本地跑批处理时的峰值占用都跟着松一截。
数值一个字节都没变
uint8 → CV_32F 与 uint8 → 提升为 float 在 0~255 上逐位相同,所以「先转 float 再算」与「直接提升再算」结果一致 —— 这正是省掉 CV_32F 中间 Mat 的依据。
三个预处理测试各保留了一个 reference* 方法(旧实现的副本)做 assertArrayEquals 逐位比对。后续再做优化时,按同样模式把参照实现留在测试里 —— 那是数值真相源,不是死代码。
顺手修掉的 native 内存滞留
文档方向预处理的中心裁剪子 Mat 视图与 resize 本体原先释放顺序颠倒(先放本体、后放视图),现改为「先视图、后本体」,引用计数 2 → 1 → 0 依次归零。
写反了不会立刻报错,只是 native 缓冲滞留到 GC —— 长时间跑批处理时,这是一笔看不见的账。
二、PDF 通道配置接通了
背景
v1.2.2 引入 PDF 双通道(文本层坐标抽取 + 渲染位图 OCR)时,mica.ai.ppocr.pdf.* 只停留在配置对象上,没有传到引擎。引擎的 PDF 入口始终用 PdfOcrConfig.defaults()。
-
实际表现是:
render-dpi调成 300 没反应,min-text-chars调成 10 没反应 —— 参数写得对,行为纹丝不动。
现在
PPOcrV6Config 新增 pdfOcrConfig 字段,PPOcrV6Engine 构造期取它,PDF 入口(run(byte[]) / run(Path) 嗅探到 %PDF- 时)实际消费。图片通道不受影响。
|
参数 |
默认值 |
作用 |
|---|---|---|
|
|
200 |
渲染通道输出 DPI。扫描件内嵌位图有原始分辨率,超过原图信息量的重采样只会放大噪声,发票 / 证件类 200~300 为宜 |
|
|
20 |
文本层可用所需的最小非空白字符数,低于则判定无文本层 |
|
|
0.90 |
文本层可读字符占比下限。缺 ToUnicode 映射的 CID 字体会抽出一批私有区字符,拉低占比 |
|
|
false |
跳过文本层探测,所有页面强制走渲染 + OCR,用于竖排、页面旋转等文本层坐标不可信的版式 |
|
mica:
ai:
ppocr:
pdf:
enabled: true
render-dpi: 200
min-text-chars: 20
min-readable-ratio: 0.90
force-ocr: false
也可以直接用代码覆盖 —— 自定义一个 PdfOcrConfig Bean 即可(Spring @Bean / Solon @Configuration),引擎会直接采用它,无需再改 yml。
两处需要留意的行为变更
1. PdfOcrConfig Bean 现在就是引擎使用的配置,不再是只读副本。此前业务方自定义同名 Bean 会被静默忽略(容器里是自定义值、引擎仍用 yml 或默认值),现在会直接改变引擎行为。
2. pdf.enabled=false 时 Bean 仍然存在,取 PdfOcrConfig.defaults()(此前是不注册这个 Bean)。两端都容忍该 Bean 缺失(回落到 yml 派生值),不会因为排除自动配置类而启动失败。
两端默认值对齐了
v1.2.3 时 Starter 暴露的默认值是 minTextChars=10 / minReadableRatio=0.6,而引擎实际在用的是 20 / 0.90 —— 文档写 10、引擎跑 20。两端现已统一为 20 / 0.90,不配置即保持原有行为。
Solon 端此前还漏了 PdfAutoConfiguration 的注册,压根拿不到 PdfOcrConfig Bean,本次补上。
三、关掉总开关不再启动失败
背景
Spring 端的 StructuredParserAutoConfiguration 漏挂了 mica.ai.ppocr.enabled 这个闸门。
10 个解析器 Bean 都以 PPOcrV6Engine 为构造参数注入。主配置被关掉 ⇒ 引擎 Bean 不存在 ⇒ 抛 NoSuchBeanDefinitionException ⇒ 容器 refresh 直接失败。
也就是说,想用这个开关在某些环境关掉 OCR,结果应用启动失败。
现在
-
三个配置类统一挂到同一个闸门:
-
Spring:
@ConditionalOnProperty(prefix = "mica.ai.ppocr", name = "enabled", havingValue = "true", matchIfMissing = true) -
Solon:
@Condition(onExpression = "${mica.ai.ppocr.enabled:true} == true")
关闭时容器里不注册任何 PP-OCR 功能 Bean(引擎 / 10 个解析器 /PPOcrTemplate/ 配置对象),应用照常启动,可用于「同一套代码在部分环境不启用 OCR」的场景。 -
⚠️ 行为变更: 关闭时两端都不再留下
PdfOcrConfigBean —— Spring 端此前会残留一个(它的PdfAutoConfiguration只挂在mica.ai.ppocr.pdf.enabled上),Solon 端此前压根没注册过PdfAutoConfiguration。 -
例外:
OpenCVNativeLoader不受该开关约束,OpenCV 原生库仍会加载。业务方若自行使用org.opencv.*,需知晓这一点。
四、按版面类型声明 DB 检测参数
一个容易被误判的现象
海报类版面(大字号稀疏数字 + 满屏背景水印),rec 单独跑同一张图完全正确,det 却一个框都没给。
-
根因不在模型,而在 DB 后处理:
boxScore是框(minAreaRect)内概率图的平均值,本质是「框内文字像素占比」。这种版面上 DB 把 8 位大数字和背景水印连成一个整体轮廓,框内真正的笔画只占约 42%,低于默认detBoxThresh=0.6被直接丢弃。
模型看见了,是后处理过滤掉的。 排查时先把 det 的概率图 dump 成 PNG(目标区域纯白高响应即模型看见了),再复刻DbPostProcessor.extractBoxes逐条打印origRect / minSideLen / score / PASS|DROP(原因),就能定位到是哪一条判据把它过滤掉了。
解法:解析器自己声明检测参数
BaseStructuredParser 新增 detParams() 钩子(默认 null = 引擎默认),一站式 parse(...) 自动带上,显式传入的 DbDetParams 优先。解析器可以按自己的版面类型声明专属参数,不影响同引擎上的其它解析器。
public class MyPosterParser extends BaseStructuredParser {
@Override
protected DbDetParams detParams() {
// 放宽 boxThresh 让稀疏大字符框过关,同时收紧 unclipRatio 避免外扩把上下水印卷进来
return DbDetParams.of(0.3f, 0.4f, 1.0f);
}
// ...
}
参数顺序是 DbDetParams.of(thresh, boxThresh, unclipRatio)。已落地在 PddLuckyBagParser.DET_PARAMS(0.3f / 0.4f / 1.0f)。
但别照抄这个参数
证件 / 票据类版面沿用引擎默认(min/64 + 0.6/1.5)。
-
不要改成
detLimitType=max+ 大detLimitSideLen—— 那会缩小整图,小字成片漏检(实测出租车票框数 55 → 26)。 -
不要靠调大
DbDetParams.minSize过滤碎片 —— 会连小字标签一起丢(如行驶证「所有人」变null)。
五、拼多多福袋类型识别
PddLuckyBagResult 新增 getType(),返回 PddLuckyBagType。两类分享图版式几乎一致(搜索邀请码 → 组队双方必得 → 大字号邀请码),仅靠文案区分:
|
枚举 |
含义 |
标题 |
口号 |
|---|---|---|---|
|
|
福袋券 |
百亿补贴 抽福袋 |
组队双方 必得现金或券 |
|
|
水果券 |
百亿补贴 水果分享券 |
组队双方 必得水果券 |
|
|
无法判定 |
特征文案缺失(如只识别出邀请码数字框) |
— |
|
|
六、其他修复与优化
-
识别 / 文档方向预处理显式校验输入为 8bit 3 通道:归一化按「每像素 3 字节」逐行读,而
Mat.get(row, 0, byte[])是从(row, 0)起连续读满数组长度 —— 通道数 3 会通道步长错位。OpenCV 虽有「缓冲长度须为通道数整数倍」的前置校验,但 1 通道(灰度图)恒能绕过。旧实现越界即抛ArrayIndexOutOfBoundsException,故障模式由「立刻报错」退化为「静默产出错误张量」,现改为显式抛IllegalArgumentException。 -
MemoryLeakReproTest误报:判据参考点由 round 5 改为max(round 5, round 10),并移除stabilizeJvmMemory()的早退守卫。round 5 紧跟强制 GC,是堆刚被 uncommit 的人为凹点,后续正常回升被算成「增长」;实测误报样本 round 20 仍比 baseline 低 26.8 MB,却因以 round 5 为参考被判成 +87 MB 泄漏。 -
结构化解析热路径分配优化:火车票解析器 3 处
Pattern.compile提为类常量;户口本解析器标签片段数组提为类常量(消除逐框每次约 110 个 String 引用分配);银行卡持卡人反查 O(n²) → O(n)(语义不变)。 -
死代码清理:
Offset.toFloat,以及 PDF 通道三个无detParams的私有重载。
升级指引
net.dreamlu
mica-ppocr-spring-boot-starter
1.2.4
Solon 用户换 mica-ppocr-solon-plugin,纯 Java 用户换 mica-ppocr-core + mica-ppocr-structured。
升级前必读:行为变更汇总
|
变更 |
影响 |
应对 |
|---|---|---|
|
|
之前靠改 yml 调 PDF 参数的用户会发现行为变了 —— 之前根本没生效 |
不配置即保持原行为;默认值两端已对齐 |
|
|
自定义同名 Bean 会改变引擎行为,此前是静默忽略 |
检查自定义 Bean 是否符合预期 |
|
|
取默认值 |
如依赖 Bean 缺失做判断,需调整 |
|
|
此前 Spring 会残留一个 |
按需改成读配置属性而非读 Bean |
|
识别 / 文档方向预处理对非 8bit 3 通道 Mat 抛 |
此前要么越界异常,要么静默产出错误张量 |
传入前先转成 |
兼容性
-
Java 8 兼容不变,运行依赖字节码均为 major 52。
-
依赖版本:onnxruntime 1.18.0 / opencv 4.9.0-0 / jts-core 1.20.0 / pdfbox 3.0.8 / slf4j 2.0.20 / spring-boot 2.7.18 / mica-auto 2.3.5 / solon 4.1.1。
-
本版实际升级的依赖只有两项:slf4j 2.0.19 → 2.0.20、solon 4.1.0 → 4.1.1(均为小版本,向后兼容)。其余依赖版本未动。
-
Spring Boot Starter 与 Solon 插件能力对齐,类名一一对应。