# AScript — 完整文档摘录 > Python 自动化全栈平台,覆盖 Android(免 Root)、iOS(免越狱)、Windows 三端。 > 官网 https://www.ascript.cn | 语言:简体中文 > > 本文件按 llms.txt 约定提供核心文档的完整正文,供大模型一次性摄取,无需逐页抓取。 > 索引见 https://www.ascript.cn/llms.txt --- # AScript 介绍 来源:https://www.ascript.cn/docs/intro/ # AScript 介绍 > **为每一个自动化想法,备好一切。** AScript 是一款 **Python 自动化全栈生态平台** —— 不只解决"写脚本",而是从第一行代码到最终用户手里的成品,整条链路全部备好。 --- ## 一句话定义 | 维度 | 内容 | |---|---| | **品类** | Python 自动化全栈生态(Python Automation Full-stack Ecosystem) | | **覆盖** | Android · iOS · Windows · AI 编程,全平台覆盖 | | **使命** | 你负责想法,我们负责一切就绪 | --- ## 三端统一覆盖 | 平台 | 关键能力 | |---|---| | 🤖 **Android** | **免 Root** 自动化,控件识别 / 图色 / OCR / HID 硬件外设全场景 | | 🍎 **iOS** | **免越狱、免签名**,支持 ESP32 蓝牙 HID 物理点击 | | 🪟 **Windows** | 实验版,桌面自动化与游戏脚本 | **统一 API,一套代码多端运行**,告别"安卓写一遍 / iOS 再写一遍"的重复劳动。 --- ## 核心能力速览 ### 🚀 上手层 · 零门槛开始 - **AI 自动编程**:一句话描述需求,代码自动生成、运行、调试,循环迭代直到跑通 - **可视化工具**:UI 生成器、逻辑编排,不会写代码也能做出专业级程序 - **极简 API**:一行代码完成点击、输入、滑动;Python 生态无缝扩展(NumPy / TensorFlow / OpenCV…) ### 🛠️ 能力层 · 专业工具就绪 - **云数据库**:免费、稳定、弹性伸缩 - **YOLO 训练**:AI 目标检测一键训练,游戏 / App 视觉识别全覆盖 - **HID 外设**:基于 ESP32 芯片的物理级触控,**最低 9 元起**,不可屏蔽 - **高精度 OCR**:文字识别、控件搜索、全分辨率识图 ### 🚢 生态层 · 商业交付闭环 - **独立打包**:Python 程序一键封装为原生 Android / iOS App - **分发系统**:基于 CDN 的高性能分发,扫码即装 - **卡密授权**:高并发激活码系统,商业化变现的基础设施 - **云控系统**:成百上千台设备集中管理,远程配置与任务下发 --- ## 谁在用 AScript | 用户群体 | 解决的痛点 | |---|---| | **企业级用户** | 流程自动化成本高、维护难 → AI 编程 + 云控集中管理 | | **工作室 / 开发者** | 缺乏后端、产品安全性低、分发困难 → 完整生态,专注业务逻辑 | | **个人开发者** | 编程基础薄弱 → AI 编程 + 可视化工具,零门槛上手 | | **游戏测试工程师** | 游戏无控件信息 → YOLO + 图色识别 + HID 物理触控 | | **App 测试工程师** | 多设备适配、回归重复 → 控件识别 + 云控批量管理 | | **自动化爱好者** | 工具配置太复杂 → 一句话生成代码,开箱即用 | --- ## 与传统自动化引擎的差异 | | 传统脚本引擎 | AScript | |---|---|---| | **覆盖环节** | 只解决"写脚本" | **AI 编程 → 调试 → 加密 → 打包 → 分发 → 卡密 → 云控** 全链路 | | **AI 能力** | 无 | 业内首个 Android · iOS · Windows **三端 AI 自动编程** | | **Python** | "类 Python" 简化语言 | **真正的 Python** 运行环境,Pip / PyCharm / VS Code 全兼容 | | **物理外设** | 无 | ESP32 HID 蓝牙 / OTG,物理级不可屏蔽 | | **多端代码复用** | 平台分裂 | 统一 API,一套代码多端运行 | --- ## 立即开始 按你的目标平台选择入门: - 🤖 [**Android 客户端**](/docs/android/intro) — 免 Root 自动化,开发者首选 - 🍎 [**iOS 接入**](/docs/ios/intro) — 免越狱免签名,配合 ESP32 蓝牙 HID - 🪟 [**Windows 实验版**](/docs/windows/intro) — 桌面自动化 - 🤖 [**AI 编程**](/ai-coding) — 自然语言一句话生成自动化脚本 或了解: - 💡 [**收费模式**](/docs/about/pricing) — AScript 永久免费 + 商业化按需付费 - 🌟 [**品牌定位**](/docs/about/brand) — 我们的使命与价值主张 - 🛒 [**官方商城**](/shop) — 会员套餐 / 游币充值 / HID 硬件 - 📞 [**联系我们**](/docs/about/contact) — QQ 群 / 抖音 / 邮箱 --- ## 一行代码体验 ```python import action # 安卓 / iOS / Windows 通用 — 一行点击 action.click(500, 800) # 找到"开始"按钮并点击 action.findText("开始").click() # 一句话调起 AI 编程,自动生成完整脚本 # 详见:/ai-coding ``` > 真正的 Python,真正的自动化。 --- # AI 万物识别 · Android 来源:https://www.ascript.cn/docs/android/api/screen/vlm/cloud/ # AI云识别 ```python from ascript.android import plug plug.load("ascript_ai") import ascript_ai as ai ``` > **本功能已抽取为独立插件** 为了让**老版本 AScript 也能用上**,AI 识别不再随主程序发布, 而是抽取成 `ascript_ai` 插件独立维护、独立更新。 所以使用前必须先 `plug.load("ascript_ai")` 把插件装载进来,之后 `import ascript_ai` 才能拿到模块。这一步是**一次性**的,脚本里写一次即可。 用**自然语言描述**在屏幕上找东西、问值、问页面状态,推理跑在 AScript AI Studio 云端。 不需要模板图、不需要固定文字、不需要训练模型 —— 直接用一句话描述你要什么。 > **使用场景** - 没有模板图、也没有固定文字,只能靠语义描述的目标:"那个红色的关闭按钮" - 界面改版频繁,写死的坐标和模板图天天失效 - 需要理解页面语义:"当前是不是登录页"、"列表里价格最低的那一项" - 从屏幕上提取结构化数据:把商品列表读成 `[{"name":..., "price":...}]` - 图色/OCR 都试过但认不出来的兜底方案 > **什么时候**不该**用** 能用 `FindImages.find()`(有模板图)或 `Ocr.find()`(有固定文字)解决的场景, 不要用AI云识别。那些是**毫秒级**且免费,这个是**秒级**且按量计费。 AI云识别是给"前两者做不到"的场景兜底的,不是用来替代它们的。 ## 准备工作 ### 1. 获取密钥 登录 [AScript AI Studio](https://ai.ascript.cn) → 账户中心 → 「API 调用」页面创建密钥。 密钥形如 `sk-as-` 开头的字符串,**明文只在创建时显示一次**,请立即保存。丢失只能重新创建。 > 每个账号最多 20 个密钥。密钥创建时会封存当时的登录凭证,如果账号中心让该凭证失效, 调用会返回 `credential_expired`,需要重新登录网页并重新创建密钥。 ### 2. 初始化 ```python from ascript.android import plug plug.load("ascript_ai") import ascript_ai as ai ai.init(api_key="sk-as-xxxxxxxx") ``` 也可以设置环境变量 `ASCRIPT_AI_KEY`,这样脚本里连 `init()` 都可以省掉。 ## 快速开始 ### 1. 第一个脚本 ```python from ascript.android import plug plug.load("ascript_ai") import ascript_ai as ai ai.init(api_key="sk-as-xxxxxxxx") r = ai.find("右上角的购物车图标") print(r) # {'text': '购物车', 'rect': [960, 120, 1040, 200], # 'center_x': 1000, 'center_y': 160, 'confidence': 1.0} ``` `find()` 的返回结构与 `ascript.android.screen.Ocr.find()` **完全一致**, Ocr 认不出来的时候可以直接换一行过来。 设了环境变量 `ASCRIPT_AI_KEY` 的话,连 `ai.init()` 这行都可以省掉。 ### 2. 找到并点击 最常用的一句。`click()` 内部会自己截屏、定位、点中心点,找不到返回 `False`。 ```python ai.click("底部的立即购买按钮") # 点不到时要有兜底,别默认它一定成功 if not ai.click("同意并继续"): print("没找到那个按钮,换个描述试试") ``` 想自己控制点击方式(长按、拖拽),就用 `find()` 拿坐标: ```python from ascript.android import action r = ai.find("列表里第一个商品的缩略图") if r: action.click(r["center_x"], r["center_y"], 800) # 长按 800ms ``` ### 3. 判断页面状态 ```python # 在不在 if ai.exists("登录按钮"): ai.click("登录按钮") # 是不是(返回真正的 bool,不是字符串) if ai.ask_value("当前是不是支付成功页", value_type=bool): print("下单完成") # 让模型用自己的话描述(原文,只适合打印给人看) print(ai.ask("当前是什么页面,用户在做什么")) ``` > `ask()` 的返回是模型原文,格式会飘,**不要拿去做 `if` 判断**。 判断一律走 `exists()` 或 `ask_value(..., value_type=bool)`。 ### 4. 从屏幕上取数据 `ask_value()` 用 **Python 类型**声明你要什么(参数名 `value_type`),返回值就是那个类型: ```python count = ai.ask_value("购物车里有几件商品", value_type=int) # -> 3 total = ai.ask_value("订单总金额是多少", value_type=float) # -> 128.5 title = ai.ask_value("标题栏写的什么", value_type=str) # -> '订单确认' paid = ai.ask_value("是否已经支付", value_type=bool) # -> False ``` 多个值用 `[T]`: ```python names = ai.ask_value("所有商品名称", value_type=[str]) # -> ['面包', '牛奶'] prices = ai.ask_value("每件商品的价格", value_type=[float]) # -> [12.5, 8.0] ``` **返回 `None` 表示模型说它答不出来**,要判一下再用: ```python n = ai.ask_value("有几条未读消息", value_type=int) if n is None: print("看不出来") # 图里没有相关信息 elif n == 0: print("一条都没有") # 计数为零是真实答案,和上面不是一回事 else: print("有 %d 条" % n) ``` ### 5. 把一张列表读成结构化数据 用带字段名的 dict 声明记录,字段名会一并告诉模型,它才好对号入座: ```python items = ai.ask_value("列表里所有商品", value_type=[{"name": str, "price": float}]) # -> [{'name': '面包', 'price': 12.5}, # {'name': '牛奶', 'price': 8.0}] for it in items or []: print(it["name"], it["price"]) ``` 单条记录就不加外面那层 `[]`: ```python info = ai.ask_value("这个商品的信息", value_type={"title": str, "price": float, "stock": int}) # -> {'title': '面包', 'price': 12.5, 'stock': 30} ``` ### 6. 等待页面变化 ```python r = ai.wait("加载完成的商品列表", timeout=20) if r is None: print("等超时了") ``` > 每轮都是一次完整的云端推理(几秒)**并且都要计费**,`timeout=20` 大概只够跑 三五轮。不要图省事写 `timeout=300`。 ### 7. 只看一块区域:又快又省 `rect=[left, top, right, bottom]`。**这是最值得养成的习惯** —— 裁剪比降采样保真得多,图更小、更快、更省钱,而返回的仍然是**完整屏幕坐标**, 不用你自己加偏移。 ```python # 只看底部 1/4 屏 ai.find("确定按钮", rect=[0, 1800, 1080, 2400]) # 只看顶部状态栏 ai.ask_value("现在电量百分之多少", value_type=int, rect=[0, 0, 1080, 100]) ``` ### 8. 一次截屏,多次提问 默认每次调用都会自动截一次屏。同一个画面要问好几件事时,自己截一次复用: ```python from ascript.android import screen shot = screen.capture_cv() total = ai.ask_value("总价", value_type=float, image=shot) count = ai.ask_value("商品件数", value_type=int, image=shot) addr = ai.ask_value("收货地址", value_type=str, image=shot) ``` `image=` 还接受图片路径、`Bitmap`、`PIL.Image`,见[下文](#image用你自己的图); `capture_cv()` / `capture_pil()` / `capture_bitmap()` 的说明见[屏幕图像 · 截图快捷方法](../screen.md#截图快捷方法)。 ### 9. 描述不准时,用 hint 补充上下文 ```python ai.find("确定按钮", hint="在底部弹窗里,不是顶部导航栏那个") ai.ask_value("图中算式的结果", value_type=int, hint="只算红框里那一道") ``` 小目标看不清时再考虑调清晰度(但**优先试 `rect`**): ```python ai.find("底部那个很小的图标", image_tokens=2048) ``` ### 10. 给找图 / OCR 做兜底 推荐的用法不是"全用 AI",而是**快的先上,认不出来再交给 AI**: ```python from ascript.android.screen import Ocr r = Ocr.find("立即购买") # 毫秒级,免费 if r is None: r = ai.find("底部的立即购买按钮") # 秒级,计费,但认得出语义 if r: action.click(r["center_x"], r["center_y"]) ``` 两者返回结构一致,所以后面的代码不用分叉。 ### 11. 完整示例:自动下单 ```python from ascript.android import action, screen, plug plug.load("ascript_ai") import ascript_ai as ai ai.init(api_key="sk-as-xxxxxxxx") BOTTOM = [0, 1700, 1080, 2400] # 操作区基本都在下半屏,固定裁这块 try: # 1) 确认在商品详情页 if not ai.ask_value("当前是不是商品详情页", value_type=bool): raise SystemExit("页面不对,先手动进详情页") # 2) 记下价格,超预算就不买 price = ai.ask_value("这个商品的价格", value_type=float) if price is None or price > 200: raise SystemExit("价格不合适:%s" % price) # 3) 走结算流程 if not ai.click("立即购买", rect=BOTTOM): raise SystemExit("没找到立即购买") if ai.wait("确认订单页面的提交订单按钮", timeout=20) is None: raise SystemExit("确认订单页没出来") # 4) 提交前核对总价(一次截屏问两件事) shot = screen.capture_cv() total = ai.ask_value("订单总金额", value_type=float, image=shot) addr = ai.ask_value("收货地址", value_type=str, image=shot) print("总价 %s,寄到 %s" % (total, addr)) ai.click("提交订单", rect=BOTTOM) except ai.NotReadyError: print("没配密钥,去 AI Studio 账户中心创建") except ai.InsufficientBalanceError: print("余额不足,请充值") except ai.AsaiError as e: print("调用失败:%s" % e) ``` ## 方法总览 按**返回什么**分成两族: | 方法 | 返回 | | --- | --- | | `find(target)` | `dict` / `None`,含标签、框、中心点 | | `find_all(target, limit)` | `list[dict]` | | `ask(question)` | `str`,模型原文 | | `ask_value(question, value_type)` | 由 `value_type` 决定,见下文 | | `exists(target)` | `bool` | | `wait(target, timeout)` | `dict` / `None` | | `click(target)` | `bool` | | `init()` / `status()` | 配置 | ## 位置:find / find_all ### find_all 按自然语言描述找出所有匹配目标,返回**屏幕坐标**。 - 函数 ```python ai.find_all(target, rect=None, image=None, limit=10, confidence=0.0, image_tokens=None, hint=None) ``` - 参数 | 参数 | 类型 | 是否必填 | 说明 | | --- | --- | --- | --- | | target | str | 是 | 目标描述,越具体越准。"蓝色的登录按钮" 好过 "按钮" | | rect | list | 否 | `[left, top, right, bottom]` 限定搜索区域,**强烈建议传** | | image | - | 否 | `None` = 自动截屏;也可传 `ndarray` / 图片路径 / `Bitmap` / `PIL.Image` | | limit | int | 否 | 最多返回几个,默认 10 | | confidence | float | 否 | 置信度过滤,默认 0 不过滤 | | image_tokens | int | 否 | 上传图片的清晰度预算,默认 1024,见下文 | | hint | str | 否 | 补充上下文 | - 返回 `list[dict]`,每项形如: ```python {'text': '购物车', 'rect': [960, 120, 1040, 200], 'center_x': 1000, 'center_y': 160, 'confidence': 1.0} ``` 空列表表示没找到。 - 示例 ```python items = ai.find_all("商品列表里的加入购物车按钮", limit=5) for it in items: print(it["text"], it["center_x"], it["center_y"]) ``` ### find 同 `find_all`,但只返回可能性最高的一个,没找到返回 `None`。 ```python ai.find(target, rect=None, image=None, confidence=0.0, image_tokens=None, hint=None) ``` ```python r = ai.find("确定按钮", rect=[0, 1800, 1080, 2400]) if r: action.click(r["center_x"], r["center_y"]) ``` ## 值:ask / ask_value ### ask 开放问答,返回**模型原文**。 ```python ai.ask(question, rect=None, image=None, image_tokens=None, hint=None) ``` ```python print(ai.ask("当前是什么页面,用户在做什么")) # '这是一个商品详情页,用户正在浏览一款蓝色的运动鞋……' ``` > `ask()` 的原文是**给人看的**,格式会飘,程序解析不可靠。 要拿去做判断的,一律用 `ask_value()`。 ### ask_value 带类型地问一个值。**返回值的类型由 `value_type` 决定。** ```python ai.ask_value(question, value_type=str, rect=None, image=None, image_tokens=None, hint=None) ``` - 参数 | 参数 | 类型 | 是否必填 | 说明 | | --- | --- | --- | --- | | question | str | 是 | 要问的问题 | | value_type | - | 否 | 想要什么类型,默认 `str`。见下文「类型写法一览」 | | rect | list | 否 | `[left, top, right, bottom]` 限定区域,**强烈建议传** | | image | - | 否 | `None` = 自动截屏;也可传 `ndarray` / 图片路径 / `Bitmap` / `PIL.Image` | | image_tokens | int | 否 | 上传图片的清晰度预算,默认 1024 | | hint | str | 否 | 补充上下文 | - 基本类型 ```python ai.ask_value("有几个面包", value_type=int) # -> 3 ai.ask_value("总价是多少", value_type=float) # -> 128.5 ai.ask_value("是不是登录页", value_type=bool) # -> True ai.ask_value("标题栏写的什么", value_type=str) # -> '设置' ``` - 多个值:用 `[T]` ```python ai.ask_value("每件商品的价格", value_type=[float]) # -> [12.5, 8.0, 30.0] ai.ask_value("所有商品名称", value_type=[str]) # -> ['面包', '牛奶'] ``` - 成对的记录:用带字段名的 dict 字段名会一并告诉模型,它才好对号入座。 ```python ai.ask_value("这个商品的信息", value_type={"title": str, "price": float, "stock": int}) # -> {'title': '面包', 'price': 12.5, 'stock': 30} ai.ask_value("所有联系人", value_type=[{"name": str, "phone": str}]) # -> [{'name': '张三', 'phone': '138-0013-8000'}, ...] ``` 某条记录缺某个字段时那个字段是 `None`,整条不会被丢掉。 - 类型写法一览 | 写法 | 含义 | | --- | --- | | `int` / `float` | 单个数 | | `bool` / `str` | 单个布尔 / 字符串 | | `[str]` | 多个字符串 | | `{"name": str, "phone": str}` | 一条记录 | | `[{"name": str, "age": int}]` | 多条记录 | > **不要写 `list[float]`** 设备上是 **Python 3.8**,内建泛型下标要 3.9+,写了会在真机上抛 `TypeError: 'type' object is not subscriptable`。 用 `[float]`,或 `typing.List[float]`。 > **`int` 和 `float` 不要混用** 类型约束是在**解码层**真实生效的:问 "12.5 加 8" 时声明 `int` 会返回 `None` (答不出整数就如实拒答),而**不是**悄悄取整成 20。 计数、下标用 `int`;价格、比例、度量用 `float`。 - 关于返回 `None` **`None` 表示模型说它答不出来**(图里没有相关信息),不是解析失败 —— 解析失败会抛 `AsaiError`。这两件事分开,脚本才能正确处理"没有"这种情况。 ```python n = ai.ask_value("有几台冰箱", value_type=int) if n is None: print("模型看不出来") elif n == 0: print("确实一台都没有") # 计数为零是真实答案,和上面不是一回事 ``` > **问坐标一定要用 find / find_all** 不要用 `ask_value` 问坐标。服务端只看得到降采样后的那张图,它说出来的坐标在 **那张图**的像素空间里;只有 `find*` 这条路会把坐标换算回屏幕坐标。 用 `ask_value` 问坐标,拿到的数字直接点会**点空**。要点击点就用 `ai.click(target)`, 或 `ai.find(target)` 取 `center_x` / `center_y`。 ## 判断与动作 ### exists 目标在不在。等价于 `find()` 是否为 `None`,花费也一样。 ```python ai.exists(target, rect=None, image=None, confidence=0.0, hint=None) ``` ```python if ai.exists("弹窗上的关闭按钮"): ai.click("弹窗上的关闭按钮") ``` ### wait 等目标出现,返回结果或 `None`。 ```python ai.wait(target, timeout=30, interval=0.0, rect=None, confidence=0.0, hint=None) ``` ```python r = ai.wait("加载完成的商品列表", timeout=20) ``` > 每轮都是一次完整的云端推理(几秒)**并且都要计费**,所以 `timeout` 设小一点。 `interval` 默认 0 —— 推理本身就是最大的间隔了。 `wait()` 刻意**没有 `image` 参数**:每轮重新截屏才是「等」的意义。 ### click 找到目标并点击它的中心点。找不到返回 `False`。 ```python ai.click(target, timeout=0, rect=None, image=None, confidence=0.0, hint=None) ``` ```python ai.click("右上角的关闭按钮") ai.click("提交订单", timeout=15) # 最多等 15 秒 ``` > 传了 `image` 时 `timeout` 无效 —— 静态图不会变,等待没有意义。 而且那张图**必须对应当前屏幕**,否则算出来的坐标会点错地方。 ## 公共参数 ### rect:限定区域(强烈建议传) ```python ai.find("确定按钮", rect=[0, 1800, 1080, 2400]) ``` `rect=[left, top, right, bottom]`。**裁剪比降采样保真得多**,图更小、更快、更省钱。 传了 `rect` 时坐标仍然返回**完整的屏幕坐标**,不需要你自己加偏移。 ### image:用你自己的图 除 `wait()` 外,所有方法都接受 `image=`。 | 传入 | 说明 | | --- | --- | | `None`(默认) | 自动截取当前全屏 | | `ndarray` | cv2 读出来的、或你自己处理过的图(BGR) | | `str` | 图片文件路径 | | Android `Bitmap` | `screen.capture_bitmap()` 的结果 | | `PIL.Image` | `screen.capture_pil()` 的结果 | 截一次、多次查询复用,能省下截屏开销: ```python from ascript.android import screen shot = screen.capture_cv() price = ai.ask_value("总价", value_type=float, image=shot) count = ai.ask_value("商品件数", value_type=int, image=shot) ``` ### image_tokens:上传图片的清晰度 控制**上传图片的清晰度**(视觉 token 预算),默认 1024。 > 它管的是**输入的图**,不是输出长度 —— 和 OpenAI 那个 `max_tokens` 是两回事。 视觉模型按 32×32 像素算一个 token,所以一张 1080×2400 的全屏图原本是 2550 个 token, 默认会被等比压到 1024: | image_tokens | 上传尺寸 | 占原图 | | --- | --- | --- | | 256 | 352×768 | 10% | | 512 | 480×1088 | 20% | | **1024(默认)** | **672×1536** | **40%** | | 2048 | 960×2144 | 79% | ```python ai.find("底部那个很小的图标", image_tokens=2048) # 小目标看不清时调大 ai.find("屏幕中间的大按钮", image_tokens=512) # 大目标够用了,省钱 ``` > **优先考虑 rect,不是调这个** 同样是减少 token,`rect` 是"只看这块但看得清",降采样是"整屏都看但都变糊"。 ### hint:补充上下文 所有方法都接受可选的 `hint=`: ```python ai.find("确定按钮", hint="在底部弹窗里,不是顶部导航栏那个") ai.ask_value("图中算式的结果", value_type=int, hint="只算红框里那一道") ``` `hint` 只补充"要找什么",输出格式仍由服务端决定 —— 这样服务端换模型时你的脚本不用改。 ## 其它 ### init ```python ai.init(api_key=None, base_url=None, timeout=None, coord_space=None) ``` | 参数 | 类型 | 是否必填 | 说明 | | --- | --- | --- | --- | | api_key | str | 否 | `sk-as-` 开头的密钥。不传则读环境变量 `ASCRIPT_AI_KEY` | | base_url | str | 否 | 自建/测试环境才需要传 | | timeout | int | 否 | 单次请求超时秒数,默认 60 | | coord_space | str | 否 | 极少用。`"abs"` / `"norm1000"` / `"ratio"`,见下 | `coord_space` 默认自动判定模型输出的坐标空间。已知有一个信息论上的死角: 模型吐 0~1000 归一化、目标又恰好在左上角时,单看一帧无法区分。 **真遇到系统性偏移**时才手动钉死: ```python ai.init(api_key="sk-as-...", coord_space="norm1000") ``` ### status 当前配置状态,排查问题时用。 ```python ai.status() # {'configured': True, 'base_url': 'https://ai.ascript.cn/v1', 'coord_space': 'auto'} ``` ### 异常 所有异常都挂在模块上,用 `ai.XXX` 取,不需要单独 import。 它们**全部继承自 `ai.AsaiError`**,只想粗粒度兜底的话捕获它一个就够。 | 异常 | 含义 | 该怎么处理 | | --- | --- | --- | | `NotReadyError` | 还没配密钥,请求根本没发出去 | 先 `init()` 或设 `ASCRIPT_AI_KEY` | | `AuthenticationError` | 密钥无效或已被删除 | 去账户中心重新创建 | | `CredentialExpiredError` | 密钥还在,但它绑定的登录状态失效了 | 先重新登录网页,再重新创建密钥 | | `InsufficientBalanceError` | 余额不足 | 提示用户充值,重试没用 | | `InvalidRequestError` | 参数有问题 | 改代码,重试没用 | | `RateLimitError` | 调用太频繁 | 等 `e.retry_after` 秒后再试 | | `ServiceUnavailableError` | 服务端暂时不可用 | 可以稍后重试 | | `UpstreamError` | 上游模型服务故障(服务端已自动重试过一次) | 稍后重试 | | `NetworkError` | 连不上服务端 | 检查网络 | | `ProtocolError` | 服务端返回了读不懂的内容 | 正常不该发生,报 `request_id` 给我们 | `CredentialExpiredError` 继承自 `AuthenticationError`,所以 `except ai.AuthenticationError` 能把两者一起兜住。 每个异常都带这几个属性,排查时有用: | 属性 | 说明 | | --- | --- | | `message` | 给人看的说明(服务端返回的中文原文) | | `type` | 机器可读的错误类型,如 `"insufficient_balance"` | | `status_code` | HTTP 状态码,本地错误时为 `None` | | `request_id` | 服务端请求 ID,**找我们排查时报这个** | ```python from ascript.android import plug plug.load("ascript_ai") import ascript_ai as ai import time try: r = ai.find("登录按钮") except ai.NotReadyError: print("还没配密钥") except ai.InsufficientBalanceError: print("余额不足,请充值") # 重试没用,直接退出 except ai.RateLimitError as e: time.sleep(e.retry_after or 5) # 服务端建议的等待秒数 r = ai.find("登录按钮") except ai.AuthenticationError: print("密钥有问题,去账户中心重新创建") # 顺带兜住 CredentialExpiredError except ai.AsaiError as e: print("调用失败:%s (request_id=%s)" % (e.message, e.request_id)) ``` > 写脚本时至少把 `InsufficientBalanceError` 单独拎出来 —— 余额不足和网络抖动看起来都是"调用失败",但一个该停、一个该重试, 混在一起兜会让脚本在没钱的情况下疯狂空转重试。 ## 计费 按实际 Token 用量从 AI Studio 账户余额扣费,与网页对话走同一套计费和日志链路。 每次调用都能在账户中心的「使用日志」里查到明细。 **`rect=` 传得越小,图越小,越省。** ## 关于端侧视觉 > [端侧视觉](./local.md)(在手机本地跑视觉模型)**目前不开放使用** —— 本地小模型单次推理 15~30 秒、输出精度不足以支撑业务。 Android 上的 AI 识别请统一使用本页的 AI云识别。 --- # AI 万物识别 · iOS 来源:https://www.ascript.cn/docs/ios/api/screen/vlm/cloud/ # AI云识别 > **需要 AScript iOS **4002 及以上**版本** 低于 4002 的版本里没有这个模块,`from ascript.ios.vlm import ascript_ai` 会直接抛 `ModuleNotFoundError`。**请先把 AScript 升级到 4002 或更新的版本。** ```python from ascript.ios.vlm import ascript_ai as ai ``` > **iOS 是内置的,不需要装插件** iOS 不支持加载插件,所以 AI 识别**直接内置在 AScript 中**,导入即用。 Android 那边为了兼容老版本,同一套能力是以 `ascript_ai` 插件的形式提供的, 要先 `plug.load("ascript_ai")`。**两端的功能和参数完全一致,只有导入方式不同**, 跨端移植脚本时改这一行即可。 用**自然语言描述**在屏幕上找东西、问值、问页面状态,推理跑在 AScript AI Studio 云端。 不需要模板图、不需要固定文字、不需要训练模型 —— 直接用一句话描述你要什么。 > **使用场景** - 没有模板图、也没有固定文字,只能靠语义描述的目标:"那个红色的关闭按钮" - 界面改版频繁,写死的坐标和模板图天天失效 - 需要理解页面语义:"当前是不是登录页"、"列表里价格最低的那一项" - 从屏幕上提取结构化数据:把商品列表读成 `[{"name":..., "price":...}]` - 图色/OCR 都试过但认不出来的兜底方案 > **什么时候**不该**用** 能用 `FindImages.find()`(有模板图)或 `Ocr.paddleocr()`(有固定文字)解决的场景, 不要用AI云识别。那些是**毫秒级**且免费,这个是**秒级**且按量计费。 AI云识别是给"前两者做不到"的场景兜底的,不是用来替代它们的。 ## 准备工作 ### 1. 获取密钥 登录 [AScript AI Studio](https://ai.ascript.cn) → 账户中心 → 「API 调用」页面创建密钥。 密钥形如 `sk-as-` 开头的字符串,**明文只在创建时显示一次**,请立即保存。丢失只能重新创建。 > 每个账号最多 20 个密钥。密钥创建时会封存当时的登录凭证,如果账号中心让该凭证失效, 调用会返回 `credential_expired`,需要重新登录网页并重新创建密钥。 ### 2. 初始化 ```python from ascript.ios.vlm import ascript_ai as ai ai.init(api_key="sk-as-xxxxxxxx") ``` 也可以设置环境变量 `ASCRIPT_AI_KEY`,这样脚本里连 `init()` 都可以省掉。 ## 快速开始 ### 1. 第一个脚本 ```python from ascript.ios.vlm import ascript_ai as ai ai.init(api_key="sk-as-xxxxxxxx") r = ai.find("右上角的购物车图标") print(r) # {'text': '购物车', 'rect': [960, 120, 1040, 200], # 'center_x': 1000, 'center_y': 160, 'confidence': 1.0} ``` `find()` 的返回结构与 `Ocr.paddleocr()` 的每一项**完全一致** (`text` / `rect` / `center_x` / `center_y` / `confidence`), OCR 认不出来的时候可以直接换过来用。 设了环境变量 `ASCRIPT_AI_KEY` 的话,连 `ai.init()` 这行都可以省掉。 ### 2. 找到并点击 最常用的一句。`click()` 内部会自己截屏、定位、点中心点,找不到返回 `False`。 ```python ai.click("底部的立即购买按钮") # 点不到时要有兜底,别默认它一定成功 if not ai.click("同意并继续"): print("没找到那个按钮,换个描述试试") ``` 想自己控制点击方式(长按、拖拽),就用 `find()` 拿坐标: ```python from ascript.ios import action r = ai.find("列表里第一个商品的缩略图") if r: action.click(r["center_x"], r["center_y"], 800) # 长按 800ms ``` ### 3. 判断页面状态 ```python # 在不在 if ai.exists("登录按钮"): ai.click("登录按钮") # 是不是(返回真正的 bool,不是字符串) if ai.ask_value("当前是不是支付成功页", value_type=bool): print("下单完成") # 让模型用自己的话描述(原文,只适合打印给人看) print(ai.ask("当前是什么页面,用户在做什么")) ``` > `ask()` 的返回是模型原文,格式会飘,**不要拿去做 `if` 判断**。 判断一律走 `exists()` 或 `ask_value(..., value_type=bool)`。 ### 4. 从屏幕上取数据 `ask_value()` 用 **Python 类型**声明你要什么(参数名 `value_type`),返回值就是那个类型: ```python count = ai.ask_value("购物车里有几件商品", value_type=int) # -> 3 total = ai.ask_value("订单总金额是多少", value_type=float) # -> 128.5 title = ai.ask_value("标题栏写的什么", value_type=str) # -> '订单确认' paid = ai.ask_value("是否已经支付", value_type=bool) # -> False ``` 多个值用 `[T]`: ```python names = ai.ask_value("所有商品名称", value_type=[str]) # -> ['面包', '牛奶'] prices = ai.ask_value("每件商品的价格", value_type=[float]) # -> [12.5, 8.0] ``` **返回 `None` 表示模型说它答不出来**,要判一下再用: ```python n = ai.ask_value("有几条未读消息", value_type=int) if n is None: print("看不出来") # 图里没有相关信息 elif n == 0: print("一条都没有") # 计数为零是真实答案,和上面不是一回事 else: print("有 %d 条" % n) ``` ### 5. 把一张列表读成结构化数据 用带字段名的 dict 声明记录,字段名会一并告诉模型,它才好对号入座: ```python items = ai.ask_value("列表里所有商品", value_type=[{"name": str, "price": float}]) # -> [{'name': '面包', 'price': 12.5}, # {'name': '牛奶', 'price': 8.0}] for it in items or []: print(it["name"], it["price"]) ``` 单条记录就不加外面那层 `[]`: ```python info = ai.ask_value("这个商品的信息", value_type={"title": str, "price": float, "stock": int}) # -> {'title': '面包', 'price': 12.5, 'stock': 30} ``` ### 6. 等待页面变化 ```python r = ai.wait("加载完成的商品列表", timeout=20) if r is None: print("等超时了") ``` > 每轮都是一次完整的云端推理(几秒)**并且都要计费**,`timeout=20` 大概只够跑 三五轮。不要图省事写 `timeout=300`。 ### 7. 只看一块区域:又快又省 `rect=[left, top, right, bottom]`。**这是最值得养成的习惯** —— 裁剪比降采样保真得多,图更小、更快、更省钱,而返回的仍然是**完整屏幕坐标**, 不用你自己加偏移。 ```python # 只看底部 1/4 屏 ai.find("确定按钮", rect=[0, 1800, 1080, 2400]) # 只看顶部状态栏 ai.ask_value("现在电量百分之多少", value_type=int, rect=[0, 0, 1080, 100]) ``` ### 8. 一次截屏,多次提问 默认每次调用都会自动截一次屏。同一个画面要问好几件事时,自己截一次复用: ```python from ascript.ios import screen shot = screen.capture() # 返回 PIL.Image,可直接传给 ai total = ai.ask_value("总价", value_type=float, image=shot) count = ai.ask_value("商品件数", value_type=int, image=shot) addr = ai.ask_value("收货地址", value_type=str, image=shot) ``` `image=` 还接受 `ndarray` 和图片路径,见[下文](#image用你自己的图)。 > iOS 还有个更省事的办法:`screen.cache(True)` 开启截图缓存后, 后续的 `capture()` / 找图 / 找色都复用同一张截图,不用自己传来传去。 用完记得 `screen.cache(False)` 关掉,否则画面变了还在用旧图。 ### 9. 描述不准时,用 hint 补充上下文 ```python ai.find("确定按钮", hint="在底部弹窗里,不是顶部导航栏那个") ai.ask_value("图中算式的结果", value_type=int, hint="只算红框里那一道") ``` 小目标看不清时再考虑调清晰度(但**优先试 `rect`**): ```python ai.find("底部那个很小的图标", image_tokens=2048) ``` ### 10. 给找图 / OCR 做兜底 推荐的用法不是"全用 AI",而是**快的先上,认不出来再交给 AI**: ```python from ascript.ios.screen import Ocr hits = Ocr.paddleocr(pattern="立即购买") # 毫秒级,免费,返回 list r = hits[0] if hits else None if r is None: r = ai.find("底部的立即购买按钮") # 秒级,计费,但认得出语义 if r: action.click(r["center_x"], r["center_y"]) ``` `Ocr.paddleocr()` 返回的是**列表**(`ai.find()` 返回单个 dict 或 `None`), 取到第一项之后两者结构一致,后面的代码不用分叉。 ### 11. 完整示例:自动下单 ```python from ascript.ios import action, screen from ascript.ios.vlm import ascript_ai as ai ai.init(api_key="sk-as-xxxxxxxx") BOTTOM = [0, 1700, 1080, 2400] # 操作区基本都在下半屏,固定裁这块 try: # 1) 确认在商品详情页 if not ai.ask_value("当前是不是商品详情页", value_type=bool): raise SystemExit("页面不对,先手动进详情页") # 2) 记下价格,超预算就不买 price = ai.ask_value("这个商品的价格", value_type=float) if price is None or price > 200: raise SystemExit("价格不合适:%s" % price) # 3) 走结算流程 if not ai.click("立即购买", rect=BOTTOM): raise SystemExit("没找到立即购买") if ai.wait("确认订单页面的提交订单按钮", timeout=20) is None: raise SystemExit("确认订单页没出来") # 4) 提交前核对总价(一次截屏问两件事) shot = screen.capture() total = ai.ask_value("订单总金额", value_type=float, image=shot) addr = ai.ask_value("收货地址", value_type=str, image=shot) print("总价 %s,寄到 %s" % (total, addr)) ai.click("提交订单", rect=BOTTOM) except ai.NotReadyError: print("没配密钥,去 AI Studio 账户中心创建") except ai.InsufficientBalanceError: print("余额不足,请充值") except ai.AsaiError as e: print("调用失败:%s" % e) ``` ## 方法总览 按**返回什么**分成两族: | 方法 | 返回 | | --- | --- | | `find(target)` | `dict` / `None`,含标签、框、中心点 | | `find_all(target, limit)` | `list[dict]` | | `ask(question)` | `str`,模型原文 | | `ask_value(question, value_type)` | 由 `value_type` 决定,见下文 | | `exists(target)` | `bool` | | `wait(target, timeout)` | `dict` / `None` | | `click(target)` | `bool` | | `init()` / `status()` | 配置 | ## 位置:find / find_all ### find_all 按自然语言描述找出所有匹配目标,返回**屏幕坐标**。 - 函数 ```python ai.find_all(target, rect=None, image=None, limit=10, confidence=0.0, image_tokens=None, hint=None) ``` - 参数 | 参数 | 类型 | 是否必填 | 说明 | | --- | --- | --- | --- | | target | str | 是 | 目标描述,越具体越准。"蓝色的登录按钮" 好过 "按钮" | | rect | list | 否 | `[left, top, right, bottom]` 限定搜索区域,**强烈建议传** | | image | - | 否 | `None` = 自动截屏;也可传 `ndarray` / 图片路径 / `Bitmap` / `PIL.Image` | | limit | int | 否 | 最多返回几个,默认 10 | | confidence | float | 否 | 置信度过滤,默认 0 不过滤 | | image_tokens | int | 否 | 上传图片的清晰度预算,默认 1024,见下文 | | hint | str | 否 | 补充上下文 | - 返回 `list[dict]`,每项形如: ```python {'text': '购物车', 'rect': [960, 120, 1040, 200], 'center_x': 1000, 'center_y': 160, 'confidence': 1.0} ``` 空列表表示没找到。 - 示例 ```python items = ai.find_all("商品列表里的加入购物车按钮", limit=5) for it in items: print(it["text"], it["center_x"], it["center_y"]) ``` ### find 同 `find_all`,但只返回可能性最高的一个,没找到返回 `None`。 ```python ai.find(target, rect=None, image=None, confidence=0.0, image_tokens=None, hint=None) ``` ```python r = ai.find("确定按钮", rect=[0, 1800, 1080, 2400]) if r: action.click(r["center_x"], r["center_y"]) ``` ## 值:ask / ask_value ### ask 开放问答,返回**模型原文**。 ```python ai.ask(question, rect=None, image=None, image_tokens=None, hint=None) ``` ```python print(ai.ask("当前是什么页面,用户在做什么")) # '这是一个商品详情页,用户正在浏览一款蓝色的运动鞋……' ``` > `ask()` 的原文是**给人看的**,格式会飘,程序解析不可靠。 要拿去做判断的,一律用 `ask_value()`。 ### ask_value 带类型地问一个值。**返回值的类型由 `value_type` 决定。** ```python ai.ask_value(question, value_type=str, rect=None, image=None, image_tokens=None, hint=None) ``` - 参数 | 参数 | 类型 | 是否必填 | 说明 | | --- | --- | --- | --- | | question | str | 是 | 要问的问题 | | value_type | - | 否 | 想要什么类型,默认 `str`。见下文「类型写法一览」 | | rect | list | 否 | `[left, top, right, bottom]` 限定区域,**强烈建议传** | | image | - | 否 | `None` = 自动截屏;也可传 `ndarray` / 图片路径 / `Bitmap` / `PIL.Image` | | image_tokens | int | 否 | 上传图片的清晰度预算,默认 1024 | | hint | str | 否 | 补充上下文 | - 基本类型 ```python ai.ask_value("有几个面包", value_type=int) # -> 3 ai.ask_value("总价是多少", value_type=float) # -> 128.5 ai.ask_value("是不是登录页", value_type=bool) # -> True ai.ask_value("标题栏写的什么", value_type=str) # -> '设置' ``` - 多个值:用 `[T]` ```python ai.ask_value("每件商品的价格", value_type=[float]) # -> [12.5, 8.0, 30.0] ai.ask_value("所有商品名称", value_type=[str]) # -> ['面包', '牛奶'] ``` - 成对的记录:用带字段名的 dict 字段名会一并告诉模型,它才好对号入座。 ```python ai.ask_value("这个商品的信息", value_type={"title": str, "price": float, "stock": int}) # -> {'title': '面包', 'price': 12.5, 'stock': 30} ai.ask_value("所有联系人", value_type=[{"name": str, "phone": str}]) # -> [{'name': '张三', 'phone': '138-0013-8000'}, ...] ``` 某条记录缺某个字段时那个字段是 `None`,整条不会被丢掉。 - 类型写法一览 | 写法 | 含义 | | --- | --- | | `int` / `float` | 单个数 | | `bool` / `str` | 单个布尔 / 字符串 | | `[str]` | 多个字符串 | | `{"name": str, "phone": str}` | 一条记录 | | `[{"name": str, "age": int}]` | 多条记录 | > **三种复数写法都能用** AScript iOS 内嵌的是 **Python 3.9**,所以下面三种写法等价: ```python ai.ask_value("每件商品的价格", value_type=[float]) # 推荐 ai.ask_value("每件商品的价格", value_type=list[float]) ai.ask_value("每件商品的价格", value_type=typing.List[float]) ``` **推荐用 `[float]`。** 不是因为另外两种在 iOS 上有问题,而是 **Android 端是 Python 3.8,写 `list[float]` 会在真机上抛 `TypeError: 'type' object is not subscriptable`**。用 `[T]` 的脚本两端都能跑。 > **`int` 和 `float` 不要混用** 类型约束是在**解码层**真实生效的:问 "12.5 加 8" 时声明 `int` 会返回 `None` (答不出整数就如实拒答),而**不是**悄悄取整成 20。 计数、下标用 `int`;价格、比例、度量用 `float`。 - 关于返回 `None` **`None` 表示模型说它答不出来**(图里没有相关信息),不是解析失败 —— 解析失败会抛 `AsaiError`。这两件事分开,脚本才能正确处理"没有"这种情况。 ```python n = ai.ask_value("有几台冰箱", value_type=int) if n is None: print("模型看不出来") elif n == 0: print("确实一台都没有") # 计数为零是真实答案,和上面不是一回事 ``` > **问坐标一定要用 find / find_all** 不要用 `ask_value` 问坐标。服务端只看得到降采样后的那张图,它说出来的坐标在 **那张图**的像素空间里;只有 `find*` 这条路会把坐标换算回屏幕坐标。 用 `ask_value` 问坐标,拿到的数字直接点会**点空**。要点击点就用 `ai.click(target)`, 或 `ai.find(target)` 取 `center_x` / `center_y`。 ## 判断与动作 ### exists 目标在不在。等价于 `find()` 是否为 `None`,花费也一样。 ```python ai.exists(target, rect=None, image=None, confidence=0.0, hint=None) ``` ```python if ai.exists("弹窗上的关闭按钮"): ai.click("弹窗上的关闭按钮") ``` ### wait 等目标出现,返回结果或 `None`。 ```python ai.wait(target, timeout=30, interval=0.0, rect=None, confidence=0.0, hint=None) ``` ```python r = ai.wait("加载完成的商品列表", timeout=20) ``` > 每轮都是一次完整的云端推理(几秒)**并且都要计费**,所以 `timeout` 设小一点。 `interval` 默认 0 —— 推理本身就是最大的间隔了。 `wait()` 刻意**没有 `image` 参数**:每轮重新截屏才是「等」的意义。 ### click 找到目标并点击它的中心点。找不到返回 `False`。 ```python ai.click(target, timeout=0, rect=None, image=None, confidence=0.0, hint=None) ``` ```python ai.click("右上角的关闭按钮") ai.click("提交订单", timeout=15) # 最多等 15 秒 ``` > 传了 `image` 时 `timeout` 无效 —— 静态图不会变,等待没有意义。 而且那张图**必须对应当前屏幕**,否则算出来的坐标会点错地方。 ## 公共参数 ### rect:限定区域(强烈建议传) ```python ai.find("确定按钮", rect=[0, 1800, 1080, 2400]) ``` `rect=[left, top, right, bottom]`。**裁剪比降采样保真得多**,图更小、更快、更省钱。 传了 `rect` 时坐标仍然返回**完整的屏幕坐标**,不需要你自己加偏移。 ### image:用你自己的图 除 `wait()` 外,所有方法都接受 `image=`。iOS 的 `screen.capture()` 默认返回 `PIL.Image`,可以直接传进来。 | 传入 | 说明 | | --- | --- | | `None`(默认) | 自动截取当前全屏 | | `PIL.Image` | `screen.capture()` 的结果(iOS 默认就是这个) | | `ndarray` | `screen.capture(format=screen.FORMAT_CV_MAT)`,或你自己处理过的图(BGR) | | `str` | 图片文件路径 | 截一次、多次查询复用,能省下截屏开销: ```python from ascript.ios import screen shot = screen.capture() price = ai.ask_value("总价", value_type=float, image=shot) count = ai.ask_value("商品件数", value_type=int, image=shot) ``` ### image_tokens:上传图片的清晰度 控制**上传图片的清晰度**(视觉 token 预算),默认 1024。 > 它管的是**输入的图**,不是输出长度 —— 和 OpenAI 那个 `max_tokens` 是两回事。 视觉模型按 32×32 像素算一个 token,所以一张 1080×2400 的全屏图原本是 2550 个 token, 默认会被等比压到 1024: | image_tokens | 上传尺寸 | 占原图 | | --- | --- | --- | | 256 | 352×768 | 10% | | 512 | 480×1088 | 20% | | **1024(默认)** | **672×1536** | **40%** | | 2048 | 960×2144 | 79% | ```python ai.find("底部那个很小的图标", image_tokens=2048) # 小目标看不清时调大 ai.find("屏幕中间的大按钮", image_tokens=512) # 大目标够用了,省钱 ``` > **优先考虑 rect,不是调这个** 同样是减少 token,`rect` 是"只看这块但看得清",降采样是"整屏都看但都变糊"。 ### hint:补充上下文 所有方法都接受可选的 `hint=`: ```python ai.find("确定按钮", hint="在底部弹窗里,不是顶部导航栏那个") ai.ask_value("图中算式的结果", value_type=int, hint="只算红框里那一道") ``` `hint` 只补充"要找什么",输出格式仍由服务端决定 —— 这样服务端换模型时你的脚本不用改。 ## 其它 ### init ```python ai.init(api_key=None, base_url=None, timeout=None, coord_space=None) ``` | 参数 | 类型 | 是否必填 | 说明 | | --- | --- | --- | --- | | api_key | str | 否 | `sk-as-` 开头的密钥。不传则读环境变量 `ASCRIPT_AI_KEY` | | base_url | str | 否 | 自建/测试环境才需要传 | | timeout | int | 否 | 单次请求超时秒数,默认 60 | | coord_space | str | 否 | 极少用。`"abs"` / `"norm1000"` / `"ratio"`,见下 | `coord_space` 默认自动判定模型输出的坐标空间。已知有一个信息论上的死角: 模型吐 0~1000 归一化、目标又恰好在左上角时,单看一帧无法区分。 **真遇到系统性偏移**时才手动钉死: ```python ai.init(api_key="sk-as-...", coord_space="norm1000") ``` ### status 当前配置状态,排查问题时用。 ```python ai.status() # {'configured': True, 'base_url': 'https://ai.ascript.cn/v1', 'coord_space': 'auto'} ``` ### 异常 所有异常都挂在模块上,用 `ai.XXX` 取,不需要单独 import。 它们**全部继承自 `ai.AsaiError`**,只想粗粒度兜底的话捕获它一个就够。 | 异常 | 含义 | 该怎么处理 | | --- | --- | --- | | `NotReadyError` | 还没配密钥,请求根本没发出去 | 先 `init()` 或设 `ASCRIPT_AI_KEY` | | `AuthenticationError` | 密钥无效或已被删除 | 去账户中心重新创建 | | `CredentialExpiredError` | 密钥还在,但它绑定的登录状态失效了 | 先重新登录网页,再重新创建密钥 | | `InsufficientBalanceError` | 余额不足 | 提示用户充值,重试没用 | | `InvalidRequestError` | 参数有问题 | 改代码,重试没用 | | `RateLimitError` | 调用太频繁 | 等 `e.retry_after` 秒后再试 | | `ServiceUnavailableError` | 服务端暂时不可用 | 可以稍后重试 | | `UpstreamError` | 上游模型服务故障(服务端已自动重试过一次) | 稍后重试 | | `NetworkError` | 连不上服务端 | 检查网络 | | `ProtocolError` | 服务端返回了读不懂的内容 | 正常不该发生,报 `request_id` 给我们 | `CredentialExpiredError` 继承自 `AuthenticationError`,所以 `except ai.AuthenticationError` 能把两者一起兜住。 每个异常都带这几个属性,排查时有用: | 属性 | 说明 | | --- | --- | | `message` | 给人看的说明(服务端返回的中文原文) | | `type` | 机器可读的错误类型,如 `"insufficient_balance"` | | `status_code` | HTTP 状态码,本地错误时为 `None` | | `request_id` | 服务端请求 ID,**找我们排查时报这个** | ```python from ascript.ios.vlm import ascript_ai as ai import time try: r = ai.find("登录按钮") except ai.NotReadyError: print("还没配密钥") except ai.InsufficientBalanceError: print("余额不足,请充值") # 重试没用,直接退出 except ai.RateLimitError as e: time.sleep(e.retry_after or 5) # 服务端建议的等待秒数 r = ai.find("登录按钮") except ai.AuthenticationError: print("密钥有问题,去账户中心重新创建") # 顺带兜住 CredentialExpiredError except ai.AsaiError as e: print("调用失败:%s (request_id=%s)" % (e.message, e.request_id)) ``` > 写脚本时至少把 `InsufficientBalanceError` 单独拎出来 —— 余额不足和网络抖动看起来都是"调用失败",但一个该停、一个该重试, 混在一起兜会让脚本在没钱的情况下疯狂空转重试。 ## 计费 按实际 Token 用量从 AI Studio 账户余额扣费,与网页对话走同一套计费和日志链路。 每次调用都能在账户中心的「使用日志」里查到明细。 **`rect=` 传得越小,图越小,越省。** --- # 屏幕图像 · Android 来源:https://www.ascript.cn/docs/android/api/screen/ # 屏幕图像 ```python from ascript.android import screen ``` 从屏幕中 `获取图像数据`, `图像转换` 等操作 > **使用场景** - 获取屏幕截图,作为所有图色识别的基础 - 指定区域截图,减少不必要的处理范围 - 不同格式转换(Bitmap、PIL Image、OpenCV Mat),适配不同处理需求 - 获取/比较指定坐标的颜色值 - 截图保存到文件,用于调试或记录 ## 方法 ### 截图 获取一张当前屏幕的截图,图片格式可以为 : > `Android Bitmap`
> `Android Image` 需手动close释放,否则后续截图报错!
> `Python PIL Image`
> `Python Opencv Mat` BGR格式
- 函数 ```python screen.capture(x:int=None,y:int=None,x1:int=None,y1:int=None,pixel_format=FORMAT_BITMAP) ``` - 参数 | 参数 | 类型 | 是否必填 | 说明 | | ------------ | ------------ | ------------ | ------------ | | x,y,x1,y1 | int | 否 | 截图的矩形范围坐标 `默认:全屏截图` | | pixel_format | int | 否 | 截图格式:
**screen.FORMAT_BITMAP** : android bitmap(默认)
**screen.FORMAT_ANDROID_IMAGE** : 已过时
**screen.FORMAT_PIL_IMAGE** : Python Pil Image
**screen.FORMAT_CV_MAT** : Python OpenCv BGR MAT
**screen.FORMAT_IMAGE_DATA** : 图片byte[]数据,包含data,rowStride,width,height| - 返回值 [Bitmap 或 Pil-Image 或 OpencvMat](#) Android图片对象 - 示例 ```python # 截取屏幕截图,格式为Bitmap from ascript.android import screen bitmap = screen.capture() print(bitmap) ``` ```python # 截取指定范围屏幕,格式为Bitmap from ascript.android import screen bitmap = screen.capture( 122,358,808,979) print(bitmap) ``` ```python # 截取屏幕截图,返回值为Pil Image from ascript.android import screen pil_image = screen.capture(pixel_format=screen.FORMAT_PIL_IMAGE) print(pil_image) ``` ```python # 截取屏幕截图,返回值为Opencv mat from ascript.android import screen cv_mat = screen.capture(pixel_format=screen.FORMAT_CV_MAT) print(cv_mat) ``` ```python # 截取屏幕截图,返回值Android Image-Data from ascript.android import screen image_data = screen.capture(pixel_format=screen.FORMAT_IMAGE_DATA) # image_data.data #图片byte[]数据 # image_date.rowStride # image_date.width # image_data.height ``` --- ### 截图快捷方法 `capture()` 每次都要写 `pixel_format=`,比较啰嗦。下面三个是它的语义化包装, 效果完全等价,参数也一样(`x, y, x1, y1`): - 函数 ```python screen.capture_bitmap(x:int=None, y:int=None, x1:int=None, y1:int=None) screen.capture_pil(x:int=None, y:int=None, x1:int=None, y1:int=None) screen.capture_cv(x:int=None, y:int=None, x1:int=None, y1:int=None) ``` - 对应关系 | 快捷方法 | 等价写法 | 返回类型 | | --- | --- | --- | | `capture_bitmap()` | `capture(pixel_format=screen.FORMAT_BITMAP)` | Android Bitmap | | `capture_pil()` | `capture(pixel_format=screen.FORMAT_PIL_IMAGE)` | Python PIL Image | | `capture_cv()` | `capture(pixel_format=screen.FORMAT_CV_MAT)` | OpenCV Mat(BGR) | - 示例 ```python from ascript.android import screen bitmap = screen.capture_bitmap() # 全屏 Bitmap pil = screen.capture_pil() # 全屏 PIL Image mat = screen.capture_cv() # 全屏 OpenCV BGR Mat mat2 = screen.capture_cv(122, 358, 808, 979) # 指定范围 ``` > 截一次、多处复用,能省下重复截屏的开销 —— 找图、找色、OCR、 [AI 云识别](./vlm/cloud.md) 都接受 `image=` 参数传入已截好的图。 ```python shot = screen.capture_cv() Ocr.find("确定", image=shot) ai.find("底部的确定按钮", image=shot) ``` --- ### 图像某坐标色值 获取图像指定坐标的颜色值 - 函数 ```python screen.get_color(x:int,y:int,bitmap = screen.capture()) ``` | 参数 | 类型 | 是否必填 | 说明 | | ------------ | ------------ | ------------ | ------------ | | x | int | 是 | 坐标x | | y | int | 是 | 坐标y | | bitmap | int | 否 | 指定图像,默认:全屏截图 | - 返回值 [Color](#) 颜色值对象,点击查看其内部属性值 - 示例 ```python from ascript.android import screen color = screen.get_color(200,200) print(color.rgb) #16进制RGB颜色值 print(color.argb) #16进制ARGB颜色值 PS:A位透明值通道 print(color.r,color.g,color.b,color.a) # 获取R通道,G通道,B通道,A通道 10进制颜色值 ``` --- ### 图像所有色值 获取整张图像所有色值 - 函数 ```python screen.get_colors(bitmap = screen.capture()) ``` | 参数 | 类型 | 是否必填 | 说明 | | ------------ | ------------ | ------------ | ------------ | | bitmap | int | 否 | 指定图像,默认:全屏截图 | - 返回值 [int[]](#) 10进制 颜色数组列表.可通过Color构造,获取RGB颜色值 - 示例 ```python from ascript.android import screen from ascript.android.screen import Color colors = screen.get_colors() print(f"共有:{len(colors)}个颜色点") for i in range(len(colors)): # 打印出屏幕第一个点的颜色值 if i == 0: c = Color(colors[i]) print(c.rgb) ``` --- ### 区域颜色点数量 在一个区域内找 指定颜色点的数量 - 函数 ```python screen.get_color_num(colors:str,rect:list=None,sim:float=0.9) ``` - 参数 | 参数 | 类型 | 是否必填 | 说明 | | ------------ | ------------ | ------------ | ------------ | | colors | int | 否 | 颜色值:
`格式1: "#FFFFFF" 检索单一颜色值数量`
`格式2:"#FFFFFF\|#000000" 多种颜色满足其一则算数`
`格式3:"#000000-#111111" 给定颜色值 在 两个颜色值之间则算数` | | rect | list | 否 | 屏幕范围 | | sim | float | 否 | 相似度 0~1之间,默认0.9:90%相似度 | - 返回值 [Bitmap](#) Android图片对象 - 示例 ```python # 获取屏幕中 #6989C5 和 #FF784B 颜色值数量 from ascript.android import screen num = screen.get_color_num("#6989C5|#FF784B") print(num) ``` ```python # 获取屏幕指定范围内 #6989C5 和 #FF784B 颜色值数量 from ascript.android import screen num = screen.get_color_num("#6989C5|#FF784B",rect =[413,358,709,578]) print(num) ``` ```python # 获取屏幕指定范围内 #6989C5 和 #FF784B 相似度80% 的颜色点数量 from ascript.android import screen num = screen.get_color_num("#6989C5|#FF784B",rect =[413,358,709,578],sim=0.8) print(num) ``` --- ### 横竖屏切换监听 当手机横竖屏变化后的监听 - 函数 ```python screen.set_ori_change_listener(obj_listener): ``` - 参数 | 参数 | 类型 | 是否必填 | 说明 | | ------------ | ------------ | ------------ | ------------ | | obj_listener | fun | 是 | 一个回调函数,必须包含两个形参,来接收回传的width,height | - 示例 ```python # 当横竖屏变化时,会调用该函数,并回传windth,height def on_ori_change(w,h): print("横竖屏变化了") if w > h: print("横屏") else: print("竖屏") # 设置监听函数 screen.set_ori_change_listener(on_ori_change) ``` --- ### 图像转换 #### 图像转文件 将 **Android Bitmap** 转存为文件 - 函数 ```python screen.bitmap_to_file(path:str,bitmap=screen.capture(),quality=100) ``` - 参数 | 参数 | 类型 | 是否必填 | 说明 | | ------------ | ------------ | ------------ | ------------ | | path | str | 是 | 文件路径 | | bitmap | Bitmap | 否 | 图片 `默认:全屏截图` | | quality | int | 否 | 图片清晰度,1-100之间 ,100为100%无损图片 `默认:100` | - 示例 ```python # 将屏幕截图存储在SD卡下的1.png中 from ascript.android import screen from ascript.android.system import R screen.bitmap_to_file(R.sd("1.png")) ``` ```python # 将屏幕截图存储在SD卡下的1.png中,清晰度设为80% from ascript.android import screen from ascript.android.system import R screen.bitmap_to_file(R.sd("1.png"),quality=80) ``` --- #### 文件转图片 将 **文件** 转存为Android Bitmap - 函数 ```python screen.file_to_bitmap(path:str,sampleSize:int=1) ``` - 参数 | 参数 | 类型 | 是否必填 | 说明 | | ------------ | ------------ | ------------ | ------------ | | path | str | 是 | 文件路径 | | sampleSize | int | 否 | 采样率,如参数为2:每隔2行,2列采集1行,结果为原图的1/4大小 `默认1:无损` | - 示例 ```python # 将屏幕截图存储在SD卡下的1.png中 from ascript.android import screen from ascript.android.system import R bitmap = screen.file_to_bitmap(R.sd("2.png")) print(bitmap) ``` --- #### 图片转Base64数据 将 **图片** 转为 **base64数据** - 函数 ```python screen.bitmap_base64(bitmap=screen.capture()) ``` - 参数 | 参数 | 类型 | 是否必填 | 说明 | | ------------ | ------------ | ------------ | ------------ | | bitmap | Bitmap | 否 | 图片 `默认:将当前屏幕转为 base64数据` | - 示例 ```python # 将屏幕图像转换为base64 from ascript.android import screen b64_str = screen.bitmap_base64() print(b64_str) ``` ```python # 将文件转换为base64 from ascript.android import screen from ascript.android.system import R bitmap = screen.file_to_bitmap(R.sd("22222.png")) b64_str = screen.bitmap_base64(bitmap) print(b64_str) ``` --- #### Base64数据转图片 将 **base64数据** 转为 **图片** - 函数 ```python screen.base64_bitmap(b64:str) ``` - 参数 | 参数 | 类型 | 是否必填 | 说明 | | ------------ | ------------ | ------------ | ------------ | | b64 | str | 否 | 图片 `base64数据` | - 示例 ```python # 将屏幕图像转换为base64 from ascript.android import screen bitmap = screen.base64_bitmap("b64:str......") print(bitmap) ``` --- #### 图片最大边缩放 将 **图片** 缩放, 最大边不超过设定值,短边等比缩放. - 函数 ```python screen.bitmap_maxside(bitmap=screen.capture(),max_side_len=9999) ``` - 参数 | 参数 | 类型 | 是否必填 | 说明 | | ------------ | ------------ | ------------ | ------------ | | bitmap | Bitmap | 否 | 要缩放的图片 `默认:当前屏幕截图` | | max_side_len | int | 是 | 最大边长度 ,默认最大边长 9999 | - 示例 ```python # 将当前屏幕截图,最大边缩放到800 bitmap= screen.bitmap_maxside(screen.capture(),max_side_len=800) print(bitmap.getWidth(),bitmap.getHeight()) ``` --- #### Bitmap 转 Python Image 将 **Android Bitmap** 转换为 **Python Image(PIL)** - 函数 ```python screen.bitmap_to_pilimage(bitmap=capture()) ``` - 参数 | 参数 | 类型 | 是否必填 | 说明 | | ------------ | ------------ | ------------ | ------------ | | bitmap | Bitmap | 否 | 要转换的Bitmap图像 `默认:当前屏幕截图` | - 返回值 [Image](#) Python PIL 图像 - 示例 ```python # 将当前屏幕Bitmap 转换为 Python Image from ascript.android import screen img= screen.bitmap_to_pilimage() print(img.width,img.height) ``` --- #### Bitmap 转 Opencv Image 将 **Android Bitmap** 转换为 **Opencv Image** 由于android图像是 ARGB通道 ,因此在转换后会丢失A通道,并且图像数据 为BGR三通道格式 - 函数 ```python screen.bitmap_to_cvimage(bitmap=capture()) ``` - 参数 | 参数 | 类型 | 是否必填 | 说明 | | ------------ | ------------ | ------------ | ------------ | | bitmap | Bitmap | 否 | 要转换的Bitmap图像 `默认:当前屏幕截图` | - 返回值 [Numpy array](#) Python Opencv Image - 示例 ```python # 将当前屏幕Bitmap 转换为 Openv Image = cv2.imread import cv2 from ascript.android.ui import ImageWindow from ascript.android import screen image = screen.bitmap_to_cvimage() # 展示图片 ImageWindow.show(image) ``` --- #### Opencv Image 转 Bitmap 将 **Opencv Image** 转换为 **Android Bitmap** 将BGR通道的CV图像 转换为 Bitmap(RGB) - 函数 ```python screen.cvimage_to_bitmap(image) ``` - 参数 | 参数 | 类型 | 是否必填 | 说明 | | ------------ | ------------ | ------------ | ------------ | | image | opencv image | 否 | 要转换的Opencv Image | - 返回值 [Bitmap](#) Python PIL 图像 - 示例 ```python # 将当前屏幕Bitmap 转换为 Openv Image = cv2.imread from ascript.android import screen img= screen.bitmap_to_cvimage() print(img) ``` --- ### 图像Bitmap ```python from android.graphics import Bitmap ``` 在Android 中的内存图像对象 #### 宽度与高度 获取图像宽度 - 示例 ```python from ascript.android import screen bitmap = screen.capture() print(bitmap.getWidth(),bitmap.getHeight()) ``` ### 颜色 ```python from ascript.android.screen import Color ``` #### 创建一个颜色 创建一个颜色值,用于颜色值转换,和值的获取 - 函数 ```python Color(color_argb:int) ``` - 参数 | 参数 | 类型 | 是否必填 | 说明 | | ------------ | ------------ | ------------ | ------------ | | color_argb | int | 是 | [图像所有色值](#图像所有色值) 该方法返回结果中的Int值 (实际为ARGB的色值) | - 示例 ```python # __init__.py 为初始化加载文件 from ascript.android import screen from ascript.android.screen import Color colors = screen.get_colors() # 获取第一个int颜色值 color_int = colors[0] # 转换为 Color对象,方便对颜色值进行转换 color = Color(color_int) print(color.rgb) ``` #### 属性 | 属性 | 返回类型 | 说明| | ------------ | ------------ |-----| | r | int | R通道 10进制 颜色值 | | g | int | G通道 10进制 颜色值 | | b | int | B通道 10进制 颜色值 | | a | int | A通道(透明值) 10进制 颜色值 | | rgb | str | 16进制 RGB颜色值 | | argb | str | 16进制 ARGB颜色值 | - 示例 ```python from ascript.android import screen from ascript.android.screen import Color colors = screen.get_colors() # 获取第一个int颜色值 color_int = colors[0] # 转换为 Color对象,方便对颜色值进行转换 color = Color(color_int) print(color.rgb) #16进制RGB颜色值 print(color.argb) #16进制ARGB颜色值 PS:A位透明值通道 print(color.r,color.g,color.b,color.a) # 获取R通道,G通道,B通道,A通道 10进制颜色值 ``` #### 从十六进制创建 从 `"#RRGGBB"` 或 `"#AARRGGBB"` 字符串创建 Color 对象。 ```python Color.from_hex(hex_str) ``` ```python from ascript.android.screen import Color c = Color.from_hex("#FF0000") # 红色 print(c.r, c.g, c.b) # 255 0 0 ``` #### 从 RGB 分量创建 ```python Color.make(r, g, b, a=255) ``` ```python from ascript.android.screen import Color c = Color.make(255, 128, 0) print(c.rgb) # #ff8000 ``` #### 颜色距离 计算两个颜色的欧氏距离(0~441.67)。支持传入 Color 对象、int 值或十六进制字符串。 ```python Color.distance(c1, c2) ``` ```python from ascript.android.screen import Color d = Color.distance("#FF0000", "#00FF00") print(d) # 360.62 ``` #### 颜色相似判断 判断两个颜色是否相似(距离小于等于 threshold)。 ```python Color.similar(c1, c2, threshold=30) ``` ```python from ascript.android.screen import Color print(Color.similar("#FF0000", "#FE0502")) # True print(Color.similar("#FF0000", "#00FF00")) # False ``` --- # 找图 · Android 来源:https://www.ascript.cn/docs/android/api/screen/findimages/ # 找图 ```python from ascript.android.screen import FindImages ``` 从屏幕中找到 **局部图片** 的 `位置`,支持透明 PNG、多图搜索、多尺度匹配。 > **使用场景** - 在屏��上查找特定图标、按钮、头像等图片元素的位置 - 判断某个 UI 元素是否出现(如加载完成的图标、红点提醒) - 游戏中查找怪物、道具、技能图标等固定图案 - 一次查找多张模板图,判断当前匹配的是哪一个 - 跨分辨率设备适配,同一套模板图在不同 DPI 下使用 ## 返回值格式 所有找图方法返回 dict 字典(单个)或 dict 数组(多个): ```python { 'rect': [915, 1761, 1076, 1899], # 子图在屏幕中的范围 'center_x': 995, # 中心点 X 坐标 'center_y': 1830, # 中心点 Y 坐标 'confidence': 0.949, # 相似度 0-1 'index': 0 # 多图搜索时,属于第几张模板图 } ``` ## 方法 ### find — 找一个 在屏幕中查找局部图片,返回第一个匹配结果。 - 函数 ```python FindImages.find(part_img, rect=None, confidence=0.1, rgb=False, source_img=None, ori=2, scale_range=None, image=None) ``` - 参数 | 参数 | 类型 | 是否必填 | 说明 | | --- | --- | --- | --- | | part_img | str/list | 是 | 局部图片路径或路径列表。只填文件名时自动在 `res/img/` 下查找 | | rect | list | 否 | 搜索范围 `[x, y, x1, y1]` | | confidence | float | 否 | 可信度阈值 0-1,低于此值的结果被过滤,默认 0.1 | | rgb | bool | 否 | True=彩色匹配,False=灰度匹配(可找到相同形状不同颜色的图案) | | ori | int | 否 | 排序方向 1-8,默认 2(左上→右下横向) | | scale_range | list | 否 | 多尺度匹配范围,如 `[0.5, 2.0]`,None=单尺度 | | image | Bitmap/ndarray | 否 | 传入源图,None=自动截屏 | - 示例 ```python from ascript.android.screen import FindImages from ascript.android.system import R # 基本找图 res = FindImages.find(R.res("/img/btn.png")) if res: print("位置:", res["center_x"], res["center_y"]) print("相似度:", res["confidence"]) # 指定范围 + 可信度 res = FindImages.find(R.res("/img/btn.png"), rect=[59, 932, 374, 1207], confidence=0.8) # 一次找多张图 res = FindImages.find(R.img("a.png", "b.png", "c.png"), confidence=0.8) if res: print("匹配的是第", res["index"], "张图") ``` --- ### find_all — 找所有 查找所有匹配的图片位置。 - 函数 ```python FindImages.find_all(part_img, rect=None, confidence=0.1, rgb=False, source_img=None, maxcnt=0, ori=2, scale_range=None, image=None) ``` - 参数 | 参数 | 类型 | 是否必填 | 说明 | | --- | --- | --- | --- | | maxcnt | int | 否 | 最大返回数量,0=不限制 | | 其他参数 | - | - | 同 `find()` | - 返回值 dict 数组,或 `None` - 示例 ```python from ascript.android.screen import FindImages from ascript.android.system import R res = FindImages.find_all(R.res("/img/icon.png"), confidence=0.8) if res: for item in res: print("位置:", item["center_x"], item["center_y"], "相似度:", item["confidence"]) ``` --- ### find_template — 模板匹配(单个) 基于 OpenCV 模板匹配,速度快,支持透明 PNG。 - 函数 ```python FindImages.find_template(part_img, rect=None, confidence=0.5, rgb=False, source_img=None, ori=2, scale_range=None, image=None) ``` 参数同 `find()`,默认 confidence=0.5。 --- ### find_all_template — 模板匹配(所有) - 函数 ```python FindImages.find_all_template(part_img, rect=None, confidence=0.5, rgb=False, maxcnt=0, source_img=None, ori=2, scale_range=None, image=None) ``` 参数同 `find_all()`,默认 confidence=0.5。 --- ## 多尺度匹配 普通模板匹配要求模板图和屏幕上的目标**大小一致**,但实际使用中经常遇到: - 模板图在 1080p 设备截取,运行在 720p 或 2K 设备上 - 游戏界面有缩放动画,按钮大小会变化 - 同一个 APP 在不同 DPI 设备上图标尺寸不同 `scale_range` 参数可以解决这些问题。 ### 参数说明 ```python scale_range = [最小缩放比, 最大缩放比] ``` | 值 | 含义 | | --- | --- | | `None` | 不启用多尺度,使用原始大小匹配(默认) | | `[0.5, 1.5]` | 模板在 50%\~150% 范围内逐步缩放匹配 | | `[0.8, 1.2]` | 小幅度搜索,适合轻微尺寸偏差 | | `[0.5, 2.0]` | 大范围搜索,适合跨分辨率场景 | 引擎会在范围内自动生成约 **15 个缩放步长**(如 0.5, 0.53, 0.57, ... 2.0),逐一尝试匹配,返回得分最高的结果。如果范围包含 1.0,会确保 1.0(原尺寸)被包含在内。 ### 示例 ```python from ascript.android.screen import FindImages from ascript.android.system import R # 场景一:1080p 截的模板,跑在不同分辨率设备 res = FindImages.find(R.res("/img/btn.png"), scale_range=[0.5, 2.0], confidence=0.7) if res: print("找到:", res["center_x"], res["center_y"]) # 场景二:按钮可能有轻微缩放,小范围搜索即可 res = FindImages.find(R.res("/img/icon.png"), scale_range=[0.9, 1.1], confidence=0.8) # 场景三:配合 rect 缩小搜索区域,提升速度 res = FindImages.find(R.res("/img/btn.png"), rect=[0, 1500, 1080, 1920], scale_range=[0.6, 1.5], confidence=0.7) ``` > **性能提示** 多尺度匹配约比单尺度慢 15 倍(15 个缩放步长),建议: - 配合 `rect` 缩小搜索区域 - 缩放范围尽量精确(如 `[0.8, 1.2]` 而非 `[0.3, 3.0]`) - 对性能要求高的场景,先用单尺度测试,确实不匹配再启用 --- ## image 参数说明 所有方法都支持 `image` 参数,可传入 Android Bitmap 或 OpenCV ndarray,系统自动转换: ```python from ascript.android.screen import FindImages, capture # 先截屏,再找图(避免多次截屏) bmp = capture() res1 = FindImages.find("a.png", image=bmp) res2 = FindImages.find("b.png", image=bmp) ``` --- ## 关于 SIFT > **兼容说明** `find_sift()` 和 `find_all_sift()` 方法因压缩版 OpenCV 不支持 SIFT 算法,已自动降级为模板匹配。如需跨分辨率匹配,请使用 `scale_range` 参数。 --- # 找色 · Android 来源:https://www.ascript.cn/docs/android/api/screen/findcolors/ # 多点找色 ```python from ascript.android.screen import FindColors ``` 从屏幕中找到 **符合颜色特征点** 的 `点位` > **使用场景** - 通过多个颜色特征点定位 UI 元素(比找图更快,不需要截模板图) - 游戏中检测血量条、进度条的颜色变化 - 查找屏幕上特定颜色的像素点位置(如红色提醒、绿色在线状态) - 配合图色工具一键生成颜色特征串,复制即可使用 ## 方法 ### find — 找一个点 找到第一个符合特征点的位置,立刻返回。 - 函数 ```python FindColors.find(colors, rect=None, space=5, ori=2, diff=0.98, image=None, mode=0, until=0) ``` - 参数 | 参数 | 类型 | 是否必填 | 说明 | | --- | --- | --- | --- | | colors | str | 是 | 颜色特征点,**通常用图色助手获取** | | rect | list | 否 | 搜索范围 `[x, y, x1, y1]` | | space | int | 否 | 结果间距,默认 5 像素。多个结果在此距离内重合则只保留一个 | | ori | int | 否 | 找色方向 1-8,默认 2(左上→右下横向)[详情见...](#关于找色方向) | | diff | float | 否 | 相似度 0-1,默认 0.98 | | image | Bitmap/ndarray | 否 | 传入源图,None=自动截屏 | | mode | int | 否 | 0=RGB, 1=Norm | | until | int | 否 | 超时毫秒数,0=不重试,>0=持续重试直到超时 | - 返回值 Point 点位对象,可通过 `.x` `.y` 获取坐标,未找到返回 `None` - 示例 ```python from ascript.android.screen import FindColors point = FindColors.find('634,2126,#05D395|675,2142,#05D395|889,2146,#05D395', rect=[82, 2010, 1005, 2215]) if point: print(point.x, point.y) ``` --- ### find_all — 找所有点 找到全部符合特征点的位置。 - 函数 ```python FindColors.find_all(colors, rect=None, space=5, ori=2, diff=0.98, image=None, p_max_num=999999, mode=0, until=0) ``` - 参数 | 参数 | 类型 | 是否必填 | 说明 | | --- | --- | --- | --- | | p_max_num | int | 否 | 最大返回数量,默认 999999 | | 其他参数 | - | - | 同 `find()` | - 返回值 Point 数组,或 `None` - 示例 ```python from ascript.android.screen import FindColors points = FindColors.find_all('634,2126,#05D395|675,2142,#05D395', rect=[82, 2010, 1005, 2215]) if points: for p in points: print(p.x, p.y) ``` --- ### find_color — 单点找色 在屏幕上查找第一个匹配指定颜色的像素点。 - 函数 ```python FindColors.find_color(color, rect=None, diff=0.98, mode=0) ``` - 参数 | 参数 | 类型 | 是否必填 | 说明 | | --- | --- | --- | --- | | color | str | 是 | 目标颜色 `"#RRGGBB"` | | rect | list | 否 | 搜索范围 | | diff | float | 否 | 相似度 0-1,默认 0.98 | | mode | int | 否 | 0=RGB, 1=Norm | - 返回值 dict `{"x": 100, "y": 200}` 或 `None` - 示例 ```python from ascript.android.screen import FindColors # 找到屏幕上第一个红色像素 pos = FindColors.find_color("#FF0000", rect=[0, 0, 500, 500]) if pos: print("找到红色:", pos["x"], pos["y"]) ``` --- ### find_all_color — 单点找色(全部) 查找所有匹配颜色的像素点。 - 函数 ```python FindColors.find_all_color(color, rect=None, diff=0.98, mode=0, max_num=999999) ``` - 返回值 `list[dict]`,每个元素为 `{"x": ..., "y": ...}`,或 `None` --- ## 关于找色方向 找色方向是指:**找到颜色特征点的先后顺序** 如图: ![来自静态目录的图像](/img/findcolors_ore.png) - 1: 左上角到右下角,纵向开始找色,先找到 a 顶点。 - 2: 左上角到右下角,横向开始找色,先找到 b 顶点。 - 3: 右上角到左下角,横向开始找色,先找到 c 顶点。 - 依此类推 --- ## image 参数说明 `find()` 和 `find_all()` 支持 `image` 参数,传入 Bitmap 或 cv2 ndarray 避免重复截屏: ```python from ascript.android.screen import FindColors, capture bmp = capture() p1 = FindColors.find('...', image=bmp) p2 = FindColors.find('...', image=bmp) ``` --- # 文字识别 · Android 来源:https://www.ascript.cn/docs/android/api/screen/ocr/ # 文字识别 ```python from ascript.android.screen import Ocr ``` 从屏幕中 `识别文字`,支持多引擎、便捷查找/点击/等待等操作。 > **使用场景** - 识别屏幕上的按钮文字、标题、提示语,判断当前页面状态 - 自动点击"确认"、"登录"、"下一步"等文字按钮 - 等待"加载完成"、"操作成功"等提示出现后再执行下一步 - 提取屏幕上的数字(金额、倒计时、验证码等) - 游戏中识别对话框、任务提示、背包物品名称 ## 引擎选择 AScript 内置两个 OCR 引擎: | 引擎 | 说明 | 速度 | 精度 | | --- | --- | --- | --- | | `Ocr.MODE_MLK` | 谷歌 ML Kit V2 | ⚡ 快 | 高 | | `Ocr.MODE_PADDLE_V2` | 百度 PP-OCR V5 | 中等 | 高,支持多语言 | 默认引擎为 `MODE_PADDLE_V2`(PP-OCR V5),可通过 `set_engine` 全局切换。 ### 设置默认引擎 ```python # 切换为谷歌 MLKit Ocr.set_engine("mlkit") # 切换为百度 PaddleOCR Ocr.set_engine("paddle") ``` ## 便捷方法 以下方法自动截屏、自动调用当前默认引擎,无需关心底层细节。 ### 查找文字 在屏幕上查找文字,返回第一个匹配结果。 - 函数 ```python Ocr.find(text, rect=None, mode=None, binary=-1, confidence=0.1, image=None) ``` - 参数 | 参数 | 类型 | 是否必填 | 说明 | | --- | --- | --- | --- | | text | str | 是 | 要查找的文字(支持正则表达式) | | rect | list | 否 | 搜索范围 `[x, y, x1, y1]` | | mode | int | 否 | 引擎模式,None=使用默认引擎。`Ocr.MODE_MLK`=谷歌 MLKit,`Ocr.MODE_PADDLE_V2`=百度 PP-OCR V5 | | binary | int | 否 | 二值化阈值,-1=不处理,0-255=二值化后再识别 | | confidence | float | 否 | 可信度阈值,默认 0.1 | | image | Bitmap/ndarray | 否 | 传入源图,None=自动截屏 | - 返回值 匹配成功返回 dict,未找到返回 `None` ```python { "text": "登录", "rect": [320, 1080, 420, 1120], "center_x": 370, "center_y": 1100, "confidence": 0.95 } ``` - 示例 ```python from ascript.android.screen import Ocr # 查找"登录"按钮 result = Ocr.find("登录") if result: print("找到:", result["text"], "位置:", result["center_x"], result["center_y"]) ``` --- ### 查找所有文字 返回所有匹配结果列表。`text` 传 `None` 可获取屏幕上全部文字。 - 函数 ```python Ocr.find_all(text=None, rect=None, mode=None, binary=-1, confidence=0.1, image=None) ``` - 参数 同 `Ocr.find()`,其中 `text` 可为 `None`(返回所有识别结果)。 - 返回值 `list[dict]` 或 `None` - 示例 ```python from ascript.android.screen import Ocr # 获取屏幕上所有文字 all_text = Ocr.find_all() if all_text: for item in all_text: print(item["text"]) # 查找所有包含"设置"的结果 results = Ocr.find_all("设置") ``` --- ### 判断文字是否存在 - 函数 ```python Ocr.exists(text, rect=None, mode=None, binary=-1, confidence=0.1, image=None) ``` - 返回值 `True` 或 `False` - 示例 ```python from ascript.android.screen import Ocr if Ocr.exists("登录"): print("登录按钮存在") ``` --- ### 找到文字并点击 找到文字后自动点击其中心位置。 - 函数 ```python Ocr.click(text, rect=None, mode=None, binary=-1, confidence=0.1, timeout=0, image=None) ``` - 参数 | 参数 | 类型 | 是否必填 | 说明 | | --- | --- | --- | --- | | text | str | 是 | 要查找并点击的文字 | | timeout | float | 否 | 超时秒数,0=只尝试一次,>0=持续重试直到超时 | | 其他参数 | - | - | 同 `Ocr.find()` | - 返回值 `True`=点击成功,`False`=未找到 - 示例 ```python from ascript.android.screen import Ocr # 找到"确认"并点击 Ocr.click("确认") # 最多等 5 秒,找到"下一步"就点击 Ocr.click("下一步", timeout=5) ``` --- ### 等待文字出现 持续截屏识别,直到找到目标文字或超时。 - 函数 ```python Ocr.wait(text, timeout=10, rect=None, mode=None, binary=-1, confidence=0.1, image=None) ``` - 返回值 匹配成功返回 dict(同 `Ocr.find()`),超时返回 `None` - 示例 ```python from ascript.android.screen import Ocr # 等待"加载完成"出现,最多等 15 秒 result = Ocr.wait("加载完成", timeout=15) if result: print("页面已加载") ``` --- ### 等待文字出现并点击 组合 `wait` + `click`,等到文字出现后自动点击。 - 函数 ```python Ocr.wait_click(text, timeout=10, rect=None, mode=None, binary=-1, confidence=0.1, image=None) ``` - 返回值 `True`=点击成功,`False`=超时未找到 - 示例 ```python from ascript.android.screen import Ocr # 等待"开始"按钮出现并点击,最多等 10 秒 Ocr.wait_click("开始", timeout=10) ``` --- ## 底层引擎方法 如果需要更精细的控制,可以直接调用引擎方法。 ### 谷歌 ML Kit V2 速度快,准确度高,支持中文+英文。 - 函数 ```python Ocr.mlkitocr_v2(rect=None, pattern=None, confidence=0.5, bitmap=None, binary=-1, image=None) ``` - 参数 | 参数 | 类型 | 是否必填 | 说明 | | --- | --- | --- | --- | | rect | list | 否 | 识别范围 `[x, y, x1, y1]` | | pattern | str | 否 | 正则表达式,从识别结果中提取匹配文本 | | confidence | float | 否 | 可信度阈值,默认 0.5 | | bitmap | Bitmap | 否 | 传入图片,None=自动截屏 | | binary | int | 否 | 二值化阈值,-1=不处理 | | image | Bitmap/ndarray | 否 | 源图别名,支持 Bitmap 或 cv2 ndarray | > **谷歌 OCR 不支持 confidence 属性** 谷歌 OCR 没有可信度,识别到的文字即为结果。 - 返回值 `OcrText` 数组对象,每个元素包含: ```python OcrText { text = '应用或小程序', # 识别到的文字 center_x = 345, # 文字中心 X 坐标 center_y = 202, # 文字中心 Y 坐标 rect = [320, 180, 644, 242] # 文字范围 [left, top, right, bottom] } ``` - 示例 ```python from ascript.android.screen import Ocr # 识别屏幕全部文字 res = Ocr.mlkitocr_v2() if res: for r in res: print("文字:", r.text, "位置:", r.center_x, r.center_y) # 指定范围 + 正则过滤 res = Ocr.mlkitocr_v2(rect=[40, 122, 714, 659], pattern=".*程序.*") ``` --- ### 百度 PP-OCR V5 基于 PP-OCR V5 引擎,高精度离线识别,支持多语言。 - 函数 ```python Ocr.paddleocr(rect=None, pattern=None, confidence=0.1, max_side_len=1200, precision=16, bitmap=None, file=None, binary=-1, language="chinese", image=None) ``` - 参数 | 参数 | 类型 | 是否必填 | 说明 | | --- | --- | --- | --- | | rect | list | 否 | 识别范围 `[x, y, x1, y1]` | | pattern | str | 否 | 正则表达式,提取匹配文本 | | confidence | float | 否 | 可信度阈值,默认 0.1 | | max_side_len | int | 否 | 图片最大边长,超过则等比缩放,默认 1200 | | precision | int | 否 | 精度参数,默认 16 | | bitmap | Bitmap | 否 | 传入图片,None=自动截屏 | | file | str | 否 | 从文件中识别 | | binary | int | 否 | 二值化阈值,-1=不处理 | | language | str | 否 | 语言模型,默认 `"chinese"`(内置中英日韩),其他语言首次使用需下载 | | image | Bitmap/ndarray | 否 | 源图别名,支持 Bitmap 或 cv2 ndarray | - 返回值 `OcrText` 数组对象,同谷歌 OCR,额外包含 `confidence` 属性。 - 示例 ```python from ascript.android.screen import Ocr # 基本用法 res = Ocr.paddleocr() if res: for r in res: print(r.text, r.confidence) # 指定范围 + 正则 + 缩放 res = Ocr.paddleocr(rect=[40, 122, 714, 659], pattern=".*程序.*", max_side_len=1600) # 指定语言 res = Ocr.paddleocr(language="en") # 从文件识别 from ascript.android.system import R res = Ocr.paddleocr(file=R.sd("test.png")) ``` > **兼容说明** `Ocr.paddleocr_v2()` 和 `Ocr.paddleocr_v3()` 为旧版兼容方法,内部均调用 `Ocr.paddleocr()`,已统一升级为 PP-OCR V5 引擎。 --- ### 点阵字库识别 对特殊图像(如游戏数字、定制字体),使用点阵字库进行识别。 > **适用场景** 点阵字库适合**小范围精确识别**,不适合全屏检索。 - 函数 ```python Ocr.matrix(font_lib, rect=None, region=0.9, image=None) ``` - 参数 | 参数 | 类型 | 是否必填 | 说明 | | --- | --- | --- | --- | | font_lib | str | 是 | 字库文件路径 | | rect | list | 否 | 识别范围 `[x, y, x1, y1]` | | region | float | 否 | 识别精度 0-1,默认 0.9(90%) | | image | Bitmap/ndarray | 否 | 传入源图,None=自动截屏 | - 返回值 识别到的文字字符串,或 `None` - 示例 ```python from ascript.android.system import R from ascript.android.screen import Ocr # 识别指定范围内的点阵文字 text = Ocr.matrix(R.res("font.t"), rect=[90, 529, 343, 612]) print(text) ``` #### 如何制作字库? 制作字库用到了**图色工具中的点阵识字** 1. 打开图色助手并点击点阵字库 2. 框选要做的字 3. 按住键盘 1-9 取色要做的字,直到右侧出现字的二值图像 4. 选择要存放字库的工程和文件名称(文件名默认 `font.t`,可以更改)。此文件存放在 "工程/res/" 目录下,会自动创建 5. 填写文字代号 6. 点击保存字体到字库 --- ## image 参数说明 所有方法都支持 `image` 参数,可传入 Android Bitmap 或 OpenCV ndarray,系统会自动转换类型: ```python from ascript.android.screen import Ocr, capture # 自动截屏(默认行为) Ocr.find("文字") # 传入 Bitmap bmp = capture() Ocr.find("文字", image=bmp) # 传入 OpenCV 图片 import cv2 img = cv2.imread("/sdcard/test.png") Ocr.find("文字", image=img) ``` --- # 动作模拟 · Android 来源:https://www.ascript.cn/docs/android/api/action/action/ # 基本操作 ```python from ascript.android import action ``` 模拟 **手指** 操作 ## 点击 模拟 **手指点击屏幕** - 函数 ```python action.click(x:int,y:int,dur:int = 20): ``` - 参数 | 参数 | 类型 | 是否必填 | 说明 | | ------------ | ------------ | ------------ | ------------ | | x | int | 是 | 要点击的屏幕X坐标 | | y | int | 是 | 要点击的屏幕Y坐标 | | dur | float | 否 | 点击持续时间,默认20毫秒 | - 示例 ```python # 模拟手指点击屏幕坐标 (898,1916) from ascript.android import action action.click(898,1916) ``` ```python # 模拟手指长按屏幕坐标 (898,1916) 按住两秒不放 from ascript.android import action action.click(898,1916,2000) ``` --- ## 滑动 模拟 **手指滑动屏幕** - 函数 ```python action.slide(x:int,y:int,x1:int,y1:int,dur:int=20) ``` - 参数 | 参数 | 类型 | 是否必填 | 说明 | | ------------ | ------------ | ------------ | ------------ | | x | int | 是 | 滑动起始点X坐标 | | y | int | 是 | 滑动起始点Y坐标 | | x1 | int | 是 | 滑动结束点X坐标 | | y1 | int | 是 | 滑动结束点Y坐标 | | dur | float | 否 | 滑动持续时间,默认20毫秒 | | interpolator | java Interpolator | 否 | 默认None,[差速器. 可以实现各种加减速滑动效果](#关于差速器) | | control_points=None | list或int | 否 | 贝塞尔控制点| - 示例 ```python # 模拟手指滑动屏幕 从坐标(157,798) 滑动至坐标(946,834) ,耗时20毫秒 from ascript.android import action action.slide(157,798,946,834) ``` ```python # 模拟手指滑动屏幕 从坐标(157,798) 滑动至坐标(946,834) ,耗时3000毫秒 from ascript.android import action action.slide(157,798,946,834,3000) ``` ```python # 加速插值。动画开始时较慢,然后逐渐加速 from android.view.animation import AccelerateInterpolator action.slide(644,739,625,2000,2000,AccelerateInterpolator()) # 减速插值。动画开始时较快,然后逐渐减速。 # from android.view.animation import DecelerateInterpolator # action.slide(644,739,625,2000,2000,DecelerateInterpolator()) # 更多有趣差速器,见下文 ``` ```python # 模拟人手真实抖动 from ascript.android import action from android.view.animation import DecelerateInterpolator action.slide(157,798,946,834,3000,DecelerateInterpolator(),control_points=0) # 随机生成100组贝塞尔控制点 # action.slide(157,798,946,834,3000,control_points=100) # 自定义2组贝塞尔控制点 # action.slide(157,798,946,834,3000,control_points=[100,100,60,60]) ``` ### 关于差速器 我们可以借助android 原生差速器实现各种效果, 如 加速、减速、先加后减,等各种效果. 差速器部分,使用android.view.animation.Interpolato **这意味着我们可以直接导入android 的所有差速器类** #### `LinearInterpolator` * **导入:** `from android.view.animation import LinearInterpolator` * **效果:** 线性插值。动画从开始到结束以恒定速度进行,没有加速或减速。 * **参数:** 无。 * **构造函数:** `LinearInterpolator()` * **用途:** 简单、均匀的过渡,如匀速移动、渐变。 --- #### `AccelerateInterpolator` * **导入:** `from android.view.animation import AccelerateInterpolator` * **效果:** 加速插值。动画开始时较慢,然后逐渐加速。 * **参数:** * `factor` (float): 控制加速度的指数。值越大,开始时越慢,加速越明显。默认值为 `1.0f`,对应 t² 的曲线。 * **构造函数:** * `AccelerateInterpolator()` (使用默认 `factor` 1.0f) * `AccelerateInterpolator(float factor)` * **用途:** 模拟物体启动、元素滑入屏幕。 --- #### `DecelerateInterpolator` * **导入:** `from android.view.animation import DecelerateInterpolator` * **效果:** 减速插值。动画开始时较快,然后逐渐减速。 * **参数:** * `factor` (float): 控制减速度的指数。值越大,开始时越快,结束时减速越明显。默认值为 `1.0f`。 * **构造函数:** * `DecelerateInterpolator()` (使用默认 `factor` 1.0f) * `DecelerateInterpolator(float factor)` * **用途:** 模拟物体停止(如受摩擦力影响)、元素滑出屏幕。 --- #### `AccelerateDecelerateInterpolator` * **导入:** `from android.view.animation import AccelerateDecelerateInterpolator` * **效果:** 先加速后减速插值。动画开始和结束时慢,中间速度最快。形成一个平滑的 "S" 形速度曲线(Ease-in, Ease-out)。 * **参数:** 无。 * **构造函数:** `AccelerateDecelerateInterpolator()` * **用途:** 非常常用,用于创建自然、平滑的 UI 过渡效果,是最符合物理直觉的缓动之一。 --- #### `OvershootInterpolator` * **导入:** `from android.view.animation import OvershootInterpolator` * **效果:** 超出目标插值。动画会超过其最终目标值一点,然后再回弹到最终值。 * **参数:** * `tension` (float): 控制超出量(张力)。值越大,超出的幅度越大。默认值为 `2.0f`。 * **构造函数:** * `OvershootInterpolator()` (使用默认 `tension` 2.0f) * `OvershootInterpolator(float tension)` * **用途:** 增加趣味性和弹性,模拟弹簧效果,例如一个窗口弹出时稍微放大再恢复。 --- #### `AnticipateInterpolator` * **导入:** `from android.view.animation import AnticipateInterpolator` * **效果:** 预期插值。动画开始前会先向相反方向移动一点,然后再朝最终目标移动。 * **参数:** * `tension` (float): 控制“后退”的量(张力)。值越大,后退的幅度越大。默认值为 `2.0f`。 * **构造函数:** * `AnticipateInterpolator()` (使用默认 `tension` 2.0f) * `AnticipateInterpolator(float tension)` * **用途:** 模拟“蓄力”或“预备”动作,增加动画的戏剧性或强调开始的动作。 --- #### `AnticipateOvershootInterpolator` * **导入:** `from android.view.animation import AnticipateOvershootInterpolator` * **效果:** 预期超出插值。结合了 `AnticipateInterpolator` 和 `OvershootInterpolator` 的效果。动画先向后退,然后向前冲过目标值,最后回弹到目标值。 * **参数:** * `tension` (float): 同时影响后退和超出的基础张力。默认值为 `2.0f`。 * `extraTension` (float): 额外的张力因子,仅作用于超出部分 (最终超出张力 = `tension * extraTension`)。默认值为 `1.5f`。 * **构造函数:** * `AnticipateOvershootInterpolator()` (使用默认 `tension` 2.0f, `extraTension` 1.5f) * `AnticipateOvershootInterpolator(float tension)` (使用指定 `tension`, 默认 `extraTension` 1.5f) * `AnticipateOvershootInterpolator(float tension, float extraTension)` * **用途:** 创建非常动态、富有弹性和趣味性的动画效果。 --- #### `BounceInterpolator` * **导入:** `from android.view.animation import BounceInterpolator` * **效果:** 弹跳插值。动画在接近结束时会像球落地一样进行几次弹跳,幅度逐渐减小,最后停在目标值。 * **参数:** 无。弹跳的物理模拟是内建的。 * **构造函数:** `BounceInterpolator()` * **用途:** 模拟物体掉落并弹跳的效果,或者给 UI 元素增加活泼、有趣的安顿方式。 --- ### 关于贝塞尔控制点 > **关于贝塞尔曲线** 所谓贝塞尔曲线即 直线外 一个控制点,则可让直线改变为一条曲线. > 1次贝塞尔曲线 = 1组控制点 > 2次贝塞尔曲线 = 2组控制点 > n次贝塞尔曲线 = n组控制点 #### control_points 参数 - control_points = 0 #模拟真实人手抖动 - control_points = 100 # 随机生成 100组控制点 - control_points = [100,100] # 一个控制点100,100,这个就是1次贝塞尔曲线,自己写控制点 - control_points = [100,100,50,50] # 2组控制点, 这就是2次贝塞尔曲线 ## 输入 模拟 **输入文本** - 函数 ```python action.input(msg:str="",selector:Selector=None) ``` - 参数 | 参数 | 类型 | 是否必填 | 说明 | | ------------ | ------------ | ------------ | ------------ | | msg | str | 是 | 输入的内容 | | selector | Selector | 否 | 控件检索规则 `当Selector 为None时,您的输入框必须已经获取了焦点` | - 示例 ```python # 模拟在当前 ‘已获取焦点’的的文本框中输入‘你好 aslib!’ from ascript.android import action action.input("你好 aslib!") ``` ```python # 模拟在 控件检查器查到的 文本框中输入‘你好 aslib!’ from ascript.android import action from ascript.android.node import Selector selector = Selector().id("com.aojoy.airscript:id/search_bar_text") action.input("你好 aslib!",selector) ``` --- ## 触摸 模拟 **手指 按下,移动,抬起** ```python #class action.Touch ``` ### 按下 模拟 **手指按下** ```python action.Touch.down(x, y, dur: int = 20, finger_id: int = 0) ``` - 参数 | 参数 | 类型 | 是否必填 | 说明 | | ------------ | ------------ | ------------ | ------------ | | x | int | 是 | 触摸X点坐标 | | y | int | 是 | 触摸Y点坐标 | | dur | float | 否 | 动作持续时间,默认20毫秒 | | finger_id | int | 否 | 手指编号,默认0。多指触控时用不同编号区分不同手指 | --- ### 移动 模拟 **手指移动** ```python action.Touch.move(x, y, dur: int = 20, finger_id: int = 0) ``` - 参数 | 参数 | 类型 | 是否必填 | 说明 | | ------------ | ------------ | ------------ | ------------ | | x | int | 是 | 触摸X点坐标 | | y | int | 是 | 触摸Y点坐标 | | dur | float | 否 | 动作持续时间,默认20毫秒 | | finger_id | int | 否 | 手指编号,默认0。多指触控时需要和对应的 down/up 保持一致 | --- ### 抬起 模拟 **手指抬起** ```python action.Touch.up(x, y, dur: int = 20, finger_id: int = 0) ``` - 参数 | 参数 | 类型 | 是否必填 | 说明 | | ------------ | ------------ | ------------ | ------------ | | x | int | 是 | 触摸X点坐标 | | y | int | 是 | 触摸Y点坐标 | | dur | float | 否 | 动作持续时间,默认20毫秒 | | finger_id | int | 否 | 手指编号,默认0。多指触控时需要释放对应编号的手指 | ```python # 一个完整的 触摸 操作 from ascript.android import action # 从坐标(100,200)的位置按下 action.Touch.down(299,629) # 按下的同时,移动到坐标(300,300) action.Touch.move(780,645) # 按下的同时,再次移动到坐标(400,350),移动用时 1000毫秒 action.Touch.move(445,1152,1000) # 移动至坐标(450,400)位置 抬起 action.Touch.up(445,1152) ``` ### 多指触控 `finger_id` 用于区分不同手指。同一根手指的 `down`、`move`、`up` 需要使用相同的 `finger_id`。 ```python from ascript.android import action action.Touch.down(300, 800, finger_id=0) action.Touch.down(700, 800, finger_id=1) action.Touch.move(250, 750, 200, finger_id=0) action.Touch.move(750, 850, 200, finger_id=1) action.Touch.up(250, 750, finger_id=0) action.Touch.up(750, 850, finger_id=1) ``` --- ## 按键 模拟 **模拟按键操作** ```python #class action.Key ``` ### Home键 模拟设备 **Home按键** ```python action.Key.home() ``` --- ### 返回键 模拟设备 **返回按键** ```python action.Key.back() ``` --- #### 弹出通知栏 模拟设备 **调出通知栏** ```python action.Key.notifactions() ``` --- ### 任务栏键 模拟设备 **任务栏键** ```python action.Key.recents() ``` --- ### 截屏键 模拟设备 **关闭屏幕** ```python action.Key.screenshot() ``` --- ### 锁屏键 模拟设备 **关闭屏幕** ```python action.Key.lockscreen() ``` --- ## 手指路径 模拟手指 **移动路径** ```python #class from ascript.android.action import Path ``` ### 创建路径 创建一根手指路径, 多根手指创建多个即可. ```python Path(self,start_time:int=0, duration:int=20, will_continue:bool=False) ``` - 参数 | 参数 | 类型 | 是否必填 | 说明 | | ------------ | ------------ | ------------ | ------------ | | start_time | int | 否 | 延迟执行时间 | | duration | int | 否 | 路径执行时常 | | will_continue | bool | 否 | 执行完毕后,是否抬起手指 `True:抬起` `False:不抬起,下一个手势将连续执行` | ```python from ascript.android.action import Path line1 = Path(0,1000); # 移动初始点 line1.moveTo(804,708) # 画直线到点 line1.lineTo(827,1479) # 使用二次贝塞尔曲线 从点(500,800) 到 (250,900) line1.quadTo(500,800,250,900) ``` ### 路径方法 Path 实现了 [android.graphics.Path](https://developer.android.google.cn/reference/android/graphics/Path?hl=en) 中的大部分方法. 具体参数参考:[android.graphics.Path](https://developer.android.google.cn/reference/android/graphics/Path?hl=en) | 支持的绘制方法如下 | | | ------------- | ----:| | moveTo | rCubicTo | | lineTo | rMoveTo | | quadTo | reset | | rCubicTo | rewind | | rQuadTo | addArc | | addCircle | addOval | | addRect | addRoundRect | | arcTo | cubicTo | | setLastPoint | rLineTo | ### 执行路径 - 函数 ```python action.gesture(paths: Union[Path, List[Path]],listener=None) ``` - 参数 | 参数 | 类型 | 是否必填 | 说明 | | ------------ | ------------ | ------------ | ------------ | | paths | [Path|List[Path]](#手指路径) | 是 | 要执行的一组手指路径 | | listener | class | 否 | 手势回调对象, 函数需包含两个函数
`手势执行完毕回调:onCompleted()`, `手势中断回调:onCancelled()` | ```python #案例1:执行一组动作,分别模拟两个手指同时操作 from ascript.android import action from ascript.android.action import Path # 模拟第一根手指 line1 = Path(0,1000); line1.moveTo(804,708) line1.lineTo(827,1479) action.gesture(line1) ``` ### 多指操作 ```python #案例1:执行一组动作,分别模拟两个手指同时操作 from ascript.android import action from ascript.android.action import Path # 模拟第一根手指 line1 = Path(0,1000) line1.moveTo(804,708) line1.lineTo(827,1479) # 模拟第二根手指 line2 = Path(0,1200) line2.moveTo(500,200) line2.lineTo(500,500) action.gesture([line1,line2]) ``` ```python #案例2,执行一组动作,分别模拟两个手指同时操作,并监听动作是否执行结束 from ascript.android import action from ascript.android.action import Path line1 = Path(0,1000) line1.moveTo(200,200) line1.lineTo(200,500) # 创建另一个新的path路径 模拟 手指2(*第二条路径相对于第一条,会延迟500ms启动) line2 = Path(500,1200) line2.moveTo(500,200) line2.lineTo(500,500) class listener: def onCompleted(): print("结束了") def onCancelled(): print("被中断取消了") # 执行 这一组动作 action.gesture([line1,line2],listener) ``` ```python #案例3:连续 执行两个Path路径,中途不松手指 #案例3:连续 执行两个Path路径,中途不松手指 from ascript.android import action from ascript.android.action import Path line1 = Path(0,1000,True); line1.moveTo(288,731) line1.lineTo(644,739) # 创建另一个新的path路径 模拟 手指2 line2 = Path(0,500) line2.moveTo(644,739) line2.lineTo(625,1323) class listener: def onCompleted(): print("执行完毕了") action.gesture(line2) def onCancelled(): print("被中断取消了") action.gesture([line1],listener) ``` --- ## 捕获点击 捕获用户点击屏幕的坐标 - 函数 ```python action.catch_click(msg: str = None, shine: bool = True): ``` - 参数 | 参数 | 类型 | 是否必填 | 说明 | | ------------ | ------------ | ------------ | ------------ | | msg | str | 否 | 捕获屏幕坐标时,要显示的文字.默认无文字显示 | | shine | bool | 否 | 是否闪烁屏幕提示用户. 默认闪烁,False为不闪烁 | - 返回值 返回用户点击的屏幕坐标位置. Point 对象. 包含 x,y 属性.分别代表横竖坐标值 - 示例 ```python # 捕获用户点击屏幕的坐标 from ascript.android import action p = action.catch_click() print(p) ``` ```python # 捕获用户点击屏幕的坐标,给出提示文字,并取消屏幕闪烁 from ascript.android import action p = action.catch_click("请点击屏幕任意位置",False) print(p) ``` --- # 控件检索 · Android 来源:https://www.ascript.cn/docs/android/api/node/app/intro/ # 控件 ```python from ascript.android import node ``` 通过 **控件选择器** 获取屏幕上的 **控件信息** 或 **操控控件** [视频教程快速入门](https://www.bilibili.com/video/BV1HX4y1i7pf?p=11) --- # 运行模式 · Android(无障碍 / 图色模式 / 数字助理 / Root 激活) 来源:https://www.ascript.cn/docs/android/api/mode/ # 模式 > **不同的模式可以`共用一套代码`,**系统会自动用不同模式下的方法去实现相应功能**** 目前 **无障碍模式功能最全,且最稳定.** - [无障碍模式](#无障碍模式) , [图色模式/原HID模式](#图色模式) , [数字助理模式](#数字助理模式) , [Root/激活模式](#root或激活模式) ## 4种模式的区别 | 功能 | 无障碍 | 图色模式(原HID模式) | 数字助理模式 | Root或激活模式| | ------------- |:-------------:| :-----|:-----:|:----| | 动作 | ✅ | 虚拟HID或HID硬件 | ✅ | ✅ | | 控件检索 | ✅ | ❌ | ✅ | ✅ | | 图色检索:
找色,找图,文字识别,目标 | ✅ | ✅ | ✅ | ✅ | | UI | ✅ | ✅
透传模式部分机型不支持 | ✅ | ✅
透传模式部分机型不支持 | | 其他功能 | ✅ | ✅ | ✅ | ✅ | | 权限需求 | 需开启无障碍 | 无需系统权限 | 助理授权 | Root / Shizuku | | 适用场景 | APP类 | 游戏自动化 | 轻量接入 / 合规场景 | APP类 / 游戏自动化 / 模拟器 | | 独立运行 | ✅ | ✅ | ✅ | ✅ | ## 无障碍模式 借助Android 的无障碍访问权限,来实现手机的自动化程序. 该模式较为稳定,且拥有最稳定,最兼容的权限. 该模式下,只需在手机上安装 AScript APP 即可编写运行程序. 安装APP后选择该 “无障碍模式运行” 即可 ## 图色模式 图色模式下,不用开无障碍权限. 可通过 `虚拟HID`,`HID物理硬件` 执行点击,滑动,模拟按键等功能. ### 物理HID硬件魔鼠 使用物理HID硬件魔鼠,可 **不用开启,开发者权限,无障碍权限** 下执行动作. ### 虚拟HID 用电脑模拟虚拟HID设备,来达到执行动作的目的 - 我们可以按照以下方式,来完成HID的配置 #### 1.驱动的安装 - mac 驱动的安装 ``` 命令行 brew install libusb ``` --- - windows驱动的安装 先行下载[usb驱动](https://airscript.oss-cn-hangzhou.aliyuncs.com/hid/libusbK-inf-wizard.exe),并双击打开 安装步骤: ![](/img/model/libusbk_install_step1.jpg) ![](/img/model/libusbk_install_step2.jpg) ![](/img/model/libusbk_install_step3.jpg) ![](/img/model/libusbk_install_step4.jpg) ![](/img/model/libusbk_install_step5.jpg) #### 2.软件安装与启动 根据您的电脑操作系统 下载HID设备管理器软件: | 平台 | | --- | | [win-x64](https://airscript.oss-cn-hangzhou.aliyuncs.com/hid/AScript-win-x64-vpn.exe.zip) | | [macos-x64](https://airscript.oss-cn-hangzhou.aliyuncs.com/hid/AScript-mac-x64-vpn.tar.gz) | | [macos-arm64](https://airscript.oss-cn-hangzhou.aliyuncs.com/hid/AScript-mac-arm-vpn.tar.gz)| | [linux-x64](https://airscript.oss-cn-hangzhou.aliyuncs.com/hid/ASHID-linux-x64)| ![](/img/model/hid_soft_open1.jpg) ![](/img/model/hid_soft_open2.png) #### 3.HID的激活与初始化 ::: danger 激活前注意事项 - 必须关闭开发者模式,否则可能会出现error -3 的错误. ![](/img/model/hid_soft_jh1.jpg) ![](/img/model/hid_soft_jh2.jpg) #### 4.设备的绑定 ::: danger 绑定前注意事项 - 请保持设备和电脑 处于同一个局域网,否则不可用. - 启动APP,选择HID模式 - 绑定IP地址 与 设备ID ## 数字助理模式 类似 `Siri` / `小爱同学` / `Bixby` 的系统数字助理通道——Android 原生为"数字助理类 App"留出的操作权限,AScript 作为数字助理接入后,**无需开启无障碍权限**即可对设备进行点击、控件检索、图色识别等自动化操作。 适合以下几类场景: - 对"开启无障碍权限"有合规顾虑的团队 - 希望用户侧接入流程更轻量(少一步权限开关) - 面向老人 / 无障碍辅助产品的二次开发 ### 启用方式 1. 安装 AScript App 并打开 2. 进入 **"设置 → 默认应用 → 数字助理应用"**(不同品牌路径略有差异,下方已列出主流品牌入口) 3. 将 **AScript** 设为默认数字助理应用 4. 回到 App,选择 `"数字助理模式运行"` 启动脚本 ### 主流品牌入口参考 | 品牌 | 入口路径 | | --- | --- | | 原生 Android / Pixel | 设置 → 应用 → 默认应用 → 数字助理应用 | | 小米 / Redmi | 设置 → 更多设置 → 无障碍 → 默认应用 → 数字助理 | | 华为 / 荣耀 | 设置 → 系统和更新 → 默认应用 → 数字助理 | | OPPO / OnePlus / realme | 设置 → 应用管理 → 默认应用 → 数字助理应用 | | vivo | 设置 → 应用与权限 → 默认应用 → 数字助理应用 | > - 数字助理模式下,**控件检索、动作模拟、图色识别** 均与无障碍模式行为一致,脚本基本无需改动。 - 设备重启后通常不会失效,但个别厂商在系统更新后可能需要重新指定默认数字助理应用。 ## Root或激活模式 ### Root模式 如果您的设备已经具有Root权限. 则拥有最高权限,这种模式不用开启无障碍,对手机无系统版本要求. 适合于模拟器使用. 安装APP后选择该 “Root模式运行” 即可 ### 激活模式 所谓激活模式是指: 给未Root的设备临时升权. 在激活模式下,可同样执行 adb shell指令, 且不用连接usb数据线. > **激活步骤** - 1. 需要安装第三方应用 [Shizuku](https://apt.izzysoft.de/fdroid/repo/moe.shizuku.privileged.api_1049.apk) - 2. 通过adb 或无限调试激活shizuku - 3. 打开AScript程序,选择Root或激活模式 `手机重启后失效.` --- # YOLO 云训练 来源:https://www.ascript.cn/docs/service/yolo-training/ # 模型训练 通过 **YOLO Tools** 可视化工具即可完成模型的本地训练,支持 YOLOv11 / v8 / v5 模型,操作简单,无需手动配置 Python 环境. 训练完成后可直接导出 NCNN 格式模型,放入 AScript 工程即可使用. ## 获取训练工具 ### 1. 下载 AScript 程序坞 前往 [AScript 程序坞](/docs/windows/store/intro) 下载并安装. ### 2. 搜索 YOLO Tools 打开程序坞,在 **AScript STORE** 中搜索 ID: **25** ![AScript Store 搜索](/img/yolo/store_search.png) ### 3. 安装并运行 点击搜索结果中的 **yolo工具**,进入详情页,点击 **立即运行程序系统** 即可启动. ![yolo工具详情](/img/yolo/store_detail.png) --- ## 数据集标注 训练模型之前,需要先准备标注好的数据集. ### 1. 打开标注工具 在 AScript WEB编辑器中,打开 **工具栏 - yolo目标检测 - 标注训练**,进入标注工具. ### 2. 截图或上传图片并打标签 将需要识别的目标截图或上传图片到标注库,然后在图片上框选目标并标注类别名称. 图片越多、场景越丰富,训练效果越好. ![标注工具](/img/yolo/yolo_label_tool.png) ### 3. 导出数据集 标注完成后,点击左上角 **导出** ,选择导出为 **ZIP 压缩包**. 导出的 ZIP 包即为训练所需的数据集. --- ## 训练流程 启动 YOLO Tools 后你会看到如下界面: ![YOLO Tools 界面](/img/yolo/yolo_tools_ui.png) ### 1. 导入数据集 - 点击左侧 **数据集** 标签 - 选择 **导入 ZIP** 导入刚才标注导出的数据集压缩包 - 导入后可预览样本图片和标注框 ### 2. 配置并开始训练 - 点击左侧 **训练** 标签 - 选择模型版本(YOLOv11 / v8 / v5)和大小(Nano ~ XLarge) - 设置训练轮数、批量大小等参数 - 点击 **开始训练** ![训练界面](/img/yolo/yolo_tools_training.png) > **模型选择建议** | 模型大小 | 手机推理速度 | 准确率 | 建议 | |---------|------------|--------|------| | Nano | 很快 (~15ms) | 较低 | 要求极致流畅 | | **Small** | **快 (~30ms)** | **较好** | **推荐手机使用** | | Medium | 一般 (~80ms) | 好 | 高端手机 | | Large / XLarge | 慢 | 很好 | 不推荐手机 | ### 3. 查看训练结果 训练完成后自动跳转到 **结果** 页面,展示: - **训练成绩单** - 一目了然的评分(S/A/B/C) - **各项指标白话解读** - 每项不达标的指标都有具体改进建议 - **结果图表** - 损失曲线、混淆矩阵、验证预测图等 ![训练结果](/img/yolo/yolo_tools_results.png) ### 4. 导出模型 - 点击左侧 **导出** 标签 - 格式选择 **NCNN** - 点击 **导出模型** - 导出完成后自动打开输出目录 ![导出界面](/img/yolo/yolo_tools_export.png) 导出后你会得到两个文件: - `model.ncnn.param` - 模型结构 - `model.ncnn.bin` - 模型权重 将这两个文件放入 AScript 工程的 `res` 目录下,即可加载使用. --- ## 在 AScript 中加载模型 ### Android ```python from ascript.android.system import R from ascript.android import plug plug.load("Yolov11Ncnn") import Yolov11Ncnn as yolo is_init = yolo.init(R.res("model.ncnn.param"),R.res("model.ncnn.bin"),R.res("metadata.yaml")) if is_init: print("初始化成功") res = yolo.detect() print(res) ``` --- # 生态服务 来源:https://www.ascript.cn/docs/service/intro/ # 简介 为开发者提供 **完善的**,**简单** **功能强大** 生态圈 是AS服务的第一原则. ## 有哪些云服务? - 云打包系统 - 云激活码系统 - 云模型训练 - 云数据库 - 超级云控系统 - 公网链接系统