一、为什么自动化需要“眼睛”
写自动化脚本的人会先遇到一个朴素的问题:脚本要操作界面,总得先“看见”界面在哪里。
常规路径是“读界面”——安卓系统会为当前界面生成一棵控件树(节点树),无障碍服务可以读取它,每个按钮、输入框、图片都有 id、文本、描述、类名和位置边界。脚本“按名字找控件”,找到就点,非常优雅。
但“读界面”有大量读不到的时候:
- 游戏画面:OpenGL / SurfaceView 渲染的内容没有标准控件节点,整个画面对无障碍服务来说几乎是空的;
- 自绘控件:很多 App 用 Canvas 自绘界面,节点树里只有空壳,没有可用的文本与描述;
- 图片按钮:一整张图就是一个按钮,没有文字可读、没有 id 可用。
这时脚本只剩一条路:像人一样“看屏幕”——截一张图,在截图里找目标,找到位置再操作。这就是图像识别的用武之地。
一句话总结:控件定位是“读界面”,图像识别是“看界面”。读不到的时候,“看”是兜底方案,也是游戏类、自绘类界面的唯一解。 在 EasyClick 里,这套“眼睛”就是图色模块(image 系列 API,对象前缀是 image,如 image.requestScreenCapture()),覆盖申请截图、抓屏、找图、找色、比色一整条链路,全部基于系统官方能力、免 root 实现。
二、从坐标到图像:识别为什么更稳
最早的自动化脚本是“坐标脚本”:录下屏幕上一个点的 (x, y),到点就点。它的致命弱点是脆弱——分辨率一变、布局一调、弹窗一弹,坐标就全废了。这也是很多“录出来的脚本能跑但不稳定”的根源。
图像识别换了个思路:不关心“目标在哪个坐标”,只关心“目标长什么样”。 找图找到的是目标本身的位置,坐标只是识别结果的一个附带输出。
这带来两个直接好处:
- 与分辨率、布局无关:按钮挪了位置、换了机型、改了字体,只要它长得还是那样,就能被找到。多机型适配时,一套模板往往通吃;
- 与“有没有控件”无关:控件树读不到的游戏画面、自绘界面,只要“看得见”就能识别。
坐标并没有被淘汰,它退位成了“兜底中的兜底”:只有目标完全没有颜色、形状特征可识别时(比如一片纯色空白区域),才退回到坐标,而且成熟平台支持“坐标区域内随机点击”,让每次点击位置不完全一致,降低操作特征(功能特性文档)。
三、找图:OpenCV 模板匹配原理
找图是目前应用最广的识别手段,它的内核是计算机视觉里最经典的算法之一——模板匹配。
原理可以用一句话说清:准备一张目标的小图(模板),在屏幕的大图上从上到下、从左到右“滑动”过去,每到一个位置就计算一次相似度,相似度超过阈值就判定命中。 命中后返回目标在屏幕上的位置区域(Rect),取区域中心点就是可用的点击坐标。
模板图怎么来?开发工具里把目标区域截下来存成小图,放进工程 res 文件夹,脚本里用 readResAutoImage() 读取即可。
相似度由阈值参数控制:
| 参数 | 含义 | 默认值 |
|---|---|---|
threshold |
图片相似度,0~1 的浮点数,最终命中判定标准 | 0.9 |
weakThreshold |
弱阈值,用于每一轮匹配中检验是否继续匹配,相似度低于它则提前放弃该轮 | 0.7 |
匹配方法(method 参数)有 6 种可选,全部来自 OpenCV 的标准模板匹配方法:
| 取值 | 匹配方法 | 说明 |
|---|---|---|
| 0 | TM_SQDIFF | 平方差匹配法 |
| 1 | TM_SQDIFF_NORMED | 归一化平方差匹配法 |
| 2 | TM_CCORR | 相关匹配法 |
| 3 | TM_CCORR_NORMED | 归一化相关匹配法 |
| 4 | TM_CCOEFF | 系数匹配法 |
| 5 | TM_CCOEFF_NORMED | 归一化系数匹配法 |
关于识别率,先对齐一个口径:“图像识别率 95% 以上”是官方宣称值,指常规、清晰、静态界面下的表现(功能特性文档)。实际效果取决于截图清晰度、模板是否与当前画面一致、界面是否频繁变化、阈值是否合理——这些我们在第五节讲怎么调。
一个最小可用的找图示例(函数名与参数顺序以文档为准):
image.initOpenCV(); // 找图前先初始化 OpenCV 类库(首次较慢,之后很快)
let template = readResAutoImage("sms.png"); // 从工程 res 文件夹读取模板小图
let aimage = image.captureFullScreen(); // 抓取当前屏幕
// 找图:弱阈值 0.7,相似度 0.9,最多找 21 个,method=5 归一化系数匹配法
let points = image.findImage(aimage, template, 0, 0, 0, 0, 0.7, 0.9, 21, 5);
if (points && points.length > 0) {
// 返回的是 Rect 区域,取中心点作为点击坐标
let x = parseInt((points[0].left + points[0].right) / 2);
let y = parseInt((points[0].top + points[0].bottom) / 2);
clickPoint(x, y);
}
image.recycle(aimage); // 图片用完要回收,防止内存暴涨
image.recycle(template);
找图家族还有几个实用变体:findImageEx 自动截屏找图(省去手动抓图);findImage2 含缩放的找图,比 findImage 更精准(目标在屏幕上被放大或缩小渲染时很管用,EC 9.41.0+);findImageByColor 透明找图,支持透明模板且不需要初始化 OpenCV(EC 7.15.0+)。
性能上有个常识值得知道:截图本身就有开销——captureScreen 需要把截图转为 Bitmap,耗时约 0~20ms,而且短时间内(约 16ms 内)连续调用会返回同一张截图(图色函数文档)。模板匹配内部采用图像金字塔加速(matchTemplate 的 maxLevel 参数),一般无需手动干预。
四、找色:单点找色、多点找色与色块查找
找色比找图更“轻”:它不比对整张图,而是按颜色找像素——在指定区域里找指定颜色的点,找到就返回坐标。
单点找色(findColor)
在区域内找颜色与给定值(可带偏色)完全匹配的点,返回坐标数组。典型场景:固定色 UI、纯色按钮、状态指示灯。颜色参数格式形如 0xCDD7E9-0x101010,前半是颜色值,后半是偏色容差;threshold 是 01 的颜色相似度,8 的查找方向。limit 限制返回个数,orz 是 1
单点找色的短板也很明显:只验证一个点的颜色,遇到大面积同色背景容易误报。
多点找色(findMultiColor)
多点找色用一个点做“锚点”,再校验周围若干点的相对位置 + 颜色,全部符合才算命中:
// 第一个点的颜色作为基准,再校验相对偏移点
// 格式:x偏移|y偏移|颜色[-偏色],多个点用逗号分隔
let firstColor = "0xDD7A5F-0x101010";
let points = "29|25|0xBB454B-0x101010,58|44|0xA6363A-0x101010";
// findMultiColorEx 自动截屏多点找色
let result = image.findMultiColorEx(firstColor, points, 0.9, 0, 0, 0, 0, 10, 1);
if (result && result.length > 0) {
clickPoint(result[0].x, result[0].y);
}
多点组合相当于给目标发了一张“身份证”——几个点之间的相对位置和颜色几乎不可能在别处巧合重现,抗干扰能力比单点强一个量级。典型场景:图标角标、红点、带纹理的按钮。
比色(cmpColor)与色块查找
比色回答的是另一个问题:“某个位置的几个点,颜色是不是我预期的样子?”cmpColor / cmpColorEx 对一组点逐一校验,全部符合才返回 true,适合判断按钮状态切换(“可领取”变灰、可点击状态变化);cmpMultiColor / cmpMultiColorEx 支持多组颜色方案依次尝试,命中就返回该组的索引,未命中返回 -1。
色块查找则利用找色的“批量返回”能力:找色函数会返回区域内所有匹配点(limit 控制个数),从而可以定位连续色块、找到色块的边界。比如血条、进度条这类目标,常见做法是找出血条两端的颜色边界,再按坐标比例估算剩余量——不需要一张完整的模板图。
另外,开发工具自带找色功能,可以直接在截图上取点、取色、生成配置文件,配套的 J 系列函数(findColorJ、findMultiColorJ 等)直接从 JSON 文件读取参数,不用手写长串颜色字符串。
五、识别失败怎么办:调优三板斧
识别不是魔法,失败是常态,关键是有一套系统化的排查方法。
第一板斧:调阈值
- 找不到 → 降低
threshold:0.9 找不到就试 0.85、0.8; - 找错位置(误识别)→ 提高
threshold:相似元素太多时把标准调严。
找色的 threshold 同理。注意阈值只是“宽容度”,模板本身要新、要清晰,版本更新后界面变了,先换模板再调阈值。
第二板斧:限定搜索范围
全屏查找既慢又容易被干扰。找图找色都支持 x, y, ex, ey 四个参数限定搜索区域——把范围圈到目标可能出现的小区域,又快又准,还能顺带避免同屏相似元素造成的误识别。
第三板斧:多图备选 + 等待重试
- 多图备选:同一个目标准备多套模板(不同状态、不同主题),依次尝试;多组比色用
cmpMultiColor一次搞定; - 等待出现:页面加载有延迟,正确姿势是“等目标出现再操作“(循环识别,超时退出),而不是盲目
sleep固定时长。image.setInitParam可以设置找图找色动作的最大时间(action_timeout),超时自动返回、避免阻塞。
一个组合兜底示例(找图 + 找色)
识别手段常常要混用:优先找图,找不到就退而求其次找色,再不行就重试。下面这个示例把“申请截图 → 找图 → 找色兜底”串在一起:
function main() {
// 1. 申请截图权限,整个脚本执行一次即可(type 0 自动选择)
let request = image.requestScreenCapture(10000, 0);
if (!request) {
loge("申请截图权限失败");
exit();
}
// 申请完权限至少等 1 秒再截图,否则可能截不到图
sleep(1000);
// 2. 找图:在屏幕上找"开始任务"按钮模板(res 文件夹下的 start.png)
image.initOpenCV();
let template = readResAutoImage("start.png");
// findImageEx 自动截屏找图:弱阈值 0.7,相似度 0.9,只找 1 个
let points = image.findImageEx(template, 0, 0, 0, 0, 0.7, 0.9, 1, 5);
if (points && points.length > 0) {
// 返回 Rect 区域,取中心点点击
let x = parseInt((points[0].left + points[0].right) / 2);
let y = parseInt((points[0].top + points[0].bottom) / 2);
clickPoint(x, y);
} else {
// 3. 找图失败,退而求其次:找红色角标(颜色带偏色容差)
let redPoints = image.findColorEx("0xFF3B30-0x101010", 0.9, 0, 0, 0, 0, 10, 1);
if (redPoints && redPoints.length > 0) {
clickPoint(redPoints[0].x, redPoints[0].y);
} else {
loge("找图、找色都没有命中,等待后重试");
}
}
image.recycle(template);
}
main();
排查速查表
| 现象 | 常见原因 | 对策 |
|---|---|---|
| 找不到图 | 目标还没出现 / 模板与画面不一致 / 阈值过高 | 等目标出现再识别;换新模板;降低 threshold |
| 找错位置 | 同屏相似元素多 / 阈值过低 | 提高 threshold;缩小搜索区域;改用多点找色 |
| 识别慢 | 全屏查找 / 频繁手动截图 | 限定 x,y,ex,ey;用 Ex 系列自动截屏函数 |
| 长时间运行内存涨 | 图片对象没回收 | 用完立即 image.recycle(img) |
最后是截图的三个常识:无障碍模式下首次调用 requestScreenCapture 会弹系统授权,建议选“总是允许”;代理模式下截图不需要权限,长时间无人值守运行建议用代理模式(图色函数文档);申请权限后至少要等约 1 秒再截图。
六、降低操作特征:坐标区域内随机点击
自动化操作要面对一个现实问题:机器行为是有特征的。 每次点击同一个像素点、固定间隔、固定顺序,在批量场景下很容易被识别为“非人工操作”。
降低操作特征最实用的一招,是让点击位置在目标区域内随机化——EasyClick 支持所有点击动作在坐标区域内随机(功能特性文档)。这一步和图像识别天然配合:找图返回的本来就是一块区域(Rect),在区域内随机取点即可:
// 在目标区域内随机取点再点击,避免每次点击位置完全一致
let x = parseInt(points[0].left + Math.random() * (points[0].right - points[0].left));
let y = parseInt(points[0].top + Math.random() * (points[0].bottom - points[0].top));
clickPoint(x, y);
需要说明的是,本文讲的所有识别与操作都建立在系统官方能力之上(无障碍服务、ADB、截屏 API 等),本身合法合规;风险永远来自使用用途本身——请始终坚持合规使用,技术手段只是工具。
七、图像识别 API 速览表
以下函数均出自图色函数文档,名称与参数请以文档为准:
| 类别 | 函数 | 作用 |
|---|---|---|
| 截图权限 | image.requestScreenCapture(timeout, type) |
申请屏幕截图权限;type:0 自动 / 1 授权 / 2 无需权限(需代理模式) |
| 截图 | image.captureScreen / image.captureFullScreen / image.captureFullScreenEx |
截取屏幕 / 全屏 / 全屏扩展,返回图片对象 |
| 截图 | image.captureScreenshot / image.captureScreenSurface |
代理模式 SurfaceControl 截图(EC 安卓 12.4.0+) |
| 截图 | image.captureToFile |
截图保存为 PNG 文件 |
| 找图 | image.findImage(image, template, …) |
模板匹配找图,返回位置区域 |
| 找图 | image.findImageEx(template, …) |
自动截屏找图 |
| 找图 | image.findImage2(image, template, …) |
含缩放的找图,更精准(EC 9.41.0+) |
| 找图 | image.findImageByColor(image, template, …) |
透明找图,无需初始化 OpenCV |
| 找色 | image.findColor(image, color, threshold, …) |
单点找色,返回坐标数组 |
| 找色 | image.findColorEx(color, …) |
自动截屏单点找色 |
| 找色 | image.findMultiColor(image, firstColor, points, …) |
多点找色 |
| 找色 | image.findMultiColorEx(firstColor, points, …) |
自动截屏多点找色 |
| 比色 | image.cmpColor / image.cmpColorEx |
单点或多点比色,全部符合返回 true |
| 比色 | image.cmpMultiColor / image.cmpMultiColorEx |
多组比色,返回命中组索引(-1 为未找到) |
| 找非色 | image.findNotColor |
查找与指定颜色不相等的点 |
| OpenCV | image.initOpenCV() |
初始化 OpenCV 类库(找图前调用) |
| OpenCV | image.matchTemplate / image.matchTemplateEx |
模板匹配封装(弱/强双阈值) |
| OpenCV | image.useOpencvMat(1) |
切换 mat 存储格式,更快更省内存(EC 10.18.0+) |
| 资源管理 | image.recycle(img) / image.recycleAllImage() |
回收图片,防止内存暴涨 |
两条规律帮你记住这套命名:Ex 结尾 = 自动截屏版(不用手动抓图);J 结尾 = 参数从 JSON 文件读取(findColorJ、findMultiColorJ 等,配置文件由开发工具生成)。
八、场景建议速查表
| 你的场景 | 推荐手段 | 理由 |
|---|---|---|
| 游戏画面 / 自绘界面(无控件) | 找图 findImageEx + 多点找色 |
模板匹配定位非标准控件 |
| 固定色按钮 / 状态点 | 单点找色 findColorEx |
颜色鲜明,执行最快 |
| 图标角标 / 红点 / 复杂图标 | 多点找色 findMultiColorEx |
组合特征抗干扰 |
| 判断按钮可点 / 不可点 | 比色 cmpColorEx |
一组点全部符合才命中 |
| 血条 / 进度条 | 找色定位颜色边界,按比例估算 | 无需完整模板图 |
| 目标可能被缩放渲染 | findImage2 缩放找图 |
比 findImage 更精准 |
| 需要读取屏幕文字 | OCR 识别(PPOCR-V4/V5/V6 全免费、本地离线) | 文字类内容用 OCR |
| 需要识别对象(怪物、物品、人物) | YOLO 目标检测(ncnn 手机优化,文档) | 神经网络定位非文字对象 |
| 长时间无人值守运行 | 代理模式 + 区域内随机点击 | 代理模式截图无需权限 |
更完整的识别栈(找图找色 / OCR / YOLO 三级)与运行模式选型,可以看安卓自动化技术栈全景。
九、FAQ
Q1:找图和找色有什么区别,什么时候用哪个? A:找图基于 OpenCV 模板匹配,识别的是“整张图的样子”,适合特征复杂的目标(图片按钮、图标、游戏画面元素);找色识别的是“颜色特征”,适合颜色鲜明、结构简单的目标(纯色按钮、角标红点、血条端点),执行更快。复杂场景常组合使用。
Q2:OpenCV 图像识别率 95%+ 是什么意思? A:95%+ 是官方宣称的识别率,指常规、清晰、静态界面下的表现。实际效果取决于截图清晰度、模板质量、界面变化程度与阈值设置,识别失败可通过降低阈值、限定区域、多图备选、重试来改善。
Q3:找图的相似度阈值怎么调?
A:threshold 是 0~1 的浮点数,默认 0.9。找不到就适当调低(如 0.85、0.8),误识别就调高;weakThreshold(默认 0.7)控制每轮匹配是否继续,一般保持默认即可。
Q4:单点找色和多点找色怎么选? A:单点找色只验证一个点的颜色,遇到大面积同色背景容易误报;多点找色以第一个点颜色为基准,再校验周围若干点的相对位置与颜色,组合特征更唯一、抗干扰更强。图标角标、红点这类场景建议用多点找色。
Q5:图像识别需要 root 吗? A:不需要。截图、识别、点击都基于系统官方能力,在免 root 范围内完成,EasyClick 支持安卓 5.0 及以上的系统。
Q6:找到目标之后怎么点击? A:找图返回位置区域,取中心点或区域内随机点点击;找色返回坐标点数组,取对应 x/y 点击。为降低操作特征,建议点击位置在目标区域内随机化。
Q7:为什么有时候找不到图? A:常见原因:目标还没出现(加载延迟)、模板与实际画面不一致(版本更新、主题变化、缩放)、阈值过高、搜索范围不对。对策:等待目标出现、准备多套模板、降低阈值、限定搜索区域。
Q8:截图需要什么权限?
A:无障碍模式下首次调用 requestScreenCapture 会弹出系统授权,选择“总是允许”;代理模式下截图不需要权限,长时间无人值守运行建议用代理模式。申请权限后需等待约 1 秒再截图。
Q9:找图找色慢怎么办?
A:把搜索范围限定到目标可能出现的小区域,避免全屏查找;用 Ex 系列自动截屏函数减少重复截图;图片用完及时 recycle;找图前先 initOpenCV;需要极致性能时可用 useOpencvMat 切换 mat 存储格式,速度更快、内存占用更少。
Q10:截图图片为什么要回收?
A:截图返回的是内存中的图片对象,不回收会持续占用内存,长时间运行可能内存暴涨。每次用完调用 image.recycle(img) 及时回收即可。
关于 EasyClick:手机自动化 AI 智能体平台,覆盖安卓免 root、iOS 免越狱、鸿蒙 Next 三大生态,提供脚本开发、苹果群控、本地中控投屏与云控系统。→ 了解全部产品
想要真实跑起来?
本文介绍的方案均可在 EasyClick 手机自动化平台落地。官网提供完整文档、开发工具与群控云控产品,免费体验。