首页
/ PaddleOCR PP-OCRv6 Android 部署实战:基于 ONNX Runtime 的 OCR SDK 与 Demo 全解析

PaddleOCR PP-OCRv6 Android 部署实战:基于 ONNX Runtime 的 OCR SDK 与 Demo 全解析

2026-09-11 17:57:24作者:劳婵绚Shirley

本文基于 PaddleOCR 官方仓库 deploy/ppocr-android 示例项目,系统讲解 PP-OCRv6 在 Android 端的 ONNX Runtime 推理部署方案。内容包括环境准备、模型文件布局、SDK 的两种集成方式(源码依赖 / AAR)、完整 API 参考与配置参数,以及检测/识别流水线的源码级实现原理和自动化性能测试方法。读完后你将能够把该 SDK 直接集成进自己的 Android 应用,完成端到端的文字检测与识别,并对各阶段耗时进行定位分析。

项目概览:SDK 与 Demo 分离架构

deploy/ppocr-android 是 PaddleOCR v6 的 Android 部署示例,使用 ONNX Runtime 完成移动端 OCR 推理。项目采用 SDK 与 Demo 分离 的架构:ppocr-sdk 是一个标准的 Android Library,可以独立集成到任意第三方应用;app 则是一个基于 MVVM + Jetpack Compose 的演示应用,展示 SDK 的完整用法。

核心特性:

  • 端到端的文字检测 + 识别流水线(Det → Rec)
  • 支持 PP-OCRv6 系列 ONNX 模型
  • 细粒度性能计时(检测/识别阶段分别拆分出预处理、推理、后处理耗时)
  • MVVM + Jetpack Compose 演示应用
  • AAR 集成支持

实际目录结构(对照仓库中 deploy/ppocr-android 的真实文件):

ppocr-android/
├── ppocr-sdk/                    # OCR SDK(Android Library)
│   ├── src/main/
│   │   ├── assets/models/        # 模型文件目录(需自行放入)
│   │   │   ├── det/              # 检测模型:inference.onnx
│   │   │   └── rec/              # 识别模型:inference.onnx、inference.yml
│   │   └── java/com/paddle/ocr/
│   │       ├── PaddleOCR.kt      # [公开 API] SDK 入口
│   │       ├── PaddleOCRConfig.kt # [公开 API] 推理配置
│   │       ├── EngineConfig.kt   # [公开 API] 引擎配置(线程数)
│   │       ├── engine/           # OCREngine / DetectionEngine /
│   │       │                     # RecognitionEngine / ORTSessionManager
│   │       ├── preprocess/       # DetPreprocessor / RecPreprocessor
│   │       ├── postprocess/      # DBPostProcessor / CTCDecoder /
│   │       │                     # PolygonUnclip / QuadTextCrop 等
│   │       ├── model/            # OCRResult / OCRRunResult / OCRError ...
│   │       └── util/             # OpenCVUtils / YamlUtils / BitmapUtils
│   └── build.gradle.kts
├── app/                          # Demo App(Compose + MVVM)
│   ├── src/main/java/com/paddle/ocr/demo/
│   │   ├── OCRApplication.kt     # 应用启动时初始化 SDK
│   │   ├── MainActivity.kt
│   │   └── ui/                   # Compose UI(HomeScreen、组件等)
│   └── build.gradle.kts
├── gradle/libs.versions.toml     # 版本目录(依赖版本统一管理)
├── run_benchmark.sh              # 性能测试脚本
└── settings.gradle.kts

从源码结构看,SDK 内部按职责分为四层:engine(会话管理与检测/识别引擎)、preprocess(图像预处理)、postprocess(DB 后处理与 CTC 解码)、model(结果与错误模型),入口 PaddleOCR.kt 是唯一需要对外暴露的 facade。

环境要求

依赖 版本
Android Studio Ladybug (2024.2+)
JDK 17
Kotlin 2.1.0
minSdk 26(Android 8.0)
ONNX Runtime 1.21.1
OpenCV 4.5.3(com.quickbirdstudios:opencv)
compileSdk / targetSdk 35

以上版本与仓库中的 版本目录 完全一致:onnxruntime = "1.21.1"opencv = "4.5.3"coroutines = "1.9.0"kotlin = "2.1.0"、AGP 8.7.3。SDK 模块 build.gradle.ktsminSdk = 26compileSdk = 35、JVM target 为 17,Demo 模块 app/build.gradle.ktstargetSdk = 35

快速开始

1. 获取代码

获取 PaddleOCR 仓库后进入示例目录:

git clone https://gitcode.com/GitHub_Trending/pa/PaddleOCR.git
cd PaddleOCR/deploy/ppocr-android

2. 准备模型文件

项目支持以下 ONNX 推理模型(可从 PaddlePaddle 官方模型仓库获取对应的 *_onnx_infer 推理包):

模型 说明
PP-OCRv6_small small 规格的检测 + 识别 ONNX 模型
PP-OCRv6_tiny tiny 规格的检测 + 识别 ONNX 模型,体积更小
PP-OCRv5_mobile PP-OCRv5 mobile 检测 + 识别 ONNX 模型

下载并解压后,将文件放到 ppocr-sdk/src/main/assets/models/

  • 检测模型:inference.onnx 放入 models/det/
  • 识别模型:inference.onnxinference.yml 放入 models/rec/

模型为什么必须放在 assets 里?从源码看,ORTSessionManager.ktreadModelAsset 通过 context.assets.open(assetPath) 读取模型字节流,缺文件时会抛出 OCRError.ModelNotFound;识别模型的 inference.yml 则被 ModelConfig.kt 解析,从中提取 PostProcess: 段下的 character_dict: 字符列表(末尾自动补空格字符)供 CTC 解码使用。因此 yml 与 onnx 必须同时提供。

3. 构建并运行

# 构建 Debug APK
./gradlew :app:assembleDebug

# 安装到设备
./gradlew :app:installDebug

也可以直接在 Android Studio 中 Run。Demo 的初始化逻辑在 OCRApplication.ktApplication.onCreate() 中先调用 OpenCVUtils.init() 加载 OpenCV 原生库,成功后在 IO 协程中执行 PaddleOCR.create(),并通过 StateFlow<ModelState>(Loading / Ready / Error 三态)驱动 Compose UI;加载失败时 UI 提供 retryLoadModels() 重试入口。

4. 体验 Demo

  1. 打开 "PP-OCRv6 Demo" 应用
  2. 等待模型加载完成
  3. 点击 "Select from Gallery" 选择一张图片
  4. 查看识别结果与分阶段耗时统计(UI 中的 TimingBar 组件展示各阶段毫秒数)

SDK 集成

方式一:源码依赖

  1. ppocr-sdk/ 拷贝到你的项目根目录

  2. settings.gradle.kts 中加入:

    include(":ppocr-sdk")
    
  3. 在 app 模块的 build.gradle.kts 中加入:

    implementation(project(":ppocr-sdk"))
    

方式二:AAR 依赖

# 构建 AAR
./gradlew :ppocr-sdk:assembleRelease

产物位于 ppocr-sdk/build/outputs/aar/ppocr-sdk-release.aar。将 AAR 放入宿主工程的 libs/ 目录,并在 app 模块的 build.gradle.kts 中声明:

dependencies {
    implementation(files("libs/ppocr-sdk-release.aar"))
    // AAR 不会传递依赖,需要手动补齐
    implementation("com.microsoft.onnxruntime:onnxruntime-android:1.21.1")
    implementation("com.quickbirdstudios:opencv:4.5.3")
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.9.0")
}

注意 SDK 模块在 build.gradle.kts 中配置了 consumerProguardFiles("proguard-rules.pro"),消费方 release 构建时会自动携带其混淆规则(见下文工程注意事项)。

API 参考

创建实例

PaddleOCR 提供三个 suspend 工厂重载(见 PaddleOCR.kt),内部统一通过 withContext(Dispatchers.IO) 执行,并使用 applicationContext 避免持有 Activity 引用:

// 默认配置(使用 assets 默认模型路径)
val ocr = PaddleOCR.create(context)

// 自定义配置
val ocr = PaddleOCR.create(
    context = context,
    config = PaddleOCRConfig(
        detThresh = 0.3f,
        detBoxThresh = 0.6f,
        recScoreThresh = 0.0f,
        recBatchSize = 1,
    ),
    engineConfig = EngineConfig(numThreads = 4),
)

// 自定义模型路径(模型不在默认 assets 位置时)
val ocr = PaddleOCR.create(
    context = context,
    config = config,
    engineConfig = EngineConfig(numThreads = 4),
    detModelAssetPath = "models/det/inference.onnx",
    recModelAssetPath = "models/rec/inference.onnx",
    recConfigAssetPath = "models/rec/inference.yml",
)

执行 OCR

// 传入 Bitmap
val result = ocr.recognize(bitmap)

// 传入图片字节(推荐,与 Python 端管线行为一致)
val result = ocr.recognize(imageBytes)

// 读取结果
result.results.forEach { item ->
    println("Text: ${item.text}, Confidence: ${item.confidence}")
    println("Box: ${item.box.points}")
}
println("Detection: ${result.detectionTimeMs}ms, Recognition: ${result.recognitionTimeMs}ms")

两种入参最终都汇聚到同一个引擎执行入口:BitmapBitmapUtils.bitmapToBGRMat 转 Mat,字节流经 BitmapUtils.imdecodeBGR 解码(解码失败或空输入会抛出 OCRError.InvalidImage)。实例还暴露 coldLoadTimeMs 属性,可直接读取模型冷加载耗时。

释放资源

ocr.release()

配置参数

PaddleOCRConfig 完整定义(与 PaddleOCRConfig.kt 一致):

data class PaddleOCRConfig(
    val detImgMode: String = "BGR",         // 检测输入颜色模式(BGR / RGB)
    val detLimitSideLen: Int = 64,          // 检测侧长限制
    val detLimitType: String = "min",       // 限制策略(min / max / none)
    val detMaxSideLimit: Int = 4000,        // 最大侧长上限
    val detThresh: Float = 0.3f,            // 概率图二值化阈值
    val detBoxThresh: Float = 0.6f,         // 检测框置信度阈值
    val detUnclipRatio: Float = 1.5f,       // 检测框扩张(unclip)比例
    val detMaxCandidates: Int = 3000,      // 最大候选框数量
    val detUseDilation: Boolean = false,   // 是否对概率图做膨胀
    val detScoreMode: String = "fast",     // 打分模式(fast / slow)
    val detBoxType: String = "quad",       // 检测框类型(仅支持 quad)
    val recScoreThresh: Float = 0.0f,      // 识别置信度阈值(低于则丢弃)
    val recBatchSize: Int = 1,              // 识别批大小
)

参数与源码行为的对应关系(有助于调参):

  • detLimitSideLen / detLimitType / detMaxSideLimit / detImgMode:传入 DetPreprocessor。默认 limit_type=min 意味着以较短边对齐 64,再按 32 的倍数缩放,且较长边不超过 detMaxSideLimit(4000),保证大图不爆内存。
  • detThresh / detBoxThresh / detUnclipRatio / detMaxCandidates / detUseDilation / detScoreMode / detBoxType:全部作用于 DBPostProcessor(DB 算法后处理,下节详述)。其中 box_type 目前仅支持 "quad",源码中有 require(boxType == "quad") 校验。
  • recScoreThresh:在 OCREngine.kt 中对每条识别结果过滤,confidence < recScoreThresh 的行不会进入最终结果;默认 0.0 表示全部保留。
  • recBatchSize:识别裁剪图按此大小分批送入识别引擎(coerceAtLeast(1) 保证至少为 1);当为 1 时结果会额外记录逐行识别耗时 perLineRecMs,便于定位慢行。
  • EngineConfig(numThreads = 4):设置 ONNX Runtime 会话的 setIntraOpNumThreads,并启用 OptLevel.ALL_OPT 全量图优化(见 ORTSessionManager.kt)。

结果模型

data class OCRRunResult(
    val results: List<OCRResult>,        // 识别结果列表
    val detectionTimeMs: Long,           // 检测总耗时
    val recognitionTimeMs: Long,         // 识别总耗时
    val totalTimeMs: Long,               // 总耗时
    val lineCount: Int,                  // 识别行数
    // 详细分段计时
    val detPreprocessMs: Long = 0,      // 检测-预处理
    val detInferenceMs: Long = 0,       // 检测-推理
    val detPostprocessMs: Long = 0,     // 检测-后处理
    val recPreprocessMs: Long = 0,      // 识别-预处理
    val recInferenceMs: Long = 0,       // 识别-推理
    val recPostprocessMs: Long = 0,     // 识别-后处理
    val pipelineOverheadMs: Long = 0,   // 管线自身开销(总耗时 - 检测 - 识别)
    val coldLoadTimeMs: Long = 0,       // 模型冷加载耗时
    // 输入张量形状
    val detInputShape: List<Int> = emptyList(),
    val recInputShapes: List<List<Int>> = emptyList(),
    // 逐行识别耗时(仅当 recBatchSize == 1 时填充)
    val perLineRecMs: List<Long> = emptyList(),
)

data class OCRResult(
    val box: OCRBox,                     // 检测框四点坐标
    val text: String,                    // 识别文本
    val confidence: Float,               // 置信度
)

完整的字段定义见 OCRRunResult.kt。异常则统一收敛为 OCRError.kt 中的 sealed 类:ModelNotFoundModelLoadFailedConfigParseFailedInvalidImageInferenceFailedDecodeError,便于上层做精确的错误处理。

推理流水线源码剖析

OCREngine 主流程

OCREngine.ktrun 方法体现了完整管线:

  1. 检测DetectionEngine.detect() 输出候选框列表;若无框则直接返回空结果(此时 recognitionTimeMs = 0)。
  2. 排序BoxSorter.sortInReadingOrder() 将四边形框按阅读顺序排列。
  3. 裁剪与识别:按 recBatchSize 分批调用 QuadTextCrop.crop() 从原图裁出文本区域,送入 RecognitionEngine.recognize() 做 CTC 解码;每行结果按 recScoreThresh 过滤后汇入 allResults
  4. 计时汇总pipelineOverhead = 总耗时 - 检测耗时 - 识别耗时,即为排序、裁剪、对象构造等管线自身开销。

所有 Mat 均在 try/finallyrelease(),避免原生内存泄漏——这是移动端 OpenCV 编程的关键实践。

检测阶段:预处理与 DB 后处理

DetPreprocessor.kt 的执行步骤:

  1. detImgMode 选择保留 BGR 或转为 RGB;
  2. resizeToMultipleOf32limitSideLen / limitType / maxSideLimit 缩放,输出尺寸保证为 32 的倍数(与 DB 网络多次下采样对齐);
  3. 归一化:x * (1/255) 后减 mean (0.485, 0.456, 0.406)、除 std (0.229, 0.224, 0.225)(ImageNet 统计量);
  4. 转成 CHW 排布的 FloatArray,形状 (1, 3, H, W)

DBPostProcessor.kt 实现了 DB 算法的经典后处理链:

  1. 将概率图按 detThreshTHRESH_BINARY 二值化;
  2. detUseDilation = true 时用 2×2 核做膨胀(缓解字符断行);
  3. RETR_LIST + CHAIN_APPROX_SIMPLE 查找轮廓,最多取 detMaxCandidates 个候选;
  4. 每个候选取 minAreaRect,短边小于 3px 的丢弃(MIN_SIZE_BEFORE_UNCLIP);
  5. 打分:score_mode=fast 时按四个顶点取值,slow 时沿轮廓逐点取值后平均;分数低于 detBoxThresh 的丢弃;
  6. PolygonUnclip.unclip(points, unclipRatio) 按扩张比例放大多边形后重新取最小外接矩形,短边仍小于 5px 的丢弃;
  7. 坐标按 originalW/pWoriginalH/pH 缩放回原图尺寸,输出四边形框。

识别阶段:定高裁剪 + CTC 解码

RecPreprocessor.kt 对每批裁剪图:BGR→RGB → 等比缩放到固定高度 48(宽度上限 3200)→ 归一化 (x / 127.5) - 1 → 右侧 padding 到批内最大宽度 → 组成长度为 N 的张量 (N, 3, 48, maxW)

识别后处理是 CTCDecoder.decode(output, shape, characterList):字符表来自识别模型 inference.ymlPostProcess.character_dict 的解析结果(ModelConfig.kt 手写了一个轻量 YAML 列表解析器,只依赖 character_dict: 段,避免了引入完整 YAML 库)。这也解释了为什么模型文件必须同时包含 inference.yml

会话管理

ORTSessionManager.kt 统一管理 OrtEnvironment 与两个 OrtSession:模型加载失败时做资源回滚(识别会话创建失败会关闭已创建的检测会话);推理时动态读取 session 的第一个输入/输出名,不硬编码张量名;coldLoadTimeMsloadModels 全程计时,release() 依次关闭两个 session 并置空。

性能测试

项目内置了基于 Android Instrumentation 的自动化基准测试 OCRBenchmarkTest.kt,配套参考图片 ppocr-sdk/src/androidTest/res/raw/android_ocr_benchmark_reference.png,并通过 run_benchmark.sh 一键驱动:

# 运行基准测试(迭代次数 默认50,预热 默认30)
./run_benchmark.sh
./run_benchmark.sh 10 3        # 10 次正式测量 + 3 次预热

脚本内部执行 :ppocr-sdk:connectedAndroidTest,只跑 OCRBenchmarkTest#testLatencyBenchmark,参数经 -Pandroid.testInstrumentationRunnerArguments.warmup / .iterations 注入;结果从 adb logcat -s System.out 中按 OCRBenchmark 标签过滤输出。测试还支持 rec_batch_size 参数对比批识别收益。输出示例(表格中的数值来自文档示例,实际数值取决于你的设备):

╔═════════════════════════════════════════════════════════════════════════╗
║  PP-OCRv6 Speed Benchmark Results                                       ║
╠═════════════════════════════════════════════════════════════════════════╣
║  Device: GM1900  |  OS: Android 9  |  Lines: 5                          ║
║  Cold load: 158ms  |  Warmup: 3  |  Measured: 10                        ║
╠═════════════════════════════════════════════════════════════════════════╣
| Stage                       |  Mean ms |    Stdev |      P90 |    Min ms|
|-----------------------------+----------+----------+----------+----------|
| Total pipeline              |   420.40 |     6.37 |      427 |      413 |
|   Detection (total)         |   348.70 |     4.67 |      356 |      343 |
|     Preprocess              |    33.30 |     2.90 |       36 |       28 |
|     Inference               |   311.00 |     2.93 |      315 |      304 |
|     Postprocess             |     4.40 |     0.49 |        5 |        4 |
|   Recognition (total)       |    66.20 |     3.16 |       68 |       64 |
|     Preprocess              |     3.00 |     0.89 |        4 |        2 |
|     Inference               |    60.60 |     3.14 |       63 |       58 |
|     Postprocess             |     2.60 |     0.92 |        4 |        1 |
|   Pipeline overhead         |     5.50 |     0.50 |        6 |        5 |
╚═════════════════════════════════════════════════════════════════════════╝

这套统计指标(Mean / Stdev / P90 / Min)与 OCRRunResult 的分段计时字段一一对应:因为 SDK 本身就把预处理、推理、后处理、管线开销拆开了,基准测试只是对其做多次采样统计。

工程注意事项

  1. OpenCV 初始化顺序:必须在 PaddleOCR.create() 之前调用 OpenCVUtils.init(context),否则预处理/后处理依赖的 OpenCV 原生函数不可用(Demo 中见 OCRApplication.kt 的顺序)。
  2. 协程调用create()recognize()release() 均为 suspend 函数,内部已切到 Dispatchers.IO,调用方只需处于协程作用域内。
  3. 内存管理:应用退出或不再需要时调用 release() 关闭 ONNX 会话;Demo 在 Application.onTerminate() 中做了释放。
  4. ProGuard 规则proguard-rules.pro 中保留了 com.paddle.ocr.**ai.onnxruntime.** 两个包,AAR 方式集成时通过 consumer rules 自动生效;源码集成且宿主开启混淆时应确保同样的 keep 规则。
  5. 模型路径前提:默认模型路径 models/det/inference.onnxmodels/rec/inference.onnxmodels/rec/inference.yml 均相对于 assets 根目录;自定义路径必须仍位于 assets 内(SDK 通过 AssetManager 读取)。

小结

deploy/ppocr-android 展示了一个可投产的移动端 OCR 集成形态:SDK 侧用 ONNX Runtime + OpenCV 完整复刻了 PP-OCR 的检测(DB)与识别(CTC)链路,参数与 PaddleOCR Python 端配置一一对应;API 层通过协程 + sealed 错误模型保证线程安全与错误可辨识;性能侧则提供从分段计时到自动化基准测试的完整工具链。对于需要在 Android 端本地运行 PP-OCRv6 的场景,直接以 ppocr-sdk 为集成起点,再按需调整 PaddleOCRConfig 中的检测阈值与识别批大小即可。

主要参考文件:

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.16 K
2.78 K
kernelkernel
deepin linux kernel
C
34
18
docsdocs
暂无描述
Markdown
904
5.83 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
934
1.86 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
862
1.36 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.96 K
1.03 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.38 K
1.47 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
535
606
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
549
398
leetcodeleetcode
🔥LeetCode solutions in any programming language | 多种编程语言实现 LeetCode、《剑指 Offer(第 2 版)》、《程序员面试金典(第 6 版)》题解
Markdown
77
23