机器被碰了
撞到了机械臂,或手机在支架里移了位,于是映射 A 现在错了——毫米不再对应同样的屏幕分数。 点击会稳定地偏同一个量。
一次点击能落在该落的地方,是因为 PhysiClaw 一次性学会了三个坐标系如何对齐:大脑的屏幕分数、 相机的像素,以及机械臂的毫米。标定就是那半个钟头——机器在这段时间里测量这些关系, 把它们烘焙进两个小矩阵;此后每一次点击,无非是把一个边界框中心穿过它们做乘法。本页讲的是直觉, 外加一点点数学——写给想理解它、而不是要重新实现它的读者。
一次点击碰到的一切,都活在三个空间之一里,而标定的全部工作就是在它们之间翻译:
screen 0–1 camera pixels arm millimetres (what the brain (what the lens (what the gantry reasons in) actually sees) actually moves in)
[0.55, 0.59] ──A──► ─────────────── ──?──► (gx, gy) mm ▲ │ └─────────────── B ◄────────── pixel ────┘ listing comes back in 0–1, from camera pixels(0,0) 是手机屏幕的左上角,(1,1) 是右下角。大脑只讲这一种话。PhysiClaw 学两张映射来打通它们:A(屏幕 → 机械臂)让它能动手,B(屏幕 → 相机)让它能把 相机画面读回成屏幕分数。
当大脑说 tap [0.51, 0.55, 0.59, 0.63] 时,真正的算术是这样的:
边界框 → 中心。 把四角取平均:cx = (0.51 + 0.59) / 2 = 0.55、
cy = (0.55 + 0.63) / 2 = 0.59。你瞄的是框的正中。
屏幕 → 毫米(映射 A)。 把这个 (0.55, 0.59) 穿过屏幕→机械臂矩阵,得到以毫米计的
GRBL 坐标 (gx, gy)。
移动并击下。 龙门架快速移到 (gx, gy);solenoid 触发;笔尖落下。
screenshot 那条路有一段是反着走的:相机看到的屏幕是以像素计的,而映射 B(求逆后)
把那些像素转回 0–1 屏幕分数,于是大脑读到的清单,和它将要点击的边界框处在同一个空间里。
A 用来动手,B 用来看见。
两张映射都是仿射变换——能拉伸、旋转、错切、平移一个平面,同时让直线保持笔直的最简单映射。 每张都存成一个 2×3 矩阵,像这样作用(这里画的是映射 A):
┌ gx ┐ ┌ a b c ┐ ┌ screen_x ┐│ │ = │ │ · │ screen_y │└ gy ┘ └ d e f ┘ └ 1 │就是六个数。a b / d e 这一块处理缩放、旋转和错切——屏幕 X 走一个单位,机械臂 X 要走多少,
以及手机相对龙门架有多歪。c f 那一列只是偏移——屏幕 (0,0) 落在机械臂空间的哪里。
六个数就够了,因为一台平放在头顶相机下的手机,非常接近一个被正面平视的平面:没有透视畸变要建模,
只有一点倾斜加拉伸。这正是为什么寥寥几个测量点就能钉住整个屏幕。
机械臂看不见屏幕,所以它靠点击并倾听来求屏幕→机械臂映射:手机会报回每次触摸究竟落在哪里
(以屏幕 0–1 计),机械臂知道自己当时在毫米空间的哪个位置,足够多这样的配对就能定下那张仿射。
探针三角(3 次点击)。 在机械臂原点 (0,0) 点一下,再沿 X +10 mm 点一下,再沿 Y
+10 mm 点一下。三个点是引导出一张粗略仿射的最少数量——刚好够预测屏幕其余部分在哪里。
网格(15 次点击)。 用那张粗略映射,机械臂走遍铺满屏幕的 3 列 × 5 行网格,逐格点击。 每一次点击都给出又一对精确的(毫米,屏幕分数)配对。
拟合 + 重定原点。 全部 18 对用最小二乘拟合出最终那张精确的映射 A。机械臂随后把屏幕中心
重新声明为它的 (0,0),于是整个坐标系锚定在手机上,而不是锚在龙门架碰巧回零的那个位置。
因为笔尖是一个固定行程的 solenoid,没有触摸深度要找——一次没点中只意味着接触不稳,所以机器干脆
重新击一下。这次拟合还顺带产出一个免费诊断:一个倾斜比,衡量手机相对机械臂行程有多歪。
接近 0 说明手机和龙门架是正的;数值偏高就是在警告你把它摆正。
映射 B(屏幕 → 相机)是反过来学的:手机显示一个已知图案,相机看。桥接页面点亮同样的
15 点网格——位于已知屏幕分数上的亮红点——头顶相机检测出每一个点的像素位置。十五对
(屏幕分数,相机像素)用最小二乘拟合出映射 B。现在服务器就能把任意相机像素转回屏幕分数,
而这正是 peek 报出一份清单所需要的。
每两个动作之间,机械臂都退到一个固定点位,就在手机外一点——屏幕分数 (-0.1, -0.05),
也就是左上角再往左、往上一点。屏幕外的坐标在这里完全合法:把 (0.55, 0.59) 转成毫米的那张映射 A,
也照样把 (-0.1, -0.05) 转成毫米——仿射变换并不在乎这个点已经越过了屏幕边缘。这个停靠点重要有两个
原因:它让触控笔退出相机视野,好让下一次 peek 不被遮挡;它也是一个已知、可复现的歇脚位置,
重启之后机器可以重新锚定到它。
两张映射都假设相机、机械臂和手机都精确地待在测点时所在的位置。标定会自我验证:它点几个随机点,
检查误差是否保持在屏幕宽度的 0.015 以内——在一块 390 像素宽的屏幕上大约是 5 个像素。
任何挪动了机器某个部件的东西都会让这个前提失效,点击便开始落偏:
机器被碰了
撞到了机械臂,或手机在支架里移了位,于是映射 A 现在错了——毫米不再对应同样的屏幕分数。 点击会稳定地偏同一个量。
相机动了
碰了或重新对焦了头顶相机,于是映射 B 现在错了——像素映射到了错误的分数,所以早在机械臂 动起来之前,大脑瞄的就是一个略微错位的目标。
破绽是一种系统性的偏差:点击朝同一个方向一致地偏开一段固定距离,而不是随机乱偏。那是一张 过期的变换,不是一张不稳的,修法是重新标定——重测那些点, 让矩阵对上机器的新现实。把一切都装得稳稳当当,你就很少需要这么做。