安卓OCR自动化原理

安卓自动化脚本 OCR 识别教程:PPOCR 本地离线文字识别原理与实战

安卓自动化脚本 OCR 识别完整教程:为什么脚本要"读"屏幕文字(游戏弹窗、自绘文本、图片文案)、云端 OCR 与端侧 OCR 的优劣对比、PPOCR-V4/V5/V6 全系免费本地离线模型怎么选、在脚本里怎么调用(初始化、识别、释放)、参数调优(padding/maxSideLen/numThread)。手机本地识别不依赖网络,隐私与速度兼得,附实战代码示例。

约 18 分钟

一、为什么脚本要“读文字”

自动化脚本的本质,是“替人看屏幕、做判断、点按钮”。大多数时候,脚本靠控件树就能“看懂”界面:系统无障碍服务会把当前界面解析成一棵节点树,每个按钮、输入框、文本都有 id、文本、描述和位置,按文本找控件、再点控件,是最稳、最不容易受分辨率影响的写法。

但控件树不是万能的。有一类文字,控件树根本读不到

场景 为什么读不到 OCR 的应对
游戏弹窗 / 公告 游戏引擎把画面“画”出来,没有标准控件节点 直接识别弹窗图片里的文字
自绘文本(Canvas) 文字是绘制出来的,不在控件树中 识别画布上的文字
图片文案 / 宣传图 文字是图片的一部分 对图片区域做 OCR
验证码类内容 为防机器读取而设计,通常不提供文本接口 OCR 读取(能否使用取决于场景合规性)

遇到这些内容,唯一的读取方式就是把屏幕当图片看——这正是 OCR(Optical Character Recognition,光学字符识别)干的事:输入一张图,输出图里的文字和位置。

还有一个容易被忽略的关联场景:在蓝牙 HID / OTG HID 运行模式下,脚本不依赖无障碍服务,控件树这条路本身就走不通,OCR 几乎是唯一能“读文字”的手段(该模式仅支持图色、OCR 与点击滑动等基础操作)。所以“会读文字”不是锦上添花,而是自动化能力的第二根支柱。

以 EasyClick 为例,OCR 是内置能力:免 root,支持安卓 5.0 ~ 最新系统,PPOCR-V4/V5/V6 全系免费、本地离线识别OCR 文档)。

二、云端 OCR vs 端侧 OCR:为什么本地识别是更优解

提到 OCR,很多人第一反应是“调用某个云平台的 OCR 接口”:把图片传上去,等服务器识别完再把结果传回来。这当然能用,但对自动化脚本来说,几乎每个维度都吃亏:

维度 云端 OCR 端侧 OCR(手机本地)
延迟 截图 → 上传 → 云端排队识别 → 回传,受网络往返影响 本地直接算,无网络环节
隐私 屏幕内容上传到第三方服务器 数据不出设备
成本 按次 / 按量计费 全系免费
断网可用性 断网即不可用 断网照常识别
稳定性 依赖服务器与网络质量 不依赖任何外部服务

自动化脚本是高频、实时、长跑型的任务——截一次屏识别一次,一天几千上万次。云端方案既慢又贵,还随时可能因为网络抖动或服务波动掉链子;端侧 OCR 把识别引擎和模型直接装进手机,本地完成,天然契合。

需要说明的是,文档里也提供了在线识别类型(如百度在线识别 baiduOnline、EC 自带的 PC 端 paddleOcrOnline 服务),以及免费的 PC 端 PPOCR-ONNX 程序(在 Windows 上运行,通过 HTTP 接口给安卓 / iOS / 鸿蒙 Next 调用,默认端口 9022,PPOCR 说明)。这类方案适合“电脑就在旁边、需要更大吞吐”的部署形态;本文主线是手机本地离线识别,这也是 PPOCR 系列在 EasyClick 中的默认用法。

三、OCR 引擎原理:检测 + 识别两阶段

OCR 不是“一张图哗啦一下变成文字”,而是一条两阶段流水线

  1. 检测(Detection):先在图片里找出“哪些地方有文字”,把每一行或每一块文字用方框框出来。可以理解成“在房间里找哪些地方贴着纸条”。
  2. 识别(Recognition):再把每个方框里的文字图像“翻译”成字符序列——“把每张纸条上的字念出来”。

两个阶段各自是一个(或一组)神经网络模型。这些模型文件直接随应用内置(模型路径可配置,不配置就用自带的),所以识别全程在手机本地完成,不联网、不传图、不按次计费

PaddleOCR 是百度飞桨(PaddlePaddle)生态的开源 OCR 方案,PPOCR-V4 / V5 / V6 是它的历代模型版本。EasyClick 内置的正是 PaddleOCR 家族:文档确认支持 PPOCR-V4、PPOCR-V5 和 PPOCR-V6 模型,其中 V6 需要安卓 8.0 / API 26 及以上(OCR 文档)。

理解“两阶段”还有一个实际用处:调参时你会反复看到“检测框”“文字框没框住文字”这类说法——它们说的都是第一阶段的检测结果,很多参数(比如后面的 padding)就是专门用来修这个环节的。

四、PPOCR-V4 / V5 / V6 怎么选

先给结论:系统版本是第一约束,其余按实际效果试

initOcr 的 type 对应模型 说明 文档示例适配版本
paddleOcrOnnxV4 PPOCR-V4 ONNX 实现,参数完整(padding、maxSideLen、文字方向检测等) 11.26+
paddleOcrOnnxV5 PPOCR-V5 ONNX 实现,参数与 V4 同族 11.26+
paddleOcrNcnnV5 PPOCR-V5 ncnn 实现——ncnn 是为手机端优化的神经网络推理框架 11.28+
paddleOcrOnnxV6 PPOCR-V6 官方 ppocr-sdk + ORT 实现,默认 modelTier=small 12.4.0+,需安卓 8.0+

选型建议:

  • 安卓 8.0 及以上:优先用 paddleOcrOnnxV6。V6 是最新模型(官方 ppocr-sdk + ORT 实现),默认档位 modelTier=small,EC 当前最新版本 12.4 已支持。
  • 安卓 8.0 以下:用 paddleOcrNcnnV5 或 paddleOcrOnnxV5。这是文档明确给出的建议——低版本系统跑不了 V6,就用 V5 系列顶上。
  • 文档没有给出 V4/V5/V6 之间的直接精度/速度对比数据,所以不用纠结“哪个一定更快”:先按默认参数跑通,再根据实际识别效果(速度、漏字、误识别)微调。

另外,EC 9.17+ 的 OCR 从单例改成了多实例模式,一个脚本里可以同时初始化多个 OCR 实例、按需切换(比如一个 V5、一个 V6 各管一类场景),互不干扰。

五、在脚本里怎么用:四步走

用法很固定,就四个步骤:创建实例 → 初始化 → 识别 → 释放资源

步骤 函数 作用
创建实例 ocr.newOcr() 创建一个 OCR 实例(9.17+ 多实例模式)
初始化 initOcr({type: ...}) 指定识别引擎与参数,返回是否成功
识别 ocrImage(img, timeout, extra) 对截图或图片识别,返回结果数组
释放 releaseAll() 释放 OCR 占用的资源

识别之前需要先 startEnv() 启动自动化环境、image.requestScreenCapture() 申请截图权限(图色文档),下面的示例省略了这部分,重点看 OCR 本身。以 PPOCR-V6 为例(需安卓 8.0+):

// ① 创建 OCR 实例(整个脚本创建一次即可)
let ocrEngine = ocr.newOcr();

// ② 初始化:type 选择识别引擎,这里用 paddleOcrOnnxV6(PPOCR-V6)
let config = {
    "type": "paddleOcrOnnxV6",
    "modelTier": "small",  // 模型档位,默认 small(PP-OCRv6_small)
    "numThread": 2,        // 使用的 CPU 线程数
    "padding": 32,         // 图像外接白框,默认 32
    "maxSideLen": 640      // 按图像长边缩放,默认 640
};
if (!ocrEngine.initOcr(config)) {
    loge("OCR 初始化失败: " + ocrEngine.getErrorMsg());
    exit();
}

// ③ 截图并识别(可多次调用,用同一个实例)
function ocrScreen() {
    let img = image.captureFullScreenEx();
    if (!img) {
        loge("截图失败");
        return;
    }
    // 识别整张屏幕,超时 20 秒;extra 里也可以动态传识别参数,如 {"padding":32}
    let result = ocrEngine.ocrImage(img, 20 * 1000, {});
    if (result) {
        for (let i = 0; i < result.length; i++) {
            let item = result[i];
            logd("文字: " + item.label +
                 " 置信度: " + item.confidence +
                 " 位置: " + item.x + "," + item.y +
                 " 大小: " + item.width + "x" + item.height);
        }
    } else {
        logw("未识别到文字");
    }
    // 图片用完要回收
    image.recycle(img);
}

ocrScreen();
ocrScreen();

// ④ 结束前释放 OCR 资源(也可放在脚本停止回调 setStopCallback 中)
ocrEngine.releaseAll();

ocrImage 返回的是一个 JSON 数组,每条数据长这样(结构以官方文档为准):

[
  {
    "label": "领取奖励",
    "confidence": 0.93,
    "x": 11,
    "y": 25,
    "width": 100,
    "height": 40
  }
]
  • label:识别的文字内容
  • confidence:置信度,数值越高越可信
  • x / y / width / height:文字的位置与大小

拿到坐标,脚本就能做真正的“读字点字”——比如只处理包含“领取”、且置信度足够高的文字,点击它的中心:

for (let i = 0; i < result.length; i++) {
    let item = result[i];
    if (item.label.indexOf("领取") >= 0 && item.confidence > 0.8) {
        let cx = item.x + Math.floor(item.width / 2);
        let cy = item.y + Math.floor(item.height / 2);
        clickPoint(cx, cy);
        break;
    }
}

六、OCR 与找图 / 找色的组合定位

OCR 读“语义”,找图找色认“外观”,两者各有盲区:OCR 可能把相似字形认错(低清图、艺术字尤其明显);找图可能因为分辨率、界面主题或缩放而失效。组合定位的思路是互为校验

先用 OCR 定位文字区域,再在区域内用找图做二次确认,两个信号都命中才执行操作。

// ① 先用 OCR 找出包含"确认"的文字
let target = null;
for (let i = 0; i < result.length; i++) {
    if (result[i].label.indexOf("确认") >= 0) {
        target = result[i];
        break;
    }
}

if (target) {
    // ② 在文字附近区域用模板图做二次确认(模板图放在工程 res 目录)
    let template = readResAutoImage("confirm_btn.png");
    // 找图区域:以文字框为中心向外扩一圈,注意别超出屏幕
    let sx = Math.max(0, target.x - 50);
    let sy = Math.max(0, target.y - 50);
    let rect = image.findImage(
        img, template,
        sx, sy,
        target.x + target.width + 50, target.y + target.height + 50,
        0.7, 0.9, 1, 5   // 弱阈值、相似度、结果数量、匹配方法
    );
    if (rect && rect.length > 0) {
        let cx = parseInt((rect[0].left + rect[0].right) / 2);
        let cy = parseInt((rect[0].top + rect[0].bottom) / 2);
        clickPoint(cx, cy);
    }
}

反过来也常用:先找图确认按钮存在,再对按钮区域做 OCR,读出上面的文字用于分支判断(比如读余额、读状态、读倒计时)。原则就一条:把“语义判断”交给 OCR,把“外观确认”交给找图找色,各用所长。

七、常见坑与参数调优

先把几个高频参数说清楚(作用以官方文档为准):

参数 作用 调优方向
padding 图像外接白框,用于提升识别率;文字框没有正确框住所有文字时,增加此值 框不全、漏字时调大
maxSideLen 按图像长边整体缩放:放大更耗时但精度更高,缩小更快但精度降低(V4/V5 中设为 0 表示不缩放) 追求速度调小,追求精度调大
numThread 使用的 CPU 线程数(paddleOcrNcnnV5 默认 0,-1 代表最大 CPU) 线程多识别快,但 CPU 占用高
modelTier V6 的模型档位,默认 small(PP-OCRv6_small) 按文档提供的档位选择

实战中常见的坑和对应解法:

  1. 识别慢:整屏识别计算量大。先把屏幕裁剪出文字区域再识别;调小 maxSideLen;合理加大 numThread
  2. 小字识别不出 / 漏字:适当调大 maxSideLen 提升精度(代价是耗时增加);padding 专治“文字框没框住所有文字”。
  3. 每次识别都重新初始化:实例化和模型加载只做一次(文档强调“仅脚本开头一次即可”),识别循环复用同一个实例,能省掉大量重复开销。
  4. 忘了回收图片:识别完用 image.recycle(img) 回收截图,否则内存会持续上涨。
  5. 初始化失败先看错误信息:用 getErrorMsg() 拿具体原因再排查——老设备初始化 V6 失败,多半是系统版本低于安卓 8.0,换 V5 系列即可。
  6. 参数改了半天没生效:引擎类型(type)等参数在 initOcr 时确定;部分识别参数可以在 ocrImageextra 里动态传,别改错地方。

八、FAQ

Q1:端侧 OCR 识别需要联网吗? A:不需要。PPOCR-V4/V5/V6 全系模型都在手机本地完成识别,不依赖云端接口,断网、无信号也能正常识别。

Q2:PPOCR-V4/V5/V6 收费吗? A:不收费,全系免费。识别在本地完成,也没有按次计费、按量计费的隐性成本。

Q3:用 OCR 识别需要手机 root 吗? A:不需要。EasyClick 免 root 即可使用 OCR,支持安卓 5.0 ~ 最新系统。

Q4:安卓 7 的老手机能用 PPOCR-V6 吗? A:不能。V6 需要安卓 8.0(API 26)及以上;低版本手机请使用 paddleOcrNcnnV5 或 paddleOcrOnnxV5。

Q5:OCR 识别结果里除了文字还有什么? A:每条结果包含 label(识别的文字)、confidence(置信度)以及 x/y/width/height(位置与大小),可以直接用坐标做定位与点击。

Q6:OCR 识别慢怎么办? A:优先考虑调小 maxSideLen(按图像长边缩放,越小越快),合理设置 numThread 线程数;也可以先把屏幕裁剪出文字区域再识别,减少计算量。

Q7:识别小字或文字没被框全怎么办? A:适当调大 maxSideLen 会提升精度但增加耗时;padding 是图像外接白框,当文字框没有正确框住所有文字时,增加此值。

Q8:OCR 和找图找色是什么关系? A:找图找色靠像素/模板匹配,OCR 读的是文字内容。可以组合使用:先用 OCR 拿到文字坐标,再在坐标附近用找图确认目标,降低误判。

Q9:OCR 会把屏幕内容上传到服务器吗? A:不会。端侧识别全程在手机本地完成,数据不出设备;对比之下云端 OCR 需要把图片上传到服务器,才有隐私与流量问题。

Q10:脚本里怎么初始化 OCR? A:用 ocr.newOcr() 创建实例,initOcr({type: …}) 初始化(type 可选 paddleOcrOnnxV4/V5、paddleOcrNcnnV5、paddleOcrOnnxV6 等),ocrImage 识别,最后 releaseAll() 释放资源。完整示例见官方 OCR 文档


关于 EasyClick:手机自动化 AI 智能体平台,覆盖安卓免 root、iOS 免越狱、鸿蒙 Next 三大生态,提供脚本开发、苹果群控、本地中控投屏与云控系统。→ 了解全部产品

想要真实跑起来?

本文介绍的方案均可在 EasyClick 手机自动化平台落地。官网提供完整文档、开发工具与群控云控产品,免费体验。

访问 EasyClick 官网 →