搜索

教程

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

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

  • Windows
  • Mac
  • 模拟器
  • 云手机
  • 手机
  • Studio
  • Python SDK
入门 更新于 阅读约 7 分钟
本页目录
  1. 为什么录制器不会分支
  2. 开始之前
  3. 在 Asset Lab 中截取模板
  4. 选择合适的调用
  5. 用 search_region 缩小搜索范围
  6. 调整阈值
  7. 用 find_any_object 处理弹窗
  8. 雷电模拟器脚本循环:等待某个状态
  9. 一个完整的蓝叠图像识别脚本
  10. 故障排查
  11. 模板永远匹配不上
  12. 匹配到错误的东西
  13. 在一个实例上能用,另一个不行

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

为什么录制器不会分支

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

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

开始之前

  • 已安装 MAS 并用试用或套餐登录。试用从免费下载开始;参见安装 MAS
  • BlueStacks 或 LDPlayer 正在运行、已开启 ADB,并已添加为设备。参见 BlueStacksLDPlayer 指南。
  • 模拟器使用一个你会一直保持的固定分辨率。各指南以竖屏 540x960、240 DPI 为基准。
  • Code Editor 中打开了一个代码型项目。参见快速开始

在 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

选择合适的调用

调用适用场景返回
find_object(image)你只想看一眼当前屏幕ObjectMatchNone
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,最后一次之后不再停顿。其他所有关键字(thresholdsearch_regionscreenshot)都会转发给 find_objectfind_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")

ObjectMatchxycentermatched_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 中用运行F5)运行它并观察控制台。模板匹配不上时,先修裁剪,再调阈值。

故障排查

模板永远匹配不上

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

匹配到错误的东西

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

在一个实例上能用,另一个不行

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

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

下一步

相关页面

这个页面有帮助吗?

有疑问? 在 Discord 提问