feat: UI 全面重设计(Bento 明亮风)+ 记一笔 NumpadSheet + 管理页模板 + OCR v5→v6 升级
UI 重设计(P1-P5): - 主题从靛蓝 accent 切换为近黑白单色(#111318),品牌感靠排版与财务语义色 - 暗色模式适配 OLED 纯黑,财务色整体提亮一档保证对比度 - 圆角体系新增 xl(24),卡片/弹窗统一大圆角 - 移除 Caveat/Quicksand 自定义字体,切换为系统字体 - 新增 display(34/800) 字阶用于净资产等大数字展示 - 分类/标签/渠道颜色统一到 palette.ts 色板(12 色循环 + FNV 哈希) 记一笔 NumpadSheet(P3 录入闭环): - 全新计算器风格录入面板:方向 chip → 大金额 → 账户 chip → 分类快捷行 → 数字键盘 - 支持表达式输入(20+15-5.5)与字符串十进制求值,避免浮点精度问题 - 高级模式保留多行分录编辑器,简单模式自动推断双腿 - postingLegs.ts 实现 buildPostings 的逆操作(编辑/草稿预填) - 未保存守卫:整页 beforeRemove + modal 脏状态上报 - 全局弹层(NumpadSheetHost)由+按钮唤起,可通过设置开关 Tab 栏重构: - AppTabBar 替换默认 tab bar,中央+按钮唤起全局记账面板 - Tab 从 5 个精简为 4 个(首页/交易/报表/设置),+按钮独立 管理页模板 ManagementScreen: - 统一「列表 + 新增 + 点按编辑 + 长按删除 + FormModal」模式 - 预算/分类/标签/信用卡等管理页消除手写样板 其他: - OCR 模型从 PP-OCRv5 升级到 v6(det/rec/dict 全部替换) - GBK 解码器 import 修复(text-encoding-gbk ESM 兼容) - 批量确认失败数计算修正(用 failedPayees.length 替代 store 快照差值) - 新增 diagnostics 诊断页、TodoStrip 首页待办条、SwipeableTransactionCard 左滑操作 - periodNav.ts 报表周期导航(anchor+period 统一状态管理,本地时区运算) - 测试覆盖 numpadExpression/postingLegs/periodNav/palette/search/transactionGroups/categoryIcons/dateGrid
This commit is contained in:
+39
-21
@@ -1,9 +1,19 @@
|
||||
# PP-OCRv5 (ONNX Runtime) Config Plugin
|
||||
# PP-OCR (ONNX Runtime) Config Plugin
|
||||
|
||||
本插件在 `expo prebuild` 时注入 PP-OCRv5 本地 OCR 原生模块(plan.md 决策 4)。
|
||||
本插件在 `expo prebuild` 时注入 PP-OCR 本地 OCR 原生模块(plan.md 决策 4)。
|
||||
|
||||
引擎选用 **ONNX Runtime**(跨平台、微软官方、Windows 友好),替代原 NCNN 方案。
|
||||
|
||||
## 当前版本
|
||||
|
||||
**PP-OCRv6 small**(det 2.5M 参数 + rec 5.3M 参数,精度显著优于 v5 mobile)
|
||||
|
||||
| 指标 | PP-OCRv5 mobile | PP-OCRv6 small |
|
||||
|------|----------------|----------------|
|
||||
| det Hmean | 75.2% | 84.1% |
|
||||
| rec W-Avg | 73.7% | 81.3% |
|
||||
| rec ONNX 大小 | ~17 MB | ~21 MB |
|
||||
|
||||
## 文件结构
|
||||
|
||||
```
|
||||
@@ -13,43 +23,41 @@ plugins/ppocr/
|
||||
│ ├── OcrModule.kt # React Native Bridge:ONNX Runtime 推理 + det/rec 前后处理
|
||||
│ └── OcrPackage.kt # RN Package 注册(注入到 MainApplication.getPackages)
|
||||
└── assets/ # ONNX 模型 + 字典(需自行下载放置)
|
||||
├── ppocrv5_det.onnx # 文本检测模型
|
||||
├── ppocrv5_rec.onnx # 文本识别模型(多语言,输出 18385 维)
|
||||
└── ppocrv5_dict.txt # PP-OCRv5 多语言字典(18383 字符,CTC 解码用)
|
||||
├── ppocrv6_det.onnx # 文本检测模型(PP-OCRv6 small)
|
||||
├── ppocrv6_rec.onnx # 文本识别模型(PP-OCRv6 small,多语言)
|
||||
└── ppocrv6_dict.txt # PP-OCRv6 多语言字典(CTC 解码用)
|
||||
```
|
||||
|
||||
## 模型获取(一键下载)
|
||||
|
||||
社区已转好的 ONNX 版本(来自官方 Paddle 权重,无质量损失):
|
||||
PP-OCRv6 官方 ONNX 模型来自 [PaddlePaddle/PP-OCRv6 系列](https://huggingface.co/collections/PaddlePaddle/pp-ocrv6):
|
||||
|
||||
```bash
|
||||
# 在项目根目录执行
|
||||
mkdir -p plugins/ppocr/assets
|
||||
cd plugins/ppocr/assets
|
||||
|
||||
# det 模型(4.8 MB)
|
||||
curl -L -o ppocrv5_det.onnx https://huggingface.co/ilaylow/PP_OCRv5_mobile_onnx/resolve/main/ppocrv5_det.onnx
|
||||
# det 模型(PP-OCRv6 small)
|
||||
curl -L -o ppocrv6_det.onnx \
|
||||
https://huggingface.co/PaddlePaddle/PP-OCRv6_small_det_onnx/resolve/main/inference.onnx
|
||||
|
||||
# rec 模型(16.6 MB)
|
||||
curl -L -o ppocrv5_rec.onnx https://huggingface.co/ilaylow/PP_OCRv5_mobile_onnx/resolve/main/ppocrv5_rec.onnx
|
||||
# rec 模型(PP-OCRv6 small)
|
||||
curl -L -o ppocrv6_rec.onnx \
|
||||
https://huggingface.co/PaddlePaddle/PP-OCRv6_small_rec_onnx/resolve/main/inference.onnx
|
||||
|
||||
# PP-OCRv5 多语言字典(74 KB,必须与上面的 rec 模型配套)
|
||||
curl -L -o ppocrv5_dict.txt https://raw.githubusercontent.com/PaddlePaddle/PaddleOCR/main/ppocr/utils/dict/ppocrv5_dict.txt
|
||||
# PP-OCRv6 多语言字典(必须与上面的 rec 模型配套)
|
||||
curl -L -o ppocrv6_dict.txt \
|
||||
https://raw.githubusercontent.com/PaddlePaddle/PaddleOCR/main/ppocr/utils/dict/ppocrv6_dict.txt
|
||||
```
|
||||
|
||||
或用 HuggingFace CLI(首次下载原生模型再转 ONNX 的方式,参见历史 git log)。
|
||||
|
||||
> ⚠️ **字典必须与 rec 模型配套**:ppocrv5_rec.onnx 输出 18385 维(= 18383 字符 + blank + 特殊位),
|
||||
> 必须使用 `ppocrv5_dict.txt`(18383 行)。若错用旧版 `ppocr_keys_v1.txt`(仅 6623 行),
|
||||
> CTC 解码会把真实字符的高索引全部丢弃,只输出形如 `'消'青'露'仰'` 的单引号穿插单字符乱码。
|
||||
|
||||
> 来源说明:[ilaylow/PP_OCRv5_mobile_onnx](https://huggingface.co/ilaylow/PP_OCRv5_mobile_onnx) 是社区维护的 PP-OCRv5 mobile ONNX 镜像,基于官方 [PaddlePaddle/PP-OCRv5_mobile_det](https://huggingface.co/PaddlePaddle/PP-OCRv5_mobile_det) 与 [_rec](https://huggingface.co/PaddlePaddle/PP-OCRv5_mobile_rec) 转换而来。
|
||||
> ⚠️ **字典必须与 rec 模型配套**:v6 字典字符集与 v5 完全不同,混用会导致 CTC 解码乱码。
|
||||
> 若之前使用过 v5 模型,务必删除旧文件(`ppocrv5_det.onnx`、`ppocrv5_rec.onnx`、`ppocrv5_dict.txt`)。
|
||||
|
||||
## 性能配置(参考 AutoAccounting OcrProcessor.kt)
|
||||
|
||||
| 优化项 | 配置 |
|
||||
|--------|------|
|
||||
| 引擎 | ONNX Runtime Android 1.20.1 |
|
||||
| 引擎 | ONNX Runtime Android |
|
||||
| 执行器 | CPU(兼容性最稳,部分设备 GPU 会崩溃) |
|
||||
| 线程 | intraOp=2 / interOp=2 |
|
||||
| det 图像 | 最大边 960px,短边压缩 720px |
|
||||
@@ -81,4 +89,14 @@ JS 层通过 `src/services/ocrBridge.ts` 的 `NativeOcrBridge` 调用,桥接
|
||||
|
||||
真机构建步骤:放置模型文件 → `npx expo prebuild --platform android`(Config Plugin 会把 Kotlin 源码与 `assets/` 下的模型/字典复制进 `android/`)→ `npx expo run:android`。
|
||||
|
||||
> 若之前已 prebuild 过且更换过字典/模型文件,务必重新执行 `npx expo prebuild --clean`,否则 `android/app/src/main/assets/` 下可能残留旧字典(如 `ppocr_keys_v1.txt`),导致新代码找不到配套字典。
|
||||
> 若之前已 prebuild 过且更换过字典/模型文件,务必重新执行 `npx expo prebuild --clean`,否则 `android/app/src/main/assets/` 下可能残留旧模型/字典。
|
||||
|
||||
## v5 → v6 迁移说明
|
||||
|
||||
若从 PP-OCRv5 升级,需完成以下步骤:
|
||||
|
||||
1. **下载新模型**:按上述「模型获取」章节下载 v6 模型和字典
|
||||
2. **删除旧文件**:移除 `ppocrv5_det.onnx`、`ppocrv5_rec.onnx`、`ppocrv5_dict.txt`
|
||||
3. **代码已自动适配**:`OcrModule.kt` 中的常量已更新为 v6 文件名
|
||||
4. **重新 prebuild**:`npx expo prebuild --clean` 确保旧资产被清理
|
||||
5. **验证 tensor 名称**:v6 ONNX 模型的输入 tensor 名可能与 v5 不同,若推理报错需用 Netron 检查并调整 `OcrModule.kt` 中的 `detInputs`/`recInputs` map key
|
||||
|
||||
@@ -29,11 +29,11 @@ import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
/**
|
||||
* PP-OCRv5 (ONNX Runtime) React Native Bridge(plan.md「3.4 Layer 2」+「决策 4 Config Plugin」)。
|
||||
* PP-OCRv6 (ONNX Runtime) React Native Bridge(plan.md「3.4 Layer 2」+「决策 4 Config Plugin」)。
|
||||
*
|
||||
* 引擎:ONNX Runtime(跨平台、微软官方、Windows 友好),替代 NCNN 路线。
|
||||
* 模型:ppocrv5_det.onnx + ppocrv5_rec.onnx(从 ilaylow/PP_OCRv5_mobile_onnx 下载)。
|
||||
* 字典:ppocrv5_dict.txt(PP-OCRv5 多语言字典,18383 字符;rec 模型 18385 维输出 = 字典 + blank + 特殊位)。
|
||||
* 模型:ppocrv6_det.onnx + ppocrv6_rec.onnx(PP-OCRv6 small,从 PaddlePaddle 官方 HuggingFace 下载)。
|
||||
* 字典:ppocrv6_dict.txt(PP-OCRv6 多语言字典,18708 字符;rec 模型 18710 维输出 = 字典 + blank + 特殊位)。
|
||||
*
|
||||
* 流水线:
|
||||
* 1. det(文本检测):bitmap → DB 后处理得到文本框
|
||||
@@ -94,7 +94,7 @@ class OcrModule(private val context: ReactApplicationContext) :
|
||||
ortEnv = env
|
||||
dictionary = dict
|
||||
initialized = true
|
||||
Log.i(OCR_MODULE_NAME, "PP-OCRv5 ONNX 模型加载成功(det+rec, dict=${dict.size})")
|
||||
Log.i(OCR_MODULE_NAME, "PP-OCRv6 ONNX 模型加载成功(det+rec, dict=${dict.size})")
|
||||
} catch (e: Exception) {
|
||||
initFailed = true
|
||||
Log.e(OCR_MODULE_NAME, "OCR 初始化失败: ${e.message}", e)
|
||||
@@ -108,7 +108,7 @@ class OcrModule(private val context: ReactApplicationContext) :
|
||||
private val recEnv: OrtEnvironment? get() = ortEnv
|
||||
|
||||
/**
|
||||
* 加载 ppocrv5_dict.txt 字典。
|
||||
* 加载 ppocrv6_dict.txt 字典。
|
||||
*
|
||||
* PaddleOCR CTC 约定:模型输出 logits 的 index 0 是 blank,字符从 index 1 开始;
|
||||
* 字典条目 dictionary[i] 对应模型输出 index i+1。解码时 dictIdx = argmaxIdx - 1。
|
||||
@@ -509,8 +509,9 @@ class OcrModule(private val context: ReactApplicationContext) :
|
||||
*
|
||||
* PaddleOCR 约定:logits 的 index 0 固定是 blank,字符从 index 1 起,
|
||||
* dictionary[i] 对应模型输出 index i+1。因此 dictIdx = argmaxIdx - 1。
|
||||
* 已用 onnxruntime 实证:argmax 序列中 0 占多数(即 blank),真实字符索引
|
||||
* (如 90→'支')按 idx-1 映射到 dictionary 即可正确还原中文。
|
||||
*
|
||||
* PP-OCRv6 模型输出已经是概率分布(值域 [0,1]),无需额外 softmax。
|
||||
* 直接取 argmax 对应的值作为该时间步的置信度。
|
||||
*/
|
||||
private fun ctcGreedyDecode(logits: Array<FloatArray>): Pair<String, Float> {
|
||||
if (logits.isEmpty()) return "" to 0f
|
||||
@@ -527,16 +528,12 @@ class OcrModule(private val context: ReactApplicationContext) :
|
||||
for (i in 1 until numClasses) {
|
||||
if (logits[t][i] > maxVal) { maxVal = logits[t][i]; maxIdx = i }
|
||||
}
|
||||
// softmax 概率(用于置信度统计)
|
||||
var expSum = 0.0
|
||||
for (i in 0 until numClasses) expSum += Math.exp(logits[t][i].toDouble())
|
||||
val prob = Math.exp(maxVal.toDouble()) / expSum
|
||||
|
||||
if (maxIdx != blankIdx && maxIdx != lastIdx) {
|
||||
val dictIdx = maxIdx - 1 // index 1..N → dictionary[0..N-1]
|
||||
if (dictIdx in 0 until dictionary.size) {
|
||||
sb.append(dictionary[dictIdx])
|
||||
confSum += prob.toFloat()
|
||||
confSum += maxVal // v6 输出已是概率,直接用
|
||||
confCount++
|
||||
}
|
||||
}
|
||||
@@ -650,10 +647,10 @@ class OcrModule(private val context: ReactApplicationContext) :
|
||||
/** rec 单行最大宽度。 */
|
||||
private const val REC_MAX_WIDTH = 320
|
||||
/** assets 中的模型/字典文件名。 */
|
||||
private const val ASSET_DET_MODEL = "ppocrv5_det.onnx"
|
||||
private const val ASSET_REC_MODEL = "ppocrv5_rec.onnx"
|
||||
// PP-OCRv5 多语言识别模型的配套字典(18383 字符 + 运行时 1 blank = 18385 维输出)。
|
||||
// 注意:必须与 rec 模型配套,错用旧版 ppocr_keys_v1.txt(6623)会导致 CTC 解码乱码。
|
||||
private const val ASSET_DICT = "ppocrv5_dict.txt"
|
||||
private const val ASSET_DET_MODEL = "ppocrv6_det.onnx"
|
||||
private const val ASSET_REC_MODEL = "ppocrv6_rec.onnx"
|
||||
// PP-OCRv6 多语言识别模型的配套字典(18708 字符 + 运行时 1 blank = 18710 维输出)。
|
||||
// 注意:必须与 rec 模型配套,错用 v5 字典(18383)会导致 CTC 解码丢字符。
|
||||
private const val ASSET_DICT = "ppocrv6_dict.txt"
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
+17967
-17642
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Reference in New Issue
Block a user