一句话总结:Computer Use 类 Agent 最常见的失败不是"看错了",是"点偏了"。从模型输出的页面坐标到真实的屏幕坐标,中间要过 DPI 缩放、窗口标题栏、窗口位置三层转换。我按 125% 缩放算了一遍,三层叠加后偏差 225×212 像素——按 32×32 的按钮算,偏出 7 个按钮宽度。
做 Computer Use 的人大概都遇到过这种情况:模型说"我点击登录按钮",日志显示它也点了,但界面上什么都没发生。
你去看截图,发现光标落在了一个空白区域。
这不是模型看错了,是坐标算错了。
一、模型给的坐标是什么坐标系
先说清楚一件事:Computer Use 模型输出的坐标,通常是相对于它看到的那张截图的。
比如截图是 1280×800,模型说"按钮在 (400, 300)",指的是那张图里第 400 列、第 300 行的位置。
而你要点的是屏幕上的那个像素。
这两者之间有转换,而且不止一层。
二、DPI 缩放:最常见的偏差源
第一层是显示缩放。
Windows 默认在 125% 或 150% 缩放下运行。这意味着逻辑像素和物理像素不是 1:1。
截图工具通常按逻辑像素截取——截出来是 1280×800。但屏幕实际的物理分辨率是 1600×1000。
模型在 1280×800 的图上说 (400, 300),落到物理屏幕上就是 (500, 375)。
偏差 (100, 75)。
这一层最容易被忽略,因为在 100% 缩放的机器上开发时,一切正常。换一台 125% 的机器,全部错位。
三、窗口偏移和标题栏
第二层是窗口位置,第三层是标题栏。
浏览器窗口不一定在屏幕左上角。假设它在 (100, 50),那么页面内容整体要加这个偏移。
标题栏和地址栏还占着高度。Chrome 大概是 80 到 90 像素,加上书签栏更多。假设 60(无书签栏、紧凑模式),页面内容的起点在窗口往下 60 像素处。

图 1:每加一层,偏差跳一次;三层加起来,偏出 7 个按钮
把三层叠起来算:
def to_screen(px, py, win_origin, chrome_h, dpr):
"""px,py: 页面内坐标;win_origin: 窗口左上角;chrome_h: 标题栏高度"""
sx = (win_origin[0] + px) * dpr
sy = (win_origin[1] + chrome_h + py) * dpr
return sx, sy
print(to_screen(400, 300, (100, 50), 60, 1.25))
# (625.0, 512.5)
模型说 (400, 300),实际要点的位置是 (625, 512)。
偏差 225 × 212 像素。
一个标准的按钮是 32×32。225 像素相当于偏出 7 个按钮的宽度——点到隔壁去了,或者点到空白。
四、三条坐标偏差怎么一起算
逐层看更清楚:
| 叠加到哪一层 | 屏幕坐标 | 累计偏差 |
|---|---|---|
| 只算页面坐标 | (400, 300) | (0, 0) |
| + DPI 1.25 | (500, 375) | (100, 75) |
| + 标题栏 60 | (500, 450) | (100, 150) |
| + 窗口 (100, 50) | (625, 512) | (225, 212) |
注意第二行:DPI 缩放对 x 和 y 都乘了 1.25,但 y 还要额外加标题栏,所以 y 的偏差比 x 大。
这就是为什么很多 Agent 的点击是"右下偏"——不是随机偏,是有规律的偏。
五、修复代码
反向转换,从屏幕坐标还原页面坐标:
def to_page(sx, sy, win_origin, chrome_h, dpr):
"""把屏幕坐标还原成页面坐标"""
px = sx / dpr - win_origin[0]
py = sy / dpr - win_origin[1] - chrome_h
return px, py
print(to_page(625, 512, (100, 50), 60, 1.25))
# (400.0, 300.0)
关键在于参数的顺序:屏幕坐标是先除 DPI 再减偏移,不能反过来。
如果按"先减再除"算:(625 - 100) / 1.25 = 420,错了 20 像素。
这个顺序错误很隐蔽,因为在偏移为 0 的时候两种算法结果一样。只有窗口不在左上角时才暴露。
六、坑清单
1. DPR 不是 1,也不是固定值。 它跟着系统缩放设置走,用户的机器可能是 100%、125%、150%、175%。别写死 1.25,运行时读 window.devicePixelRatio 或者 Win32 的 DPI 接口。
2. 标题栏高度会变。 全屏、无边框窗口、标签页在顶部还是侧边,都会影响这个值。稳妥做法是从窗口内容区的实际位置取,不要用常量。
3. 多显示器要算相对坐标。 副屏的左上角不是 (0,0),可能是 (-1920, 0)。所有坐标要基于"所在显示器的原点"来算。
4. 截图和点击要用同一套坐标基准。 如果截图按物理像素存(1600×1000),模型给的坐标就要按物理像素理解,不能再乘 DPR。混用会导致双重缩放。
5. 页面滚动是第四层。 我只算了三层。如果页面滚动了 500 像素,元素在页面内的位置不变,但在视口内的位置变了。截图反映的是视口,所以要加上滚动偏移。
七、边界
这篇算的是"窗口在桌面上的坐标转换",适用于桌面自动化。
浏览器内的自动化通常有更简单的路径——用 CDP(Chrome DevTools Protocol)能直接拿到元素的 DOM 坐标,不用算这些。
另外我用的 60 像素标题栏是估算值,实际依浏览器和设置而定。真实的偏差要先测一次,不能照搬我的数字。
无障碍树那篇给了另一条路:不靠坐标,靠元素的 ID 和角色定位。这条路绕开了所有坐标转换的问题,但要求软件支持无障碍接口。
通过率那篇讲的是怎么判断"做对了没",这篇讲的是"为什么点不中"。两个问题经常同时出现——点偏了,任务自然不通过。
结语
坐标这件事,说起来是小学数学,做起来到处是坑。
我的建议是:别自己算。 能用系统的坐标转换接口就用,能用元素定位就用元素定位。手算 DPI 加偏移,早晚会在某台机器上翻车。
如果非要算,记住一句话:顺序比公式重要。 先除还是先减,差一个数量级。
参考资料
- Microsoft:高 DPI 桌面应用开发 - DPI 感知与坐标转换
- Chrome DevTools Protocol - 元素坐标的获取方式
- agent-desktop 无障碍树方案 - 绕开坐标的另一条路
- 参考资料
发表评论
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。