一、为什么脚本要“读文字”
自动化脚本的本质,是“替人看屏幕、做判断、点按钮”。大多数时候,脚本靠控件树就能“看懂”界面:系统无障碍服务会把当前界面解析成一棵节点树,每个按钮、输入框、文本都有 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 不是“一张图哗啦一下变成文字”,而是一条两阶段流水线:
- 检测(Detection):先在图片里找出“哪些地方有文字”,把每一行或每一块文字用方框框出来。可以理解成“在房间里找哪些地方贴着纸条”。
- 识别(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) | 按文档提供的档位选择 |
实战中常见的坑和对应解法:
- 识别慢:整屏识别计算量大。先把屏幕裁剪出文字区域再识别;调小
maxSideLen;合理加大numThread。 - 小字识别不出 / 漏字:适当调大
maxSideLen提升精度(代价是耗时增加);padding专治“文字框没框住所有文字”。 - 每次识别都重新初始化:实例化和模型加载只做一次(文档强调“仅脚本开头一次即可”),识别循环复用同一个实例,能省掉大量重复开销。
- 忘了回收图片:识别完用
image.recycle(img)回收截图,否则内存会持续上涨。 - 初始化失败先看错误信息:用
getErrorMsg()拿具体原因再排查——老设备初始化 V6 失败,多半是系统版本低于安卓 8.0,换 V5 系列即可。 - 参数改了半天没生效:引擎类型(
type)等参数在initOcr时确定;部分识别参数可以在ocrImage的extra里动态传,别改错地方。
八、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 手机自动化平台落地。官网提供完整文档、开发工具与群控云控产品,免费体验。