图片找图 — match 模板匹配详解
什么是找图
找图(模板匹配)是在大图像(通常是当前屏幕截图)中查找是否存在一张小图像(模板)的操作。与找色的区别在于:找色只匹配颜色,找图匹配整块区域的像素特征。
截图 (screen) 模板 (icon)
┌──────────────┐ ┌─────┐
│ │ │ [🔍]│
│ [🔍] │ ← 匹配 │ │
│ │ └─────┘
└──────────────┘
基本用法
准备模板图片
首先需要一张干净的模板图片——要找的目标区域的截图:
- 打开目标界面
- 用 AutoJs6 截图或系统截图
- 手动剪裁出目标区域(建议在电脑上用图片编辑器)
- 保存为 PNG(推荐,无损)或 JPG
加载模板
// 从文件加载模板图
let template = images.read("/sdcard/templates/login_btn.png");
// 或用网络图片(v6.7 新增异步加载)
// let template = await images.loadAsync("https://...");
执行匹配
let screen = images.captureScreen();
let template = images.read("/sdcard/templates/login_btn.png");
// 基本匹配
let result = images.match(screen, template);
console.log("匹配结果类型: " + typeof result);
// 单目标匹配 —— 返回最佳匹配点
if (result) {
console.log("找到模板!位置: (" + result.x + ", " + result.y + ")");
console.log("相似度: " + result.matches); // 0-100
click(result.x + template.width / 2, result.y + template.height / 2); // 点击中心
}
screen.recycle();
template.recycle();
匹配阈值
相似度阈值(threshold)
// threshold 决定多大相似度才算"匹配"
// 默认 0.9(90% 相似度)
let result = images.match(screen, template, {
threshold: 0.9 // 0-1,越接近 1 要求越高
});
// 更宽松(适配颜色略有差异的情况)
let result = images.match(screen, template, { threshold: 0.8 });
// 更严格(避免误匹配)
let result = images.match(screen, template, { threshold: 0.95 });
阈值选择建议
| 阈值范围 | 适用场景 | 风险 |
|---|---|---|
| 0.95 - 1.0 | 静态界面、精确匹配 | 可能找不到 |
| 0.85 - 0.95 | 推荐 | 平衡精度与召回 |
| 0.7 - 0.85 | 有细微变化的界面 | 可能误匹配 |
| < 0.7 | 模糊匹配 | 误匹配概率高 |
多目标匹配
当屏幕上可能出现多个模板实例时:
// 找全部匹配点
let results = images.matchAll(screen, template, {
threshold: 0.85
});
console.log("共找到 " + results.length + " 个匹配");
results.forEach(r => {
console.log("位置: (" + r.x + ", " + r.y + ") 相似度: " + r.matches);
});
// 逐个点击
results.forEach(r => {
click(r.x + template.width / 2, r.y + template.height / 2);
sleep(300); // 间隔一下
});
去重匹配
matchAll 默认开启了非极大值抑制(NMS),避免同一个位置返回多个重叠结果。可以调整去重距离:
let results = images.matchAll(screen, template, {
threshold: 0.85,
region: [0, 0, 1080, 1920], // 限定搜索区域
max: 10, // 最多返回几个结果
weak: true, // 是否包含弱匹配结果
// 去重相关
thresholdFirst: false, // 先做阈值筛选再去重
similarityThreshold: 0.9, // 去重时的相似度阈值
target: null // 自定义去重目标函数
});
区域限定匹配
// 只在屏幕下半部分搜索
let result = images.match(screen, template, {
region: [0, 960, 1080, 960] // x, y, width, height
});
// 性能提升明显!
多模板匹配
// 准备多个模板,分别匹配
let templates = [
{ name: "play", img: images.read("/sdcard/templates/play.png") },
{ name: "pause", img: images.read("/sdcard/templates/pause.png") },
{ name: "stop", img: images.read("/sdcard/templates/stop.png") }
];
function findBestMatch(screen, templates) {
let best = null;
templates.forEach(t => {
let r = images.match(screen, t.img, { threshold: 0.8 });
if (r && (!best || r.matches > best.matches)) {
best = { name: t.name, x: r.x, y: r.y, similarity: r.matches };
}
});
return best;
}
let screen = images.captureScreen();
let found = findBestMatch(screen, templates);
if (found) {
console.log("匹配到: " + found.name + " 相似度: " + found.similarity);
}
// 清理
screen.recycle();
templates.forEach(t => t.img.recycle());
图像预处理
匹配前可以对图像做预处理,提高匹配成功率:
剪裁后匹配
let screen = images.captureScreen();
// 只在关键区域剪裁后匹配
let region = images.clip(screen, 0, 1000, 1080, 500);
let result = images.match(region, template, { threshold: 0.9 });
region.recycle();
screen.recycle();
灰度匹配
某些情况下灰度图比彩色更稳定:
// 预先把模板转为灰度
let template = images.read("/sdcard/templates/target.png");
let grayTemplate = images.grayscale(template);
// 灰度图做匹配
let result = images.match(screen, grayTemplate);
template.recycle();
grayTemplate.recycle();
二值化
let binary = images.binary(template, {
threshold: 128 // 二值化阈值
});
常见问题
匹配返回 null
// 可能原因排查:
// 1. 模板图片太大 —— 推荐 < 200x200px
// 2. 阈值太高 —— 降低到 0.8 或 0.7
// 3. 模板不在截图中 —— 确保目标真的在屏幕上
// 4. 分辨率不同 —— 模板要在目标设备上截取
// 调试:逐步降低阈值
for (let t = 0.95; t >= 0.5; t -= 0.05) {
let r = images.match(screen, template, { threshold: t });
if (r) {
console.log("阈值 " + t + " 时匹配成功,相似度 " + r.matches);
break;
}
}
匹配位置偏移
截图坐标系和点击坐标系可能存在差异:
// 点击时记得加模板宽高的一半(点中心)
click(result.x + template.width / 2, result.y + template.height / 2);
内存管理
// ❌ 错误:不释放图片
for (let i = 0; i < 100; i++) {
let screen = images.captureScreen();
// 使用 screen...
// 忘了 recycle!循环 100 次 = 内存爆炸
}
// ✅ 正确:用完就释放
for (let i = 0; i < 100; i++) {
let screen = images.captureScreen();
// 使用 screen...
screen.recycle(); // ✅ 及时释放
}
性能优化
| 优化 | 方法 | 效果 |
|---|---|---|
| 限定搜索区域 | region 参数 | 搜索范围越小越快 |
| 小尺寸模板 | < 200x200px | 计算量减少 |
| 复用截图 | 同一张图多次匹配 | 避免重复截图开销 |
| 灰度匹配 | 转灰度后匹配 | 减少色彩干扰 |
| 合适阈值 | 先 0.85 再微调 | 避免反复尝试 |