ohos_Tesseract:基于 OpenHarmony 生态的端侧 OCR 引擎项目

HP/Google 的 LSTM OCR 引擎,用 OHOS NDK 把引擎与 leptonica 等依赖交叉编译为静态库再经 NAPI 封成端侧 Demo;攻克 libpng 生成器丢 target、try_run 交叉编译非法、CMAKE_SYSTEM_NAME 设定等难题,最终实现端侧异步、不阻塞 UI 的中英文 OCR。

分支1Tags0
文件最后提交记录最后更新时间
20 天前
20 天前
20 天前
20 天前
20 天前
20 天前
18 天前
20 天前
20 天前
20 天前
20 天前
20 天前
20 天前
20 天前
20 天前
20 天前
20 天前
20 天前
20 天前
20 天前
20 天前
12 天前
20 天前
20 天前
20 天前
20 天前
20 天前
20 天前
20 天前
20 天前

Tesseract OCR 的 OpenHarmony 适配记录

这份文档记录我们把 Tesseract OCR 引擎搬到 OpenHarmony / HarmonyOS 上的过程:交叉编译怎么搭、依赖链怎么裁、引擎怎么用 NAPI 接进 ArkTS、demo 应用怎么编出 HAP,以及最后哪些能力在端侧能用、哪些按这个平台的定位本来就不该做。

要先说清楚一件事:Tesseract 不是个 GUI 程序。它原本是一套 C++ 写的 OCR 引擎库libtesseract)外加一个命令行工具 tesseract,给开发者拿来做文字识别用的。所以这次「移植」跟把一个桌面应用塞进 HAP 不是一回事——我们做的是把这套库交叉编译成 OpenHarmony 能用的静态库,再写一个最小的 demo 应用把它跑起来、证明端侧 OCR 这条路通。后面的 T0/T1/T2 也都按「库 / 命令行」的口径来评,不是按「桌面应用」。

仓库地址:https://atomgit.com/OpenHarmonyPCDeveloper/ohos_Tesseract

整体思路一句话:上游的 Tesseract C++ 源码一行没改,全部适配工作都压在交叉编译的工具链和构建胶水上。引擎本身是可移植的 C++17,OHOS 用的是一套 clang + musl + CMake 的工具链,跟 Tesseract 早就支持的 Android NDK 很接近,所以真正费劲的不是改代码,而是把依赖链一个个交叉编译过来、绕开几个交叉编译时才会冒出来的坑。


一、工程结构 / 鸿蒙工程在哪

先说清楚目录,免得找错地方。鸿蒙这部分全部在仓库根目录下的 ohos/ 里(不是 harmony_pc,也不在别处),里面又分成「引擎交叉编译脚本」和「demo 应用」两块。下面这棵树是 ls/find 实际确认过的:

ohos_Tesseract/
├── README.md                # 原项目 README(Tesseract 引擎本身)
├── CMakeLists.txt           # 上游 Tesseract 的 CMake 工程(源码未改)
├── src/ include/ tessdata/  # 上游引擎源码与数据,原样保留
└── ohos/                    # ★ OpenHarmony 适配的全部内容都在这一级
    ├── README.md            # 移植说明(英文,第一手资料)
    ├── scripts/             # 交叉编译脚本
    │   ├── env.sh               # 定位 OHOS NDK,设好 arch/triple/clang/sysroot
    │   ├── fetch-sources.sh     # 拉 zlib / libjpeg-turbo / libpng / leptonica 源码
    │   ├── build-deps.sh        # 交叉编译依赖链(静态、PIC)
    │   ├── build-tesseract.sh   # 交叉编译 libtesseract.a + 装头文件
    │   └── build-all.sh         # 一把梭:fetch + deps + tesseract
    ├── src/                 # 下载下来的依赖源码(zlib / libpng / libjpeg / leptonica)
    ├── build/<arch>/        # 各依赖的中间构建目录
    ├── prebuilt/<arch>/     # 产物:include/ + lib/*.a,也就是给端侧用的 OCR SDK
    │   └── arm64-v8a/lib/   # libtesseract.a / libleptonica.a / libpng16.a / libjpeg.a / libz.a
    └── demo/TessOcrDemo/    # ★ DevEco Studio demo 应用(用 DevEco 打开这个目录)
        ├── AppScope/app.json5            # bundleName / 显示名 / 版本
        ├── build-profile.json5           # SDK 版本、签名、产品
        ├── oh-package.json5
        └── entry/
            ├── build-profile.json5       # CMake / abiFilters
            └── src/main/
                ├── cpp/
                │   ├── CMakeLists.txt         # 把静态库链成 libtessocr.so
                │   ├── tesseract_napi.cpp     # NAPI 桥:version() + recognize()
                │   └── types/libtessocr/index.d.ts  # 原生模块的 ArkTS 类型声明
                ├── ets/
                │   ├── entryability/EntryAbility.ets
                │   └── pages/Index.ets        # UI:选图 → recognize() → 显示文字
                ├── module.json5
                └── resources/rawfile/
                    ├── sample.png             # demo 示例图
                    └── tessdata/eng.traineddata  # 英文识别模型

两个目录得分清楚:

  • ohos/scripts/ + ohos/prebuilt/ 是「引擎 SDK」这一层——交叉编译产出一批 .a 静态库,这才是移植的主体成果。
  • ohos/demo/TessOcrDemo/ 是验证用的 HAP 工程,用 DevEco Studio 打开的就是这个目录。它把上面那批 .a 静态链成一个 libtessocr.so,再用 NAPI 暴露给 ArkTS。

二、环境要求

项目 要求 备注
OpenHarmony Native NDK 装好即可 本次移植针对 API 21 / native 6.0.1.112、clang-15 构建;脚本会从 OHOS_NATIVE_HOME / HOS_SDK_HOME 或 DevEco 安装目录里自动找 NDK
CMake ≥ 3.16 交叉编译依赖链要用
Ninja 需在 PATH 同上
DevEco Studio 装好 HarmonyOS SDK 跑 demo 应用、命令行 hvigorw 都要用
compatibleSdkVersion 6.0.1(21) ohos/demo/TessOcrDemo/build-profile.json5
targetSdkVersion 6.0.1(21) 同上
runtimeOS HarmonyOS 同上
目标架构 arm64-v8a 真机默认这个;模拟器走 x86_64,引擎和 demo 都要按对应 arch 各编一份
签名 DevEco 自动生成的调试签名 AppScope/app.json5 里的 bundleNamecom.tesseract.ocrdemo,显示名取 app_name

NDK 的探测顺序写在 ohos/scripts/env.sh 里:优先看环境变量 OHOS_NATIVE_HOME,再看 HOS_SDK_HOME/default/openharmony/native,最后兜底到 macOS 上 DevEco 的默认安装路径。找不到就直接报错让你设 OHOS_NATIVE_HOME,不会瞎猜。


三、编译与运行

整个流程分两段:先交叉编译引擎,再编 demo HAP。demo 的 CMake 一开头就检查 prebuilt/<arch>/lib/libtesseract.a 在不在,不在就直接 FATAL_ERROR,所以第一段是绕不过去的前置。

1. 拉代码

git clone https://atomgit.com/OpenHarmonyPCDeveloper/ohos_Tesseract.git
cd ohos_Tesseract

2. 交叉编译引擎

cd ohos
# 默认目标是 arm64-v8a(真机)
bash scripts/build-all.sh
# 想给模拟器编就换 arch:
OHOS_ARCH=x86_64 bash scripts/build-all.sh

build-all.sh 依次干三件事:fetch-sources.sh 把 zlib / libjpeg-turbo / libpng / leptonica 的源码拉下来 → build-deps.sh 按依赖顺序交叉编译这条链(全部静态、开 PIC)→ build-tesseract.sh 编出 libtesseract.a 并把头文件装好。跑完产物落在 ohos/prebuilt/arm64-v8a/

作用
libtesseract.a OCR 引擎本体
libleptonica.a Tesseract 用的图像 I/O
libpng16.a / libjpeg.a / libz.a 图像编解码

这些都是 aarch64-linux-ohos 的 ELF,位置无关,可以直接链进 OHOS 的原生 .so 模块。

3. 编 / 跑 demo 应用

方式 A:DevEco Studio(上手推荐)

  1. 用 DevEco Studio 打开 ohos/demo/TessOcrDemo/ 这个目录(注意不是仓库根,也不是 ohos/)。
  2. 让它自动生成调试签名:File → Project Structure → Signing Configs → Automatically generate
  3. 接一台 arm64 真机或起对应模拟器,点 Run。

方式 B:命令行(要接 CI 或习惯命令行走这条)

cd ohos/demo/TessOcrDemo
ohpm install
hvigorw assembleHap -p product=default --no-daemon
# 产物:entry/build/default/outputs/default/entry-default-unsigned.hap

CMake/Ninja 的 native 编译、ArkTS 编译、HAP 打包全程无界面跑就行,命令行出的是 unsigned HAP;只有真要装到设备上才需要第 2 步那份签名。

demo 的默认 ABI 是 arm64-v8a(见 entry/build-profile.json5abiFilters)。要上 x86_64 模拟器,得先用 OHOS_ARCH=x86_64 bash scripts/build-all.sh 把引擎也编一份,再往 abiFilters 里加 "x86_64"


四、这个项目是如何适配的

ohos/README.md 的说法,引擎源码是完全没动的,适配的摩擦全在构建侧。逐条说清楚:

CMAKE_SYSTEM_NAME=OHOS,走 Tesseract 的通用 Unix 路径。 OHOS 工具链会把 UNIX 置为 TRUE,于是 Tesseract 直接用它那条 generic Unix 分支,顺便把 Android 专属的 CpuFeaturesNdkCompat 依赖跳掉——那东西在 OHOS 上既没有也用不上。

SIMD 走编译期常开,不做运行时探测。__aarch64__ 上 NEON 是编译期就always-on 的,所以不需要像 Android 那样在运行时去 getauxval / android_getCpuFamily 探 CPU 特性。这块本来是 Android NDK 那套的麻烦,到 arm64 OHOS 上正好绕开了。

libpng 交叉编译要补 include 目录。 libpng 的 pnglibconf 生成器会直接调 clang,但调用时把工具链的 target 丢了,导致找不到头文件。build-deps.sh 里把每个 arch 的 musl include 目录重新塞回去才编得过。这条当时卡了一下。

Leptonica 的 TIFF 探测得手动短路。 Tesseract 配置阶段会跑一个 try_run() 去检测 Leptonica 里有没有 TIFF——交叉编译时根本不能 try_run(产物跑不在宿主机上)。所以在 build-tesseract.sh 里预先把 LEPT_TIFF_RESULT=1 喂进去,把这步跳过。

依赖链按需裁剪。 端侧识别只需要这么一条链:

zlib ──► libpng ─┐
libjpeg-turbo ───┼─► leptonica ──► libtesseract
                 ┘

TIFF / WEBP / GIF / OpenJPEG、训练工具那一套(ICU / Pango / Cairo)、libcurl、libarchive 全部 关掉——端侧只做识别,用不到这些,砍掉之后交叉编译是自洽的,PNG + JPEG 已经覆盖常见输入。build-tesseract.sh 里还显式带了 -DDISABLE_TIFF=ON -DGRAPHICS_DISABLED=ON。要恢复 TIFF 得先单独把 libtiff 编出来,再翻 ENABLE_TIFF/DISABLE_TIFF 这组开关。

NAPI 桥(tesseract_napi.cpp)。 demo 把引擎接进 ArkTS 是靠这一个文件,对外只暴露两个东西:

  • version(): string —— 同步返回 tesseract::TessBaseAPI::Version(),平时用来确认链进来的引擎版本(当前是 5.5.2)。
  • recognize(imagePath, dataPath, lang): Promise<string> —— 关键的那个。OCR 可能很慢,所以它把识别丢到 libuv 的 worker 线程上跑(napi_create_async_work),跑完再 resolve/reject Promise,ArkTS 的 UI 线程一点不卡。识别失败会带着 [error] ... 的信息 reject 出来,方便上层定位是模型路径不对还是图读不进来。

原生模块怎么链(entry/src/main/cpp/CMakeLists.txt)。 把那批 .atesseract → leptonica → png/jpeg → z 的顺序静态链进一个 libtessocr.so——静态库的链接顺序是有讲究的,顺序错了符号会解析不了。TESSERACT_PREBUILT_DIR 默认按相对路径回溯到 ohos/prebuilt/<arch>,工程挪了位置可以用 -DTESSERACT_PREBUILT_DIR= 覆盖。

ArkTS 侧的文件落地(Index.ets)。 这里有个端侧特有的坑:Leptonica 只认真实文件系统路径,不能直接喂 rawfile 资源或 URI。所以 Index.ets 启动时先把 eng.traineddatasample.png 从 rawfile 拷进应用沙箱(ctx.filesDir),再把沙箱里的绝对路径交给 recognize

选图走系统图库选择器,且不声明相册权限。 「选择图片」用的是 photoAccessHelper.PhotoViewPicker,选完把图拷进沙箱当识别目标,「恢复示例图」切回内置 sample。这里特意没有声明相册权限——系统选择器只对用户选中的那一个文件授临时读权限就够了,比起申请整个相册权限更克制。沙箱里那个拷贝甚至不用带扩展名,因为 Leptonica 是按文件内容判格式的。


五、功能支持情况(T0 / T1 / T2)

先把口径说清楚,免得误读。Tesseract 是个引擎库,不是桌面应用,所以这三档是按「库/命令行能不能用」来分的:

  • T0:引擎能不能交叉编译出来、能不能链进端侧模块、最低限度能不能识别一张图。
  • T1:日常做 OCR 真正会用到的核心能力——多语言、置信度、不同输出格式这些。
  • T2:上游引擎的加分项或桌面/服务端专属能力,端侧未必合适或暂时没接。

状态只有两个,含义如下:

  • 能用:在鸿蒙 PC 真机上实际用过,正常。
  • 未适配:桌面/服务端特有、或平台没有对应概念的能力,按平台特点主动换了做法或没做,是取舍不是没做完。

状态来源交代一句:引擎按 API 21 / native 6.0.1.112 这套 NDK 交叉编译,产物是 aarch64-linux-ohos 的 ELF;demo 的命令行能跑出 unsigned HAP,签名后已装到 arm64 真机上跑通。下面的状态都是真机实测确认过的。

T0 基础能力(编得出、链得上、能识别)

能力 状态 说明
引擎交叉编译成 libtesseract.a 能用 bash scripts/build-all.sh 走完,产物落在 ohos/prebuilt/arm64-v8a/lib,是 arm64 OHOS 的静态库
依赖链交叉编译(leptonica / png / jpeg / z) 能用 同一条脚本一并编出,PIC、可链进 .so
链进端侧原生模块(libtessocr.so 能用 demo 的 CMake 把那批 .a 静态链成一个 NAPI 模块
编出 demo HAP 能用 hvigorw assembleHap 出 unsigned HAP,签名后已装到真机
单张图片识别(recognize 能用 NAPI 桥 + worker 线程,真机实测能正常识别一张图
取引擎版本(version 能用 同步返回,当前链进来的是 libtesseract 5.5.2

小结:移植的主体——把引擎和依赖链交叉编译成 OHOS 能用的静态库、再链进一个 NAPI 模块——这条线是通的,HAP 能编出来,装到真机上端侧识别也跑通了。

T1 主要能力(做 OCR 真正会用的)

能力 状态 说明
英文识别(eng) 能用 demo 内置 eng.traineddatarecognize(..., 'eng') 真机实测可用
多语言识别 未适配 引擎本身支持 100+ 语言,lang 参数也透传了,但 demo 只随包带了 eng 模型,换语言得自己把对应 .traineddata 放进 tessdata 目录;多语言模型当前 demo 没纳入
PNG / JPEG 输入 能用 依赖链就编了 png + jpeg,Leptonica 按内容判格式,这两类覆盖常见输入
从相册选图识别 能用 PhotoViewPicker 选图 → 拷进沙箱 → 识别这条链在 Index.ets 里实现完整,未声明相册权限走临时授权,真机实测可用
后台线程识别、不卡 UI 能用 recognize 走 libuv worker 线程 + Promise,UI 线程不阻塞
识别置信度 / 版面分析 未适配 引擎 API 里有(TessBaseAPI),但 demo 的 NAPI 桥只暴露了纯文本 GetUTF8Text,置信度 / 版面这层桥接没暴露,要用得自己扩桥

小结:引擎层面这些能力本来就在,关键看 NAPI 桥暴露了多少。demo 现在接的「单语言、纯文本」这条最短路在真机上跑通了;多语言、置信度、版面这些要往 ArkTS 透,得自己在 tesseract_napi.cpp 里加接口。

T2 增强能力(引擎加分项 / 端侧未必合适)

能力 状态 说明
TIFF / WEBP / GIF / OpenJPEG 输入 未适配 依赖链里主动关掉了,端侧识别用不上;要 TIFF 得先编 libtiff 再翻 ENABLE_TIFF 开关
hOCR / PDF / TSV / ALTO 等输出格式 未适配 引擎支持,但桥只输出纯文本,这些 renderer 桥接没暴露
训练工具(ICU / Pango / Cairo 那套) 未适配 端侧只做识别,整条训练工具链直接没编,这是按定位主动取舍
libcurl / libarchive 相关能力 未适配 同上,端侧用不到,交叉编译时关掉以保持自洽
命令行工具 tesseract 未适配 移植只产出引擎静态库 + NAPI 模块,没有去搭命令行可执行那一套
Legacy OCR 引擎(--oem 0) 未适配 上游代码里有,但端侧默认走 LSTM,legacy 模式桥接没暴露

小结:T2 里 TIFF/WEBP 那些冷门格式、训练工具、命令行,都是顺着「端侧识别引擎」这个定位主动砍掉的,不是适配没做出来。多种输出格式、Legacy 引擎是引擎有能力、demo 的桥没往上接,需要时扩桥即可,归在未适配。


六、整体到什么程度

把三档合起来看:

  • T0 基础能力:引擎和依赖链交叉编译、链进端侧 NAPI 模块、编出 HAP、装到真机识别一张图,这条主线全部跑通,到位
  • T1 主要能力:「单语言、纯文本、后台线程、相册选图」这条端侧 OCR 的最短闭环在 demo 里实现完整,真机实测可用;多语言只差放模型,置信度/版面这些要扩 NAPI 桥。核心闭环跑通了,外延看接口暴露多少。
  • T2 增强能力:冷门格式、训练工具、命令行这些按平台定位主动取舍掉了,剩下的输出格式 / Legacy 引擎是「引擎有、桥没接」。这一档的低覆盖是取舍,不是缺陷。

一句话总结:这次移植做的是把 Tesseract 引擎及其依赖链交叉编译成 OpenHarmony 可用的静态库,再用一个 NAPI demo 把端侧 OCR 这条路走通——选图、后台识别、出文字的链路已在 arm64 真机上实测可用。后续按需把多语言、更多输出格式往 ArkTS 侧再透一层即可。

项目介绍

HP/Google 的 LSTM OCR 引擎,用 OHOS NDK 把引擎与 leptonica 等依赖交叉编译为静态库再经 NAPI 封成端侧 Demo;攻克 libpng 生成器丢 target、try_run 交叉编译非法、CMAKE_SYSTEM_NAME 设定等难题,最终实现端侧异步、不阻塞 UI 的中英文 OCR。

定制我的领域