安卓OCR自动化原理

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

云端 OCR 要联网、有隐私顾虑;端侧离线模型 PPOCR-V4/V5/V6 全免费。怎么选模型、脚本里怎么调用、padding 和 maxSideLen 怎么调,一次讲完。

约 14 分钟

为什么要本地 OCR:原理和两条路线

自动化脚本要看屏幕、做判断、点按钮。大多数时候靠控件树:系统无障碍服务会把当前界面解析成一棵节点树,每个按钮、输入框、文本都有 id、文本、描述和位置。按文本找控件再点,受分辨率影响小。有一类文字控件树读不到:

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

这些内容要把屏幕当图片来读。OCR(Optical Character Recognition,光学字符识别)做的就是这件事:输入一张图,输出图里的文字和位置。蓝牙 HID / OTG HID 模式下,脚本不依赖无障碍服务,控件树走不通,能读文字的手段主要是 OCR(该模式仅支持图色、OCR 与点击滑动等基础操作)。EasyClick 的 OCR 免 root,支持安卓 5.0 到最新系统,PPOCR-V4/V5/V6 全系免费、本地离线识别(OCR 文档)。

另一种做法是把图片传到云平台的 OCR 接口,等服务器识别完再把结果传回来。自动化脚本里,两边的差别如下:

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

脚本经常是截一次屏识别一次,一天几千上万次。云端方案要等网络,断网就停。端侧 OCR 把识别引擎和模型装进手机,本地算完。文档里也提供了在线识别类型(如百度在线识别 baiduOnline、EC 自带的 PC 端 paddleOcrOnline 服务),以及免费的 PC 端 PPOCR-ONNX 程序(在 Windows 上运行,通过 HTTP 接口给安卓 / iOS / 鸿蒙 Next 调用,默认端口 9022,PPOCR 说明)。电脑就在旁边、需要更大吞吐时可以用这些。PPOCR 系列在 EasyClick 里的默认用法是手机本地离线识别。

识别分两段。检测(Detection)先在图片里找出哪些地方有文字,把每一行或每一块用方框框出来。识别(Recognition)再把每个方框里的图像转成字符。两个阶段各自是一个或一组神经网络模型,模型文件随应用内置(路径可配置,不配置就用自带的)。全程在手机本地完成,不联网、不传图、不按次计费。PaddleOCR 是百度飞桨(PaddlePaddle)生态的开源 OCR 方案,PPOCR-V4 / V5 / V6 是它的模型版本。EasyClick 内置的是这一家,文档确认支持 PPOCR-V4、V5 和 V6,其中 V6 需要安卓 8.0 / API 26 及以上(OCR 文档)。调参时说的「检测框」「文字框没框住文字」,指的是第一阶段。后面的 padding 就是用来修这个环节的。

模型怎么选,脚本里怎么调用

先看系统版本,其余按实际效果试。文档没有给出哪个一定更快。

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。它是最新模型(官方 ppocr-sdk + ORT 实现),默认档位 modelTier=small,EC 当前最新版本 12.4 已支持。安卓 8.0 以下用 paddleOcrNcnnV5 或 paddleOcrOnnxV5,文档写明低版本系统跑不了 V6。文档没有给出 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 找出包含"确认"的文字
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) 按文档提供的档位选择

识别慢,多半是整屏都在算。先把屏幕裁出文字区域,调小 maxSideLen,再把 numThread 加大一点。小字认不出、字被框漏了,把 maxSideLen 调大,精度上去,耗时也上去。padding 用来对付文字框没框住所有文字。实例化和模型加载只做一次,文档写的是仅脚本开头一次即可,循环里复用同一个实例。截图用完调用 image.recycle(img),不然内存会一直涨。初始化失败先看 getErrorMsg()。老设备初始化 V6 失败,多半是系统低于安卓 8.0,换 V5 系列。引擎类型 type 在 initOcr 时确定,一部分识别参数可以在 ocrImage 的 extra 里再传。


关于 EasyClick:手机自动化脚本开发平台,覆盖安卓免 ROOT、iOS 免越狱、鸿蒙三大手机生态,还提供苹果脚本编写、苹果群控、安卓群控、远程云控、AI 群控手机等。→ 了解全部产品

想要真实跑起来?

本文介绍的方案均可基于 EasyClick 能力在 iEasyClick 落地。官网提供完整文档、开发工具与自动化产品,免费体验。

访问 iEasyClick 官网 →