为什么要本地 OCR:原理和两条路线
自动化脚本要看屏幕、做判断、点按钮。大多数时候靠控件树:系统无障碍服务会把当前界面解析成一棵节点树,每个按钮、输入框、文本都有 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 接口,等服务器识别完再把结果传回来。自动化脚本里,两边的差别如下:
| 维度 | 云端 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 落地。官网提供完整文档、开发工具与自动化产品,免费体验。