# Python 找图点击脚本：蓝叠与雷电模拟器图像识别循环

> 在 Asset Lab 中截取模板，在 BlueStacks 或 LDPlayer 上匹配，并循环等待某个状态出现，做出一个带图像识别的 Python 找图点击脚本。

Source: https://automationmacro.com/zh-CN/docs/guides/image-recognition-macros (教程, updated 2026-09-05)

一个带图像识别的 Python 找图点击脚本在行动之前先看屏幕。它点按钮是因为按钮在那里，关弹窗是因为弹窗出现了，"体力不足"提示出现时它就停下。这篇指南在 Macro Automation Studio（MAS）中、在 BlueStacks 或 LDPlayer 上做出这样一个脚本，从第一次裁剪模板到一个等待某个状态的循环。适合已经用不惯模拟器自带录制器的人。

## 为什么录制器不会分支

BlueStacks 或 LDPlayer 的宏录制器保存点击和时间，然后重放。它从不看屏幕。它无法知道弹窗盖住了按钮、今天加载页面花了更长时间，或者计数器归零了。每一个录制的宏都是一条直线。

MAS 宏是一段 Python 脚本。每次 `mas.find_object` 调用都截一张图，用 OpenCV 模板匹配在其中搜索模板图片。最佳匹配达到阈值（默认 `threshold=0.8`）时，调用返回它的中心。否则返回 `None`。正是这一个 `if`，让宏能够分支、等待、重试和停止。

## 开始之前

- 已安装 MAS 并用试用或套餐登录。试用从[免费下载](/download)开始；参见[安装 MAS](/docs/install)。
- BlueStacks 或 LDPlayer 正在运行、已开启 ADB，并已添加为设备。参见 [BlueStacks](/docs/bluestacks-setup-guide) 或 [LDPlayer](/docs/ldplayer-setup-guide) 指南。
- 模拟器使用一个你会一直保持的固定分辨率。各指南以竖屏 540x960、240 DPI 为基准。
- Code Editor 中打开了一个代码型项目。参见[快速开始](/docs/getting-started)。

## 在 Asset Lab 中截取模板

1. 把应用切到有你要找的按钮的屏幕。
2. 在 Code Editor 中打开**资源**面板，点击**打开资源助手**。Asset Lab 会带着你设备的实时画面打开。
3. 紧贴按钮裁一个矩形并保存。裁剪会进入你的图片库并获得一个数字 ID。
4. 对宏必须识别的每个状态重复：弹窗的关闭按钮、确认按钮、"完成"提示。
5. 在**资源**面板中对每张图片点击**复制 ID**，在脚本顶部声明它们。

```python
import mas

images = mas.images({
    "play_button": 201,
    "close_popup": 202,
    "out_of_energy": 203,
})
```

`mas.images` 给每个 ID 一个可读的名字，并告诉打包器发布时要捆绑哪些图片。模板可以是 jpg、png、gif 或 webp，单张最大 10 MB。参见 [Asset Lab](/docs/asset-lab)。

> [!TIP]
> 裁剪永远不变的部分。带数字的按钮只能匹配那个数字；改为裁数字旁边的图标。

## 选择合适的调用

| 调用 | 适用场景 | 返回 |
|---|---|---|
| `find_object(image)` | 你只想看一眼当前屏幕 | `ObjectMatch` 或 `None` |
| `find_object_retry(image, total_tries=3, time_sleep=2.0)` | 目标可能需要一会儿才出现 | `ObjectMatch`，或最后一次尝试后返回 `None` |
| `find_any_object([a, b, c])` | 几个模板都可以接受：弹窗变体、两套主题 | 第一个匹配，带 `matched_template_id` |
| `find_objects(image, max_matches=10)` | 同一个图标出现多次，你全都要 | 按置信度排序的列表 |

`find_object_retry` 是本站惯用的等待原语：它最多调用 `find_object` `total_tries` 次，失败的尝试之间固定停顿 `time_sleep`，最后一次之后不再停顿。其他所有关键字（`threshold`、`search_region`、`screenshot`）都会转发给 `find_object`。`find_any_object_retry` 对列表做同样的事。优先用这些，而不是为旧宏保留的 `wait_for_object`。

```python
match = mas.find_object_retry(images.play_button, total_tries=5, time_sleep=1.5)
if match:
    mas.click(match.x, match.y, delay_ms=1000)
else:
    mas.log("Play button did not appear", level="warning")
```

`ObjectMatch` 有 `x`、`y`、`center` 和 `matched_template_id`。它不带分数，所以请在调用上调整阈值，而不是事后读取置信度。

## 用 search_region 缩小搜索范围

模板匹配会把模板滑过整张截图。`Region` 把滑动限制在一个矩形内，这样更快，也避免屏幕其他位置的相似图案。坐标是相对左上角的像素。

```python
from mas import Region

TOP_BAR = Region(x1=0, y1=0, x2=540, y2=120)

energy_icon = mas.find_object(images.energy_icon, search_region=TOP_BAR)
```

只要你知道元素在哪里，就用区域：计数器在顶栏，操作按钮在底部，对话框在中间。从 Asset Lab 读取区域；坐标拾取器会在实时画面上显示像素坐标。

## 调整阈值

`threshold` 的范围是 0.0 到 1.0，默认 0.8。匹配必须达到它才算数。

- 当正确的模板因为抗锯齿、轻微的缩放变化或按钮上的发光效果一直匹配不上时，降到 0.7。
- 当模板匹配到错误的位置时，例如一排两个相似的图标，或者一个也会以灰色禁用态出现的按钮，提高到 0.9。
- 降到 0.7 以下之前先重新裁剪。那么低的阈值几乎会接受任何同色的东西。

```python
enabled = mas.find_object(images.claim_enabled, threshold=0.9)   # strict: disabled and enabled look alike
glowing = mas.find_object(images.reward_icon, threshold=0.7)     # lenient: the icon has a pulsing glow
```

## 用 find_any_object 处理弹窗

弹窗是录制宏失效的原因。给宏一个认识所有弹窗的函数，在每一轮循环的开头调用它。

```python
POPUPS = [images.close_popup, images.confirm_button, images.later_button]


def clear_popups():
    popup = mas.find_any_object(POPUPS)
    if popup is None:
        return False
    mas.log(f"Closing popup {popup.matched_template_id}")
    mas.click(popup.x, popup.y, delay_ms=800)
    return True
```

`find_any_object` 接受一个列表并返回第一个匹配。`search_strategy="best_match"` 改为在整个列表中选最高分，`"priority_order"` 按你列出的顺序尝试模板。返回 `True` 让循环 `continue`，这样下一轮看到的是干净的屏幕。

## 雷电模拟器脚本循环：等待某个状态

等待就是一个带截止时间的循环。`find_object_retry` 覆盖短等待；对于可能长达一分钟的加载页面，自己写循环，这样可以记录进度并干净地放弃。

```python
import time

def wait_for(image, timeout_s=60, every_s=2.0):
    deadline = time.monotonic() + timeout_s
    while time.monotonic() < deadline:
        match = mas.find_object(image)
        if match:
            return match
        time.sleep(every_s)
    return None
```

`time.monotonic()` 按秒计数，时钟变化时也不会跳变，这让它成为截止时间的正确计时器。每个等待都必须有终点：返回 `None`，让调用方决定是否停止运行。

## 一个完整的蓝叠图像识别脚本

这个脚本打开一个屏幕、清理弹窗、在按钮可用时点击它，并在"体力不足"模板出现或时间预算用完时停止。把图片 ID 替换成你图片库里的。

```python
import random
import sys
import time

import mas
from mas import Region

images = mas.images({
    "play_button": 201,
    "close_popup": 202,
    "confirm_button": 203,
    "out_of_energy": 204,
    "home_screen": 205,
})

BOTTOM = Region(x1=0, y1=700, x2=540, y2=960)
TIME_BUDGET_S = 15 * 60


def clear_popups():
    popup = mas.find_any_object([images.close_popup, images.confirm_button])
    if popup is None:
        return False
    mas.click(popup.x, popup.y, delay_ms=800)
    return True


def main():
    home = mas.find_object_retry(images.home_screen, total_tries=10, time_sleep=3.0)
    if home is None:
        mas.log("Home screen never appeared", level="error")
        sys.exit(2)

    started = time.monotonic()
    taps = 0
    while time.monotonic() - started < TIME_BUDGET_S:
        if clear_popups():
            continue
        if mas.find_object(images.out_of_energy):
            mas.log("Out of energy, done")
            break
        button = mas.find_object_retry(images.play_button, total_tries=3, time_sleep=2.0, search_region=BOTTOM)
        if button is None:
            mas.log("Play button not found, looking again", level="warning")
            continue
        mas.click(button.x, button.y, delay_ms=1000)
        taps += 1
        time.sleep(random.uniform(0.8, 2.0))

    mas.log(f"Finished after {taps} taps")


if __name__ == "__main__":
    main()
```

在 Code Editor 中用**运行**（<kbd>F5</kbd>）运行它并观察控制台。模板匹配不上时，先修裁剪，再调阈值。

## 故障排查

### 模板永远匹配不上

裁剪时的分辨率或 DPI 和设备现在运行的不同、裁剪包含了已经变化的背景，或者元素是动画的。对照截取时的设置检查模拟器的显示设置，裁得更紧，并试试 `threshold=0.7`。元素有几种样子时，每种都截下来，用 `find_any_object`。`mas.ImageNotFoundError` 表示这个 ID 根本不在你的图片库里；从**资源**面板重新复制。

### 匹配到错误的东西

模板太通用：一个普通箭头、一块单色方块、一个出现两次的词。改裁它旁边独特的东西，把阈值提高到 0.9，或者传入 `search_region` 让搜索留在元素所在的位置。`find_objects` 会显示模板得分超过阈值的每个位置，相似图案一眼就能看出来。

### 在一个实例上能用，另一个不行

第二个模拟器实例运行在另一种分辨率或 DPI。模板匹配基于像素，所以在 540x960 截取的模板在 720x1280 或不同的 DPI 下不会匹配。把每个实例设为相同的显示设置并重启。在云设备上，用你截取时的显示预设创建设备。如果实例必须不同，为每种分辨率截一套模板，用 `mas.get_screen_size()` 选择。

没有任何自动化工具是 100% 无风险的，请负责任地使用自动化，并自行斟酌决定。
