PaddleOCR Android 端到端部署实战:基于 ONNX Runtime 的 PP-OCRv6 SDK 集成指南
【免费下载链接】PaddleOCR飞桨多语言OCR工具包(实用超轻量OCR系统,支持80+种语言识别,提供数据标注与合成工具,支持服务器、移动端、嵌入式及IoT设备端的训练与部署) Awesome multilingual OCR toolkits based on PaddlePaddle (practical ultra lightweight OCR system, support 80+ languages recognition, provide data annotation and synthesis tools, support training and deployment among server, mobile, embedded and IoT devices)项目地址: https://gitcode.com/paddlepaddle/PaddleOCR
本文以 PaddleOCR 官方 Android 部署示例(deploy/ppocr-android)及其配套部署文档(android_deployment.md)为主线,系统讲解 PP-OCRv6 系列模型在 Android 平台上的完整落地流程:从 SDK/Demo 分离的工程架构、模型准备与 Gradle 构建,到公开 API 的调用方式、推理参数语义、端到端识别流水线的源码级实现,再到自动化性能基准测试。读完本文,你将能够把 PaddleOCR 文本检测 + 识别能力以 AAR 或源码依赖的形式集成进自己的 Android 应用,并精确掌控各阶段耗时。
项目定位:SDK 与 Demo 分离的移动端 OCR 方案
本项目是 PaddleOCR v6 在 Android 平台的部署示例,核心特点是不依赖 Paddle Lite,而是基于 ONNX Runtime 实现移动端 OCR 推理,并使用 OpenCV 完成图像预处理与后处理。整个工程采用SDK 与 Demo 分离架构:ppocr-sdk是一个可独立打包发布的 Android Library,第三方应用可以直接集成;app则是一个基于 MVVM + Jetpack Compose 的参考 Demo,演示 SDK 的标准用法。
从官方文档归纳的核心能力包括:
- 文本检测 + 文本识别端到端流程;
- 支持 PP-OCRv6 系列 ONNX 模型(同时兼容 PP-OCRv5_mobile);
- 详细的性能计时,检测/识别各阶段(预处理、推理、后处理)耗时独立统计;
- MVVM + Jetpack Compose Demo 应用;
- 支持 AAR 方式集成。
项目结构解析
官方文档给出的工程结构如下:
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] 推理参数配置 │ │ └── ... │ └── build.gradle.kts ├── app/ # Demo App │ ├── src/main/java/com/paddle/ocr/demo/ │ │ ├── OCRApplication.kt # 初始化 SDK │ │ └── ui/ # Compose UI │ └── build.gradle.kts ├── run_benchmark.sh # 性能测试脚本 └── README.md对照仓库实际源码,ppocr-sdk内部进一步细分为若干职责清晰的包(见 ppocr-sdk/src/main/java/com/paddle/ocr):
engine/:核心推理引擎层,包含OCREngine.kt(端到端编排)、DetectionEngine.kt(文本检测)、RecognitionEngine.kt(文本识别)、ORTSessionManager.kt(ONNX Runtime 会话与模型加载管理);preprocess/:预处理层,DetPreprocessor.kt负责检测输入缩放,RecPreprocessor.kt负责识别文本行裁剪批处理;postprocess/:后处理层,DBPostProcessor.kt实现 DB 检测结果的二值化与文本多边形还原,CTCDecoder.kt实现 CTC 解码,BoxSorter.kt按阅读顺序排序文本框,QuadTextCrop.kt负责按四边形裁剪文本行,另有PolygonUnclip.kt、QuadGeometry.kt等几何工具;model/:数据模型层,定义OCRResult、OCRRunResult、OCRBox、OCRError等;util/:工具层,包含BitmapUtils、ImageUtils、MathUtils、OpenCVUtils、YamlUtils(解析识别模型的inference.yml字符表)。
环境要求
官方文档明确的环境依赖如下:
| 依赖 | 版本 |
|---|---|
| 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 |
仓库中的版本目录文件 gradle/libs.versions.toml 给出了更完整的依赖清单,可作为核对基准:AGP 8.7.3、Kotlin 2.1.0、Compose BOM 2024.12.01、androidx.core-ktx 1.15.0、lifecycle-runtime 2.8.7、activity-compose 1.9.3、onnxruntime-android 1.21.1、opencv-android 4.5.3(com.quickbirdstudios)、kotlinx-coroutines-android 1.9.0、coil-compose 2.7.0,测试侧使用 androidx.test runner 1.2.0 与 JUnit 4.13.2。其中 OpenCV 采用com.quickbirdstudios:opencv封装,避免了繁琐的本地库初始化步骤,但注意在PaddleOCR.create()前仍须完成 OpenCV 初始化(见下文注意事项)。
快速开始
1. 克隆项目
git clone https://gitcode.com/paddlepaddle/PaddleOCR.git cd PaddleOCR/deploy/ppocr-android2. 准备模型
本项目支持以下 ONNX 模型(检测与识别模型均以.tar压缩包形式提供,官方同时发布在 HuggingFace 与 BOS 渠道,可按需选择下载):
| 模型 | 说明 |
|---|---|
| PP-OCRv6_small | 检测模型PP-OCRv6_small_det_onnx/ 识别模型PP-OCRv6_small_rec_onnx |
| PP-OCRv6_tiny | 检测模型PP-OCRv6_tiny_det_onnx/ 识别模型PP-OCRv6_tiny_rec_onnx |
| PP-OCRv5_mobile | 检测模型PP-OCRv5_mobile_det_onnx/ 识别模型PP-OCRv5_mobile_rec_onnx |
下载并解压后,将文件放入ppocr-sdk/src/main/assets/models/目录:
- 检测模型:将
inference.onnx放入models/det/ - 识别模型:将
inference.onnx和inference.yml放入models/rec/
其中识别模型额外要求inference.yml配置文件——从源码看,它在 SDK 初始化阶段被ModelConfig.parse(context, recConfigAsset)解析,用于提取字符表(characterList)供 CTC 解码使用(见 OCREngine.kt 与 RecognitionEngine.kt 的构造函数)。因此只放入 ONNX 权重文件、缺少inference.yml时识别阶段将无法正确解码。
3. 编译运行
# 编译 Debug APK ./gradlew :app:assembleDebug # 安装到设备 ./gradlew :app:installDebug或使用 Android Studio 直接打开deploy/ppocr-android目录运行。若本机 JDK 版本不匹配,可通过JAVA_HOME环境变量指定 JDK 17。
4. 体验 Demo
- 打开 "PP-OCRv6 Demo" 应用;
- 等待模型加载完成;
- 点击 "Select from Gallery" 选择图片;
- 查看识别结果和耗时统计。
Demo 端到端 UI 位于 app/src/main/java/com/paddle/ocr/demo,其中OCRApplication.kt负责应用启动时初始化 SDK,MainActivity.kt与ui/目录(HomeScreen.kt、ImagePicker.kt、ResultList.kt、LoadingOverlay.kt、TimingBar.kt、ErrorDialog.kt等)构成了基于 Compose 的完整交互界面,OCRViewModel.kt以 MVVM 方式封装识别状态。
SDK 集成:两种接入方式
方式一:源码依赖
将
ppocr-sdk/复制到项目根目录;在
settings.gradle.kts添加模块:include(":ppocr-sdk")在 App 模块
build.gradle.kts添加依赖:implementation(project(":ppocr-sdk"))
方式二:AAR 依赖
# 构建 AAR ./gradlew :ppocr-sdk:assembleReleaseAAR 输出:ppocr-sdk/build/outputs/aar/ppocr-sdk-release.aar
将 AAR 放入 App 模块的libs/目录,并在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") }注意 AAR 默认不传递传递依赖,因此 ONNX Runtime、OpenCV、协程这三个 SDK 的运行时依赖必须在宿主 App 中显式声明,版本与 gradle/libs.versions.toml 保持一致(onnxruntime 1.21.1、opencv 4.5.3、coroutines 1.9.0)。
API 参考:公开调用方式
创建实例
// 默认配置 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), detModelAssetPath = "models/det/inference.onnx", recModelAssetPath = "models/rec/inference.onnx", recConfigAssetPath = "models/rec/inference.yml", )从 PaddleOCR.kt 源码可以看到,create()是suspend函数,内部通过withContext(Dispatchers.IO)在 IO 线程完成OCREngine构建(含 ONNX 模型加载与 YAML 解析),因此必须在协程中调用,且模型加载耗时可通过ocr.coldLoadTimeMs属性获取。EngineConfig目前仅暴露numThreads一个字段,默认 4,对应 ONNX Runtime 的线程数配置(见 EngineConfig.kt)。
执行 OCR
// 传入 Bitmap val result = ocr.recognize(bitmap) // 传入图片字节数据(推荐,与 Python 流程一致) val result = ocr.recognize(imageBytes) // 读取结果 result.results.forEach { item -> println("文本: ${item.text}, 置信度: ${item.confidence}") println("坐标: ${item.box.points}") } println("检测: ${result.detectionTimeMs}ms, 识别: ${result.recognitionTimeMs}ms")recognize()同样为suspend函数。两种重载的差异在 OCREngine.kt 中体现得很清楚:Bitmap重载通过BitmapUtils.bitmapToBGRMat转成 BGR 的 OpenCVMat;字节数组重载则调用BitmapUtils.imdecodeBGR解码,并在解码结果为空(Mat.empty())时抛出OCRError.InvalidImage。字节数组方式与 Python 端cv2.imread的流程一致,官方推荐在流式或文件读取场景下使用。
释放资源
ocr.release()release()内部调用engine.release(),最终释放 ORT 会话等底层资源(见 OCREngine.kt),在页面销毁等场景务必调用,避免内存泄漏。
配置参数
PaddleOCRConfig全部字段及其默认值如下(与 PaddleOCRConfig.kt 完全一致):
data class PaddleOCRConfig( val detImgMode: String = "BGR", // 输入色彩模式 val detLimitSideLen: Int = 64, // 检测侧边长限制 val detLimitType: String = "min", // 限制策略 val detMaxSideLimit: Int = 4000, // 最长边上限 val detThresh: Float = 0.3f, // 二值化阈值 val detBoxThresh: Float = 0.6f, // 检测框置信度阈值 val detUnclipRatio: Float = 1.5f, // 检测框扩展比例 val detMaxCandidates: Int = 3000, // 最大候选框数 val detUseDilation: Boolean = false, // 是否膨胀 val detScoreMode: String = "fast", // 打分模式 val detBoxType: String = "quad", // 检测框类型 val recScoreThresh: Float = 0.0f, // 识别置信度阈值 val recBatchSize: Int = 1, // 识别批大小 )这些参数在源码中的实际消费路径如下:
- 检测相关参数在 DetectionEngine.kt 中被完整使用:
detLimitSideLen、detLimitType、detMaxSideLimit、detImgMode传入DetPreprocessor.preprocess控制输入缩放策略(限制最长边、保持比例);detThresh、detBoxThresh、detUnclipRatio、detMaxCandidates、detUseDilation、detScoreMode、detBoxType则全部透传给DBPostProcessor.process,与 PP-OCR Python 版 DB 后处理的语义一一对应; - 识别相关参数在 OCREngine.kt 中生效:
recBatchSize(coerceAtLeast(1)保证最小为 1)控制文本行裁剪的批大小,recScoreThresh用于过滤低置信度结果(confidence >= recScoreThresh才进入输出列表)。
结果模型
data class OCRRunResult( val results: List<OCRResult>, // 识别结果列表 val detectionTimeMs: Long, // 检测耗时 val recognitionTimeMs: Long, // 识别耗时 val totalTimeMs: Long, // 总耗时 val lineCount: Int, // 识别行数 // 详细计时... ) data class OCRResult( val box: OCRBox, // 检测框坐标 val text: String, // 识别文本 val confidence: Float, // 置信度 )除文档列出的字段外,对照 PaddleOCR.kt 的映射代码可知,OCRRunResult还携带了更细粒度的性能数据:detPreprocessMs、detInferenceMs、detPostprocessMs、recPreprocessMs、recInferenceMs、recPostprocessMs、pipelineOverheadMs、coldLoadTimeMs,以及detInputShape、recInputShapes(实际推理输入张量形状)和perLineRecMs(单行识别耗时,仅在recBatchSize == 1时填充)。这些字段正是性能测试与耗时分析的数据来源。
源码级原理:端到端识别流水线
从 OCREngine.kt 可以完整还原一次recognize()的内部流程:
- 文本检测:
detectionEngine.detect(srcMat)依次执行——DetPreprocessor.preprocess(限长缩放 + BGR 归一化)→ortManager.runDetection(ONNX Runtime 推理)→DBPostProcessor.process(二值化、外接多边形还原、按detBoxType输出四边形框)。若检测框为空,直接返回空结果(识别耗时为 0,检测各阶段耗时照常上报); - 阅读顺序排序:
BoxSorter.sortInReadingOrder(boxes)按从上到下、从左到右的阅读顺序对检测框排序,保证输出文本行顺序与人类阅读习惯一致; - 批量裁剪识别:按
recBatchSize分批,对每个检测框调用QuadTextCrop.crop做透视裁剪,裁剪失败的框(宽或高为 0)会被跳过;随后recognitionEngine.recognize(batchCrops)走RecPreprocessor.preprocessBatch→ortManager.runRecognition→CTCDecoder.decode(基于 YAML 解析出的字符表做 CTC 解码),得到(text, confidence)对,经recScoreThresh过滤后写入结果列表; - 计时汇总:
totalElapsed - detResult.timeMs - totalRecMs计算流水线开销(含排序与裁剪时间),与各阶段耗时一起封装进OCRRunResult。
识别引擎的批处理能力(RecognitionEngine.kt)使得多行文本场景可以单次 ORT 调用完成整批识别,显著降低推理开销;测试用例可参考 OCRBenchmarkTest.kt(testLatencyBenchmark),其使用 android_ocr_benchmark_reference.png 作为基准输入图,通过 instrumentation 参数注入预热次数与迭代次数。
性能测试:自动化基准脚本
项目提供一键式性能测试脚本 run_benchmark.sh:
# 运行 benchmark(10次测试,3次预热) ./run_benchmark.sh 10 3脚本行为拆解(与文档说明一致,并对照源码脚本确认):默认参数为 50 次迭代、30 次预热;脚本先adb logcat -c清空日志,再通过 Gradle 的connectedAndroidTest以 instrumentation 参数class=com.paddle.ocr.benchmark.OCRBenchmarkTest#testLatencyBenchmark、warmup、iterations驱动真机/模拟器上的基准测试,最后用adb logcat -d -s System.out:I | grep "OCRBenchmark"过滤出测试输出。
文档给出的典型输出示例:
╔═════════════════════════════════════════════════════════════════════════╗ ║ 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 | +-----------------------------+----------+----------+----------+----------+ ╚═════════════════════════════════════════════════════════════════════════╝从输出可以直观看到:单帧端到端约 420ms,其中检测推理(约 311ms)是主要瓶颈,识别部分约 66ms,流水线开销约 5.5ms。这一细粒度计时的实现依据即上一节所述的OCRRunResult分段耗时字段(detInferenceMs、recInferenceMs等),开发者可以据此定位优化方向——例如调整EngineConfig(numThreads)或更换 PP-OCRv6_tiny 模型以压缩检测推理时间。需要注意的是,示例数据来自特定测试设备(GM1900 / Android 9),实际耗时随设备算力、模型规格与图片内容(文本行数)不同而变化,应以本机实测为准。
注意事项与最佳实践
官方文档明确强调以下四点,结合源码可以进一步给出实操建议:
- OpenCV 初始化:调用
PaddleOCR.create()前需先调用OpenCVUtils.init(context)(位于 OpenCVUtils.kt),否则后续图像转Mat、透视裁剪等操作会失败; - 协程调用:
create()和recognize()都是suspend函数,需在协程中调用;SDK 内部已将重活调度到Dispatchers.IO,因此 UI 线程只需发起协程即可,无需额外切线程; - 内存管理:不再使用时调用
release()释放资源,同时 SDK 内部对临时Mat采用try/finally确保释放(如 OCREngine.kt 的runWithOwnedMat),第三方集成时勿重复持有 Bitmap 引用; - 混淆规则:参考 ppocr-sdk/proguard-rules.pro,在开启混淆的 Release 构建中需保留 ONNX Runtime 与 OpenCV 相关的类与 JNI 方法,否则运行时可能抛出
UnsatisfiedLinkError或类缺失异常。
另外,若需在 Release 包中内置模型,应将模型文件放置于ppocr-sdk/src/main/assets/models/并确认打包进 AAR;若模型体积过大,也可考虑运行时从外部存储或网络加载,此时需自行实现 assets 路径以外的模型读取逻辑(当前 SDK 公开 API 默认从 assets 读取)。
总结
PaddleOCR 的 Android 部署方案通过 ONNX Runtime 将 PP-OCRv6 的检测与识别能力完整迁移到移动端,以 SDK/Demo 分离的架构兼顾了开箱即用与二次集成。本文从工程结构、环境配置、模型准备、构建运行、SDK 集成、公开 API、源码级流水线原理到性能基准测试,完整覆盖了官方部署文档的全部要点,并逐一对照 ppocr-sdk 源码验证了配置参数的消费路径与端到端执行流程。开发者可以直接基于 deploy/ppocr-android 目录开始实践,更多跨平台部署方案(iOS、Web 等)可参考 cross_platform 部署文档目录。
【免费下载链接】PaddleOCR飞桨多语言OCR工具包(实用超轻量OCR系统,支持80+种语言识别,提供数据标注与合成工具,支持服务器、移动端、嵌入式及IoT设备端的训练与部署) Awesome multilingual OCR toolkits based on PaddlePaddle (practical ultra lightweight OCR system, support 80+ languages recognition, provide data annotation and synthesis tools, support training and deployment among server, mobile, embedded and IoT devices)项目地址: https://gitcode.com/paddlepaddle/PaddleOCR
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考