跳到主要内容

第 3 章 · 视觉定位:把一句话变成屏幕坐标

这章讲整个项目工程含量最高的一环:grounding(视觉定位)。读完你能说清「点那个蓝色登录按钮」这句话,是怎么一步步变成鼠标要移动到的精确 (x, y) 的,以及 OS-Atlas 和 ShowUI 两条实现路子的差别。


3.1 它要解决的小问题

agent 决定要「点击 Firefox 图标」,但 sandbox.move_mouse 只认坐标数字。中间缺一步翻译:从「Firefox 图标」这个文字描述 + 当前截图,算出图标在屏幕上的像素位置。这就是 grounding。

为什么单拎出来?因为通用大模型报坐标很不准——它们能认出屏幕上有什么,但说不准「在第 812 个像素」。所以本项目外挂专门为 GUI 定位训练的模型(OS-Atlas、ShowUI)。


3.2 思路 / 直觉

三步:

  1. 问定位模型:把「描述 + 截图」发给 OS-Atlas,它返回一个边界框(bounding box, bbox)——即目标元素的矩形框。
  2. 算中点:矩形框有四个数(左上、右下),取中心当作要点击的点。
  3. 画红点存档:在截图上那个坐标画个大红点存成图片,方便人事后核对「它以为点的是这里」。

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-ATLASosatlas_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-AtlasShowUI
HF Spacemaxiw/OS-ATLASshowlab/ShowUI
返回格式绝对像素 bbox(文本)归一化坐标(0~1 的点)
解析方式正则抠数字取中点ast.literal_eval 解析后 乘以图片宽高 还原像素
代码osatlas_provider.pyshowui_provider.py

ShowUI 返回的是相对坐标(占宽高的比例),所以 ShowUIProvider.extract_norm_pointshowui_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 返回 Nonegrounding.py:22),此时上层拿到的坐标是 Nonemove_mouse 会出错——没有专门兜底。
  • ShowUI 有个潜在 bug。 extract_norm_point 在非字符串分支用了 np.uint8showui_provider.py:22),但文件里没 import numpy——走到那条分支会 NameError。默认路径(image_url 是字符串)不受影响。

3.9 本章代码地图

主题文件符号
坐标解析(抠数字取中点)os_computer_use/grounding.pyextract_bbox_midpoint
画红点存档os_computer_use/grounding.pydraw_big_dot
OS-Atlas 定位客户端os_computer_use/osatlas_provider.pyOSAtlasProvider.call
ShowUI 定位客户端os_computer_use/showui_provider.pyShowUIProvider.callShowUIProvider.extract_norm_point
点击时的调用编排os_computer_use/sandbox_agent.pySandboxAgent.click_element