数据截至 (上游 commit 610bac85d242)
第 3 章 · 视觉定位:把一句话变成屏幕坐标
这章讲整个项目工程含量最高的一环:grounding(视觉定位)。读完你能说清「点那个蓝色登录按钮」这句话,是怎么一步步变成鼠标要移动到的精确
(x, y)的,以及 OS-Atlas 和 ShowUI 两条实现路子的差别。
3.1 它要解决的小问题
agent 决定要「点击 Firefox 图标」,但 sandbox.move_mouse 只认坐标数字。中间缺一步翻译:从「Firefox 图标」这个文字描述 + 当前截图,算出图标在屏幕上的像素位置。这就是 grounding。
为什么单拎出来?因为通用大模型报坐标很不准——它们能认出屏幕上有什么,但说不准「在第 812 个像素」。所以本项目外挂专门为 GUI 定位训练的模型(OS-Atlas、ShowUI)。
3.2 思路 / 直觉
三步:
- 问定位模型:把「描述 + 截图」发给 OS-Atlas,它返回一个边界框(bounding box, bbox)——即目标元素的矩形框。
- 算中点:矩形框有四个数(左上、右下),取中心当作要点击的点。
- 画红点存档:在截图上那个坐标画个大红点存成图片,方便人事后核对「它以为点的是这里」。
3.3 图示:一次 click 的完整落地
怎么读:从左到右是
click("登录按钮")被调用后发生的事。grounding 模型和坐标解析是中间两环。
click("登录按钮")
│
▼
click_element: 重新截图 ──▶ grounding_model.call("登录按钮", 截图)
│
▼
OS-Atlas 返回 bbox 文本
"<|box_start|>(x1,y1),(x2,y2)<|box_end|>"
│
▼
extract_bbox_midpoint: 正则抠数字 → 取中点
│ (x, y)
▼
draw_big_dot: 截图上画红点存档(人可核对)
│
▼
sandbox.move_mouse(x, y) → left_click()
3.4 原理演示(示意,非源码)
坐标解析是这章的核心小算法。它要同时应付「模型直接给两个数 (一个点)」和「给四个数(一个框)」两种情况:
# 示意,非源码:从模型回复里抠出点击点
def bbox_to_point(response):
nums = find_all_numbers(response) # 从文本里正则抓出所有数字
if len(nums) == 2: # 模型直接给了一个点
return nums[0], nums[1]
if len(nums) >= 4: # 模型给了一个框 → 取中心
return (nums[0] + nums[2]) // 2, (nums[1] + nums[3]) // 2
return None # 抠不出来,放弃
重点看:它对模型输出很宽容——不管有没有 <|box_start|> 包裹、不管是点还是框,只按「抓到几个数」来决定怎么算。
3.5 真实实现
坐标解析:extract_bbox_midpoint
在 os_computer_use/grounding.py:13-22。它先用正则剥掉 OS-Atlas 特有的 <|box_start|>…<|box_end|> 标记(grounding.py:14),再用 re.findall(r"\d+\.\d+|\d+", ...) 抓所有整数/小数(grounding.py:16),然后按数字个数决定返回点还是框中点(grounding.py:17-22)。四个数时用整除 // 取中点,得到整数坐标。
OS-Atlas 客户端:OSAtlasProvider.call
在 os_computer_use/osatlas_provider.py:23-33。它不是走 OpenAI 那套,而是通过 Gradio client 调用一个 HuggingFace Space(maxiw/OS-ATLAS,osatlas_provider.py:8-9):
# 真实源码 osatlas_provider.py:24-32
result = self.client.predict(
image=handle_file(image_data),
text_input=prompt + "\nReturn the response in the form of a bbox",
model_id=OSATLAS_HUGGINGFACE_MODEL,
api_name=OSATLAS_HUGGINGFACE_API,
)
position = extract_bbox_midpoint(result[1])
注意它在 prompt 后面硬拼一句 "Return the response in the form of a bbox",主动要求模型返回边界框格式,好让上面的解析器接得住。
画红点:draw_big_dot
在 grounding.py:5-10,用 PIL 在坐标处画一个半径 12 的红色实心圆。click_element 拿它把「打点后的截图」存进临时目录(sandbox_agent.py:122-124),成为可回看的定位证据。
3.6 两条实现路:OS-Atlas vs ShowUI
项目提供两个 grounding provider,接口一样(都有 call(prompt, image_data))但内部约定不同:
| 维度 | OS-Atlas | ShowUI |
|---|---|---|
| HF Space | maxiw/OS-ATLAS | showlab/ShowUI |
| 返回格式 | 绝对像素 bbox(文本) | 归一化坐标(0~1 的点) |
| 解析方式 | 正则抠数字取中点 | ast.literal_eval 解析后 乘以图片宽高 还原像素 |
| 代码 | osatlas_provider.py | showui_provider.py |
ShowUI 返回的是相对坐标(占宽高的比例),所以 ShowUIProvider.extract_norm_point(showui_provider.py:18-29)要 point[0] * image.width, point[1] * image.height 换算回像素——这是两者最实质的差别。
3.7 巧妙之处
- 把「模型输出格式的脏活」关进解析器。
extract_bbox_midpoint用正则兼容多种返回(有无特殊标记、点或框),让上层不用管模型到底怎么回话。 - 主动要格式。 OS-Atlas 调用时硬拼
"Return the response in the form of a bbox",用 prompt 约束输出,降低解析失败率。 - 可视化校验。 每次点击都存一张画了红点的图,出错时能一眼看出「模型是不是点歪了」——调试 computer-use agent 极其关键。
3.8 边界与局限
- 依赖公共 HF Space。 grounding 走的是 HuggingFace 上的托管 Space(
Client(...)),受其可用性和限流影响;README 建议配HF_TOKEN绕开 Gradio 限流。 - 解析可能返回
None。 数字不是 2 个也不是 ≥4 个时extract_bbox_midpoint返回None(grounding.py:22),此时上层拿到的坐标是None,move_mouse会出错——没有专门兜底。 - ShowUI 有个潜在 bug。
extract_norm_point在非字符串分支用了np.uint8(showui_provider.py:22),但文件里没 import numpy——走到那条分支会NameError。默认路径(image_url 是字符串)不受影响。
3.9 本章代码地图
| 主题 | 文件 | 符号 |
|---|---|---|
| 坐标解析(抠数字取中点) | os_computer_use/grounding.py | extract_bbox_midpoint |
| 画红点存档 | os_computer_use/grounding.py | draw_big_dot |
| OS-Atlas 定位客户端 | os_computer_use/osatlas_provider.py | OSAtlasProvider.call |
| ShowUI 定位客户端 | os_computer_use/showui_provider.py | ShowUIProvider.call、ShowUIProvider.extract_norm_point |
| 点击时的调用编排 | os_computer_use/sandbox_agent.py | SandboxAgent.click_element |