CORE-05 · CALIBRATION TRACK · 综合专题(第 4 块 · 课程收尾)

标定专题:五个部分,让机械臂「对齐世界」

模型、协议、运动学都对了,抓取为什么还会差几毫米到几厘米?因为从"电"到"眼"的每一层都有出厂偏置。本专题按五步走:① 机械臂零位标定 → ② TCP 标定(四点法)→ ③ 相机内参标定 → ④ 手眼标定 → ⑤ 抓取验证——全部对应本仓库 ros2_ws 里现成的工具、配置与讲义步骤,每部分配一个可调交互,最后把 5mm、>85% 这些散落数字串成一条合格链。

① 零位② TCP 四点法③ 相机内参④ 手眼 AX=XB⑤ 抓取验证2~4 学时 · 设计性
INTRO & OUTLINE · 为什么标 / 五个部分

误差四象限与五部分总览

教学目标:拿到"抓偏了"的反馈时,能先分类再动手——是零点漂了、工具点没算对、尺子错了,还是"眼"装的位置想错了。

症状像什么归属象限对应部分
发"全零"指令臂不笔直/不水平整臂系统性歪一个固定角① 电零位第 1 部分 · 机械臂零位标定
换个姿态抓,落点方向跟着绕圈偏固定方向准、换姿态就画圆② 工具点(TCP)第 2 部分 · TCP 标定(四点法)
视觉算出的目标随距离越远越偏近处还行、远处越抓越歪③ 光学内参第 3 部分 · 相机内参标定
不同观察姿态下整片区域平移偏换个姿势看,偏移方向跟着变④ 空间外参(手眼)第 4 部分 · 手眼标定
单项都"应该"好了,真机还是抓不稳流程/时序/统计层面的问题— 系统级第 5 部分 · 抓取验证
范围声明:杆长/装配级的运动学几何标定(link1/link2 微米级复测)依赖精密量具,本专题只在第 5 部分以闭环复核的形式把关,不展开标定流程;深度的 scale·d+offset 校正是手眼标定同一次会话的免费副产物,在第 4 部分顺带介绍(无需额外设备)。TCP 采用四点法——一支尖笔 + 纸即可完成,无需精密量具。
部分教学目标案例(工具/出处)可视化 / 交互方案衔接
1 机械臂零位标定 理解"电中位≠机械中位",会跑 servo_calib 五步并解释 offset 生效边界 calibration/servo_calib_gui.py + servo_offsets.yaml(PWM−1500);讲义实验3 步骤3c J2/J3 零位偏移滑块 → 侧视连杆"指令目标 vs 实际姿态"对比,Apply 校准一键对齐 EXP-05 §08 · §10
2 TCP 标定(四点法) 明白 TCP 是"法兰之后的等效工具点";会用四姿态顶同一尖点解出偏移;解释 FINGERTIP 0.033 的来历 kinematics/config.py+transform.py:FINGERTIP_OFFSET_Z=0.033 / Y_TCP=0.215mm / TCP_OFFSET_Z=0.0762;讲义实验5 标定工具清单 四姿态法兰位姿示意 + Δ 偏移滑块 → 尖点弥散圈实时 mm 读数;"四点法求解"最小二乘一键塌缩到同一点 EXP-06 §07 · 原理 M5
3 相机内参标定 读懂 K 矩阵每个数的物理含义;会评估"内参错了多少、抓取点偏多少" HP60C 出厂 K(fx=fy=614, cx=320, cy=240)+ PLUMB_BOB(CameraPublisher.cpp);讲义实验7 7.2.2 针孔俯视图 + Δfx 误差注入滑块 → 同一像素在 0.3/0.6/1.0m 处的落点偏差直读(mm) EXP-07 §07 · 原理 M6
4 手眼标定 说清 eye-in-hand 要解的 X 是什么;懂 14 姿态怎么设计;会跑 采集→求解→验证→接入 四步 eye_hand_calibration 全流程(AprilTag/PnP、Tsai-Lenz、14 姿态 yaml、验收 5mm);讲义实验7 步骤4"未接入"警告 位姿配对变换链示意;旋转多样性/姿态数滑块 → n(n−1)/2 配对数与条件评分;深度 scale·d+offset 拟合台;接入两条路 EXP-07 §08 · 原理 M1
5 抓取验证 把零位→TCP→内参→手眼→真机成功率串成一次可执行的验收流程;知道何时该重标哪一层 joint_command 归零 / 尖点重合 / camera_info+卷尺 / verify(5mm) / EXP-07 基线(>85%) 五步验收清单(勾选推进度条)→ 全绿输出结论卡 + "何时重标"事件速查表 EXP-04 §08 · EXP-07 §08
PART 1 · 机械臂零位标定

1 · 机械臂零位标定:机械中位不等于信号 1500

教学目标:① 解释装配后"电零点"系统性偏移的成因与症状;② 跑通 servo_calib 五步流程;③ 明白 offset 写进 yaml 后的生效边界;④ 掌握"粗调→精调"迭代与回滚。

🎯 案例(代码 · calibration 包 + 讲义实验3 步骤3c):ros2 launch calibration servo_calib.launch.py 打开 5 关节面板 → 拖 PWM 滑块找到关节"机械中位"(如大臂真水平时 PWM=1585,而非 1500)→ Save 把 当前 PWM − 1500 = offset 写入 servo_offsets.yaml → 发全零 joint_command 验证归零笔直。讲义口径:校准是渐进的,先粗后精多轮;改前备份 cp servo_offsets.yaml servo_offsets.yaml.bak。 出处:ros2_ws/.../calibration/calibration/servo_calib_gui.py("保存偏移:当前 PWM 与中心 1500 的偏差")+ 讲义实验3 · 步骤3c(含坑表:SSH 无显示、Apply 需 arm_controller 在线)。

交互 · 不校准 vs 已校准(侧视姿态对比)

流程与边界(代码事实)

  1. 选关节:joint1~joint5 标签页,面板读 joint_states 实时显示。
  2. 拖滑块找机械中位:肉眼 + 水平仪(讲义标定工具清单)判"真水平/真笔直",慢速逼近——多轮"粗调→精调"。
  3. Save:offset = 当前 PWM − 1500,写 config/servo_offsets.yaml(src 与 install 两份)。
  4. 归零验证:发 /arm_controller/joint_command 全零,看臂是否笔直(讲义明确:用 joint_command 而不是 target_pose)。
  5. 回滚:cp servo_offsets.yaml.bak servo_offsets.yaml 恢复。
δ (μs) → 角度偏差 = δ × 0.002356 rad/μs  例:+70μs ≈ +9.4°
校准后发送:PWM_send = PWM_target(rad 转来) − δ // δ = 实际中位 PWM − 1500,所以发送时要把它减掉
为什么"±9.4°"在指尖会放大成几厘米:J2 的偏差会把整条大臂+小臂+工具(0.126+0.126+0.151 ≈ 0.40 m)一起转过去,臂越远放大越多——顶端 9.4° 的角误差 ≈ 0.40 × 0.164 rad ≈ 6.6 cm;J3 只影响小臂+工具(≈0.28 m),同样 9.4° 也有 ≈4.5 cm。这就是"手臂看着只歪一点点、抓取却偏一大截"的原因:角误差在关节处很小,在末端被连杆长度放大。
反过来给一个现场口径:想让末端控制在 5 mm 内,J2 允许角误差 ≈ 5 mm ÷ 403 mm ≈ 0.0124 rad ≈ 0.71°≈ 5 μs——5 μs 只有 1 个 PWM 刻度(舵机理论分辨率约 2.4 μs)的两倍,所以校准"再来一轮精调"不是吹毛求疵,而是被几何放大的必然要求。
生效边界(读码发现,务必讲到):全仓库 servo_offsets.yaml 只被 calibration 包的 GUI/服务读写,arm 控制链(arm_controller / kinematics)并不自动加载它——所以① GUI 里的 Apply 是"会话内补偿";② 长期生效要回到第 4 步"归零验证 + 各链路各自偏置";③ 这正是讲义坑表"校准后仍不齐 → 多轮迭代"的深层原因。
和第 4 部分(手眼)的顺序:零位管"每个关节从哪算 0",手眼管"相机装在哪"。零位没校,手眼标定时采的每个 T_ee 都带着同样的歪——AX=XB 会把一部分歪悄悄吸收进 X 里,换个位姿又露馅。所以顺序永远是:先第 1 部分,再第 4 部分。
PART 2 · TCP 标定(四点法)

2 · TCP 标定(四点法):工具尖端在哪,落点就该在哪

教学目标:① 理解 TCP(Tool Center Point)= 法兰坐标系之后的等效工具点,不标 = 换姿态就画圆;② 掌握四点法原理与最少条件(≥4 组不同姿态、同一尖点);③ 会做四点法的最小二乘解算与弥散验收;④ 看懂仓库里 0.033 / 0.215 这些 TCP 常数从哪来。

🎯 案例(代码 · kinematics/config.py + 讲义实验5 工具清单):仓库里的 TCP 不是理论值而是标定产物:FINGERTIP_OFFSET_Z = 0.033(实测值:指尖在 tcp_link 下方 33 mm)、TCP_OFFSET_Z = 0.0762(实测 76.25 mm)、Y_TCP = 0.215 mm(IK 里那个 asin 补偿的真身)。注意别把它们和 gripper_depth 混为一谈:33 mm 是"指尖比 tcp_link 低多少"的几何常数;gripper_depth 是"抓取点相对检测面再挪多少"的运行参数(yaml 实配 −0.040 是抬高 40 mm)。两者单位都是米,含义完全不同——很多同学会以为 −0.033 就是来自这 33 mm,那是巧合,不是因果。讲义实验5 的标定工具清单:刻度标定尺、水平仪——四点法只需再加一支可固定的尖笔/锥销,把它顶在同一纸上小孔里转四个姿态即可。 出处:ros2_ws/.../arm/kinematics/kinematics/config.py(LEVEL3_SUMMARY 称其 "Robot Calibration Parameters")+ TESTING_GUIDE.md"指尖位于 tcp_link 下方 33mm";讲义实验5·1.3 实验环境标定工具行。
  1. 装尖点:把尖笔(或锥销)固定在夹爪中央,代替正常工件——现在"工具尖"就是待标定的 TCP。
  2. 顶四个姿态:控制机械臂从 ≥4 个明显不同的姿态(俯仰至少差 30°+)让尖端触到同一个图钉孔/纸上小点,每到位一次记录关节角。
  3. FK 算法兰:每个姿态用正运动学得到法兰原点 p_i 与姿态 R_i(这就是 demo 里点 P₁~P₄ 的由来)。
  4. 解四点方程:四组 p_i + R_i·t = c(c 未知、是同一个尖点)——两两相减消 c,最小二乘解工具偏移 t = (t_x, t_z, t_y),写回 config 的 TCP 常数。
  5. 弥散验收:再换任意姿态重复顶点,若计算出的尖端都落在同一点(弥散 ≤ 纸上一点),TCP 标定通过。

交互 · 四点法台:姿态一换,未标的偏移就"画圆"

每个姿态 i:已知 p_i, R_i(FK),求 t(TCP 偏移) 使下式对所有 i 成立:
   p_i + R_i · t = c(同一个尖点,未知)
≥4 组 → 两两相减消 c → 线性方程组最小二乘解 t(本台 2D 简化:平面 p+t)

读图与判定

怎么看:灰色小方块 = 四个姿态下的法兰原点(FK 得到、彼此不同);红叉 = 用"你以为的 TCP"推算出的四个尖端位置——只要 Δz′ ≠ 真实 Δz(或 Δx 不为 0 时算错),四个红叉就摆成一个圆(画圆半径 ≈ 偏移误差)。点"四点法求解"后四个红叉塌缩到黑点(同一尖点),弥散读数归零——这就是标定完成的判据。
为什么恰好能解:n 组位姿给 n 个向量方程,未知量只有 t(和 c)。两两相减把 c 消掉后,未知数个数不再随 n 增长——数据越多,最小二乘越抗单次 FK 误差。姿态差异越大(俯仰换着来),方程组越"硬",条件数越好——这和第 4 部分手眼标定要"旋转多样性"是同一个道理。
接回仓库:解出 Δz 写进 FINGERTIP_OFFSET_Z 这类常数后,EXP-06 下探 z−gripper_depth 才真正落到物体表面;不标 TCP,六阶段里"下探"深度全靠运气。gripper_depth 滑块在 EXP-06 §07 参数调优台。
现场器材再降:没锥销就用图钉 + 硬纸板;工厂级四点法同理,只是位姿由示教器记录。本方法只依赖 FK 已准——所以做完第 1 部分零位、并且闭环(test_fk_ik)没有大偏差,四点法的结果才可信。
PART 3 · 相机内参标定

3 · 相机内参标定:把像素换算成毫米的那把"尺子"

教学目标:① 读懂 K 矩阵每个数的物理含义(fx/fy 焦距、cx/cy 光心);② 明白出厂标定的边界(何时必须重标);③ 能算"内参偏了 Δ,抓取点偏多少"——把抽象误差变成毫米。

🎯 案例(代码 · HP60C 驱动 + 讲义实验7 7.2.2):相机驱动 CameraPublisher.cpp 出厂即发布 camera_info:K = [614, 0, 320; 0, 614, 240](640×480、PLUMB_BOB 五畸变系数);抓取链读它做反投影,AprilTag 检测还用畸变系数做 PnP(第 4 部分手眼的前置)。验证读法:ros2 topic echo /camera/color/camera_info --once。 出处:ros2_ws/.../examples/camera/HP60C/src/ascamera/src/CameraPublisher.cpp(distortion_model=PLUMB_BOB);讲义实验7 步骤3 反投影公式与"内参从 camera_info 的 K 矩阵获得"。

交互 · 内参误差注入:Δfx 一动,落点立刻偏

真值:X = (u−cx)·d / 614 = — m
误用内参:X′ = (u−cx)·d / 614 = — m  →  落点偏差 |X′−X| = 0.0 mm

三档距离直读 + 判定

距离真值 X误用后 X′偏差
规律:Δfx 固定时,偏差随距离 线性变大——所以"近处能抓、一放远就歪"第一嫌疑人就是 fx 相对真实焦距偏了(对焦变了、镜头摔过、驱动 K 没对上实际出图分辨率)。现场判定就用这招:同一点位移近/中/远三次,远端先偏 → 怀疑内参。
什么时候必须重标:跌落/撞击后、更换或松动镜头、相机出图分辨率或 ROI 改变。方法论(概念层):打印棋盘格/AprilTag 板,多张不同位姿照片 → ROS camera_calibration 包最小二乘解 K+畸变 → 写回驱动或 camera_info。本车 K 为出厂值,日常只做"读取 + 距离合理性复核"(第 5 部分卷尺抽查互为印证)。
连带概念——畸变:PLUMB_BOB 用 k1/k2/p1/p2/k3 五系数描述镜筒"枕形/桶形"弯曲。边缘像素先去畸变再套针孔公式;AprilTag 的 PnP 就是这么算位姿的。内参错 = 尺子弯了——长度和弯都要说对。
PART 4 · 手眼标定

4 · 手眼标定 AX=XB:解"相机装在哪"

教学目标:① 说清 eye-in-hand 下待求的 X = T_cam_ee 是什么、方程怎么成立;② 理解 14 个采集姿态为什么那样设计(旋转多样性);③ 跑通 采集→求解→验证→接入 全流程,并知道讲义"未接入"的坑怎么填。

🎯 案例(代码 · eye_hand_calibration 包全流程 + 讲义实验7 步骤4):桌面中央贴 AprilTag(臂前 ~25cm)→ 14 个位姿逐个到位(calibration_poses.yaml:pitch 3.14/2.80/2.44 三档、左右 ±6cm、高低错落;注释原话"AX=XB 需要足够的旋转多样性 >60°,相邻 >15°",ALL_PAIRS 全配对、最小旋转 8°、有效位姿 ≥10)→ compute_hand_eye_transform 解方程(Tsai-Lenz 默认 / Horaud,顺带拟合深度 scale·d+offset)→ verify_calibration 换个位置放板报 3D 误差,合格阈值默认 5.0mm。⚠ 讲义原话:标定会话与深度校正"当前未接入抓取链路",visual_grasp 默认仍走 URDF 固定外参。 出处:eye_hand_calibration 的 hand_eye_solver.py(AX=XB 定义与算法)、calibration_poses.yaml(姿态设计)、verify_calibration.launch.py(pass_threshold_mm=5.0)、apply_correction.launch.py;讲义实验7 步骤4 第 5 条。
  1. 前置:第 1 部分零位已校、第 3 部分 K 读取正常(AprilTag PnP 依赖内参+畸变)。Tag 平整贴于桌面中央。
  2. 采集:ros2 launch eye_hand_calibration collect_data.launch.py——臂按 14 姿态逐个到位(settle 2s),每位姿记录 T_ee_base(TF/关节正解)与 T_tag_cam(AprilTag PnP),存会话 ~/.ros/eye_hand_calib/calibration_*。
  3. 求解:ros2 run eye_hand_calibration compute_hand_eye_transform——位姿两两配对构 A、B,解 A·X = X·B 得 X = T_cam_ee;同会话顺带拟合深度线性校正(免费副产物)。
  4. 验证:ros2 launch eye_hand_calibration verify_calibration.launch.py——换新位置放 tag,/eye_hand_calib/verify/report 出 3D 误差,<5mm 合格。
  5. 接入(讲义标记的缺口):① X 发布成 ROS 静态 TF,替换 URDF 相机 joint 近似值;② 深度校正 apply_correction.launch.py 重发布话题并把 visual_grasp remap 过去。不接入 = 白标。

交互① · 姿态设计台:多样性与配对数怎么算

A_i = T_ee(i+1)·T_ee(i)⁻¹  // 臂末端换姿态——相对运动自己门儿清
B_i = T_tag(i+1)·T_tag(i)⁻¹  // 同一幕在相机眼里的变化
A·X = X·B ,X = T_cam_ee(常量——相机没换地方装就该处处成立)
多次配对 ⇢ 解出唯一 X:旋转越"花"、位姿越多,方程越"过约束"越稳
第 1 对

交互② · 深度校正(同一会话的免费副产物)

深度原始读数:— 校正模型:d_corr = scale·d_raw + offset(线性模型,有效区间 0.10~1.50m)
真值(卷尺)raw 读数校正后残差
为什么不用单独练:求解器拟合 X 时用同一批 tag 深度观测顺手解出 scale/offset——现场只需卷尺三点(0.3/0.5/1.0m)复核,正好是讲义实验7 步骤3 的验收法(基线 ±2~3cm@1m)。
接入警示(讲义原话转述):visual_grasp 默认用原始深度 + URDF 固定外参;要吃到标定成果需手动 launch apply_correction 并把深度话题 remap 到校正输出。标完不接 = 第 4 部分只完成一半。
PART 5 · 抓取验证

5 · 抓取验证:五项绿勾才算标定闭环

教学目标:把前四部分与全课散落的数字(残余<1°、尖点重合、K=614、5mm、>85%)按依赖顺序串成一次可执行的验收——任何一环不过,后面的数字都不可信;并会用"何时重标"表应对日后变动。

交互 · 点击逐项验收(进度条全绿出结论)

进度 0/5 —— 从第 1 项开始
  • ① 零位复核(部分 1)— 发 joint_command 全零,五关节笔直/水平(水平仪,残余 <1° ≈ δ<42μs)。不过 → 回部分 1 再精调一轮。
  • ② TCP 复验(部分 2)— 换任意大姿态让尖笔再顶同一个图钉孔:计算尖端弥散肉眼重合于一点(纸上一个孔痕)。同时顺手跑 test_fk_ik 记录闭环残差(几何把关,不在此标定)。
  • ③ 内参 + 深度抽查(部分 3)— camera_info 确认 K=614 与分辨率一致;近/中/远三点(0.3/0.5/1.0m)看落点与深度——远端先偏即回部分 3。
  • ④ 手眼验证(部分 4)— verify_calibration 换位放 AprilTag,报告 < 5mm(pass_threshold_mm=5.0);并确认接入生效(静态 TF / 深度 remap 已切换)。
  • ⑤ 真机抓取验证 — EXP-07 性能基线:OBSERVE<1s · PICK<8s · 成功率>85%(100 次统计,失败按 视觉/IK/抓取 三类归因)。到这一步,标定链才算闭环。

何时重标哪一部分(速查)

变动事件重标只需复核
舵机拆装/更换、撞后不齐部分 1 零位② 闭环
更换夹爪/工具/尖笔位置变了部分 2 TCP 四点⑤ 抽测一次
相机跌落、镜头松动/对焦变、出图分辨率改部分 3 内参(厂家/棋盘格)③ 远端三点
相机支架/腕部拆装、换相机安装位部分 4 手眼全流程⑤ 真机复测
只搬动底座位置(安装关系没变)—① 全零 + ⑤ 抽测
换机械臂杆件(几何变)超出本专题 → 几何标定(需毫米级量具)
频率建议:常规教学周只做 ①+⑤ 抽测;学期初/设备动过再跑 ②③④ 全链。出厂 K 平时读不坏——把"echo K + 卷尺看远端"养成例行两眼。
归因纪律:⑤ 失败先回看 ①~④ 哪个绿勾是"勉强过的",再分视觉/IK/抓取三类——顺序反了就是浪费 100 次抓取。