# 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`
---
## 关于找色方向
找色方向是指:**找到颜色特征点的先后顺序**
如图:

- 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),并双击打开
安装步骤:





#### 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)|


#### 3.HID的激活与初始化
::: danger 激活前注意事项
- 必须关闭开发者模式,否则可能会出现error -3 的错误.


#### 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**

### 3. 安装并运行
点击搜索结果中的 **yolo工具**,进入详情页,点击 **立即运行程序系统** 即可启动.

---
## 数据集标注
训练模型之前,需要先准备标注好的数据集.
### 1. 打开标注工具
在 AScript WEB编辑器中,打开 **工具栏 - yolo目标检测 - 标注训练**,进入标注工具.
### 2. 截图或上传图片并打标签
将需要识别的目标截图或上传图片到标注库,然后在图片上框选目标并标注类别名称.
图片越多、场景越丰富,训练效果越好.

### 3. 导出数据集
标注完成后,点击左上角 **导出** ,选择导出为 **ZIP 压缩包**.
导出的 ZIP 包即为训练所需的数据集.
---
## 训练流程
启动 YOLO Tools 后你会看到如下界面:

### 1. 导入数据集
- 点击左侧 **数据集** 标签
- 选择 **导入 ZIP** 导入刚才标注导出的数据集压缩包
- 导入后可预览样本图片和标注框
### 2. 配置并开始训练
- 点击左侧 **训练** 标签
- 选择模型版本(YOLOv11 / v8 / v5)和大小(Nano ~ XLarge)
- 设置训练轮数、批量大小等参数
- 点击 **开始训练**

> **模型选择建议**
| 模型大小 | 手机推理速度 | 准确率 | 建议 |
|---------|------------|--------|------|
| Nano | 很快 (~15ms) | 较低 | 要求极致流畅 |
| **Small** | **快 (~30ms)** | **较好** | **推荐手机使用** |
| Medium | 一般 (~80ms) | 好 | 高端手机 |
| Large / XLarge | 慢 | 很好 | 不推荐手机 |
### 3. 查看训练结果
训练完成后自动跳转到 **结果** 页面,展示:
- **训练成绩单** - 一目了然的评分(S/A/B/C)
- **各项指标白话解读** - 每项不达标的指标都有具体改进建议
- **结果图表** - 损失曲线、混淆矩阵、验证预测图等

### 4. 导出模型
- 点击左侧 **导出** 标签
- 格式选择 **NCNN**
- 点击 **导出模型**
- 导出完成后自动打开输出目录

导出后你会得到两个文件:
- `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服务的第一原则.
## 有哪些云服务?
- 云打包系统
- 云激活码系统
- 云模型训练
- 云数据库
- 超级云控系统
- 公网链接系统