资讯动态

机械臂手眼标定实战:眼在手外与眼在手上的完整方案与代码解析

发布时间:2026/8/29 11:08:20 来源:尧图企业网站定制
简介在机器人视觉引导系统中让机械臂准确抓取物体的前提是打通相机坐标系与机械臂坐标系之间的变换关系。这个关系通常被称为手眼标定其核心是求解经典的AXXB矩阵方程涉及齐次变换矩阵、相机内参标定、位姿估计等基础知识。实践中眼在手外和眼在手上两种部署模式分别适用于固定工位与移动操作场景对相机选型、标定板固定和数据采集策略都有不同要求。本文以一台搭载Kinect2与Astra深度相机的Aubo机械臂为例阐述从内参标定到外参求解的完整流程并对比多种求解算法。通过丰富机械臂位姿、保证标定板可见性等工程细节可以获得较高精度的变换矩阵为后续视觉抓取、路径规划提供可靠基础。无论做毕设、实验室项目还是工业应用这套方案都可复用。 把一台相机架在机械臂旁边或者直接装在机械臂末端让它给机械臂当“眼睛”这个想法听起来很直观。可真正动手搭起来第一步就会卡住相机看到的每个点坐标都是相对于相机自己的而机械臂执行动作用的是自己基座坐标系。两套坐标系之间的换算关系搞不清楚视觉系统识别得再准机械臂也照样抓不准东西。这个换算关系就是手眼标定要解决的问题。我最近整理完的一个项目正好把这件事儿做透了一台遨博 Aubo 协作机械臂配了两台深度相机——微软 Kinect2 和奥比中光 Astra分别完成了眼在手外和眼在手上两种最主流的手眼标定方式并且把采集、求解、验证、保存的整套源码和文档都整理齐了。这篇文章我就把这个项目的方案、原理、实现细节和踩坑记录展开聊聊给正在做机械臂视觉引导、毕业设计或者实验室项目的人一份可以直接参考的完整范例。1. 项目概览与方案选型为什么同时做两套手眼标定1.1 一个抓取任务背后的坐标系换算问题先回到起点。假设你要让机械臂完成“看到物体→规划路径→抓取物体”这样一条流水线视觉部分通常要做三件事第一是目标检测与定位得到目标在相机坐标系下的三维坐标第二是手眼标定得到相机坐标系和机械臂基座或末端坐标系之间的变换矩阵第三是坐标变换把相机里的坐标转换到机械臂能执行的坐标系里。手眼标定卡在第二环它在整个流程里属于那种“不解决就寸步难行解决了又看不见效果”的模块。很多新手容易忽略它直接把相机识别到的坐标发给机械臂结果误差大到离谱然后开始怀疑视觉识别算法有问题其实问题根本不在识别而在坐标系的对应关系没标定好。在这个项目里我做了两套标定分别对应两种工业现场最常见的相机部署方式。一套用 Kinect2 固定安装在工作区侧面标定相机相对于机械臂基座的外参也就是“眼在手外”另一套把 Astra 装在机械臂末端法兰盘上标定相机相对于机械臂末端的外参也就是“眼在手上”。两套标定共用同一个标定板、同一套采集框架、同一个求解流程但坐标系变换链不同这也是我特意把两个场景放在一起做对比的原因。1.2 眼在手外和眼在手上适用场景完全不同眼在手外的典型场景是流水线固定工位相机挂在天花板或支架上机械臂在相机视野范围内作业。这种方案的好处是相机不跟着机械臂动视野稳定标定一次可以用很久也不给机械臂增加额外负载。缺点是容易被机械臂本体遮挡而且如果工作范围大一台固定相机可能覆盖不全。眼在手上的典型场景是移动操作、狭小空间作业或者需要在不同角度观察工件的场合相机装在机械臂末端跟着机械臂走想看哪儿看哪儿近距离测量精度更高。缺点也明显每次更换末端工具、拆装相机之后都要重新标定而且机械臂运动时相机的抖动会影响图像质量。这个项目的核心任务就是分别把这两套“相机—机械臂”外参标定出来并且写成通用代码让后续做视觉抓取实验时可以直接调用。项目里还配套了完整的标定文档和使用说明数据采集、标定求解、结果验证的代码都分模块整理好了这也是项目被称为“高分优秀项目”的原因之一。1.3 为什么选 Kinect2 和 Astra 这两台相机选 Kinect2 做眼在手外一是因为它彩色分辨率高1920×1080用来观察大范围工作台很合适二是因为它视野角较大可以覆盖机械臂大部分工作空间三是因为 Kinect2 作为深度相机后续如果要扩展深度定位、点云抓取它本身就能直接提供深度图不必额外换传感器。选 Astra 做眼在手上核心原因是轻。Astra 整机重量比 Kinect2 小一个量级挂在机械臂末端不会给关节带来额外负担也更容易固定。另一个原因是它的 SDK 对彩色流、深度流支持比较成熟数据格式转 OpenCV 很方便。当然实际装的时候要注意Astra 的固定支架最好用 3D 打印一个转接板确保相机光轴和机械臂末端的安装平面尽量垂直减少安装引入的角度偏差。2. 手眼标定的数学原理AXXB 到底在说什么2.1 齐次变换矩阵把旋转和平移统一成一个矩阵手眼标定涉及的数学对象只有一个就是齐次变换矩阵。一个 4×4 的齐次变换矩阵由旋转矩阵 R3×3和平移向量 t3×1组成表示一个坐标系相对于另一个坐标系的位姿。坐标系的旋转和平移用这个矩阵乘上点的齐次坐标一步就能完成。这个矩阵在机械臂里很常见机械臂控制器给出的“末端位姿”其实就是末端坐标系相对于基座坐标系的变换矩阵。相机标定里cv2.solvePnP 解出来的 rvec 和 tvec表示的也是标定板坐标系相对于相机坐标系的变换。手眼标定要做的就是把这两个“相对变换”之间的关系找出来。2.2 从两次观测推导出 AXXB先看眼在手上的情形。机械臂的末端坐标系记为 gripper基座坐标系记为 base相机坐标系记为 camera标定板坐标系记为 target。相机固定在末端上所以 camera 和 gripper 之间的变换关系是固定的这个固定变换就是我们要标定的 X。对于任意一次观测从标定板上的一个点出发它先经过相机到末端的变换 X再经过末端到基座的变换最终落在基座坐标系里。标定板在基座坐标系里的位姿是固定不变的于是两次不同机械臂姿态下的观测可以联立起来。经过化简会得到一个形如 A × X X × B 的矩阵方程其中 A 由两次机械臂末端相对运动求出B 由两次标定板在相机下的相对位姿求出X 就是未知的手眼变换矩阵。眼在手外的推导方向不同标定板固定在机械臂末端相机固定在外面但结果同样能化成 AXXB 的形式。正因为如此OpenCV 里的 cv2.calibrateHandEye 函数对两种模式都适用区别只在输入数据的组织和最后对 X 的物理含义解释上。2.3 为什么至少要十几组位姿而不是一两个位置就能解AXXB 这个方程看起来简单但如果不加约束单次观测只能提供有限的信息。实际求解时我们采集十几组甚至几十组机械臂位姿通过多组相对运动方程联立才能把 X 解出来。光有平移没有转动或者转动方向太单一方程会退化解出来的 X 在某个方向上的误差特别大。这就像测量一张桌子有多高你只从正前方看会得到很多个可能的答案因为视角单一的信息不足以确定三维结构只有从不同角度多观察几次才能稳定地估计出真实数值。手眼标定同理机械臂的位姿变化越丰富标定结果越稳定。2.4 OpenCV 提供的几种求解方法cv2.calibrateHandEye 里带了几种经典求解算法包括 Tsai-Lenz、Park-Martin、Horaud 和 Daniilidis。Tsai-Lenz 是最经典的两步法先解旋转再解平移速度快适合大多数场景Park 用李代数表示数值稳定性较好Daniilidis 基于四元数和对偶四元数对噪声更鲁棒。我在项目里的做法是先用 Park 算一个初始结果再看重投影误差是否在合理范围。如果误差偏大就改用 Daniilidis 对比一次。实际用下来的经验是在姿态丰富、采集质量正常的条件下几种方法的结果差别不大差异通常在一两个毫米以内如果发现不同方法解出来的 X 差别很大基本可以断定是采集数据出了问题而不是算法的问题。3. 系统搭建与标定全流程从硬件固定到数据采集3.1 整体系统结构项目里整个系统分四个部分机械臂本体、两台相机、标定板、上位机软件。Aubo 机械臂通过网线和上位机通信上位机跑 Python 控制程序读取机械臂末端位姿Kinect2 通过 USB3.0 连接到上位机Astra 通过 USB2.0/3.0 连接标定板在眼在手外模式下固定在机械臂末端在眼在手上模式下固定在工作台面上。项目代码目录结构我按功能模块分得很清楚project/ ├── config/ │ ├── camera_intrinsics_kinect2.yaml │ ├── camera_intrinsics_astra.yaml │ ├── eye_to_hand_result.yaml │ └── eye_in_hand_result.yaml ├── data/ │ ├── eye_to_hand/ │ │ ├── rgb/ │ │ ├── robot_pose/ │ │ └── board_pose/ │ └── eye_in_hand/ │ ├── rgb/ │ ├── robot_pose/ │ └── board_pose/ ├── src/ │ ├── capture_kinect2.py │ ├── capture_astra.py │ ├── robot_control.py │ ├── board_pose.py │ └── calibrate_hand_eye.py └── README.md数据单独存放的好处是标定采集的过程和标定求解是解耦的。你可以先花半小时采集一批数据再慢慢调试求解逻辑。每次重新标定不用再动机械臂直接读取历史数据就行。3.2 相机内参标定手眼标定之前的必经步骤手眼标定要想准相机内参必须先标准。内参是相机坐标系里描述焦距、主点、畸变系数的参数它决定了三维点投影到二维图像上的数学关系。如果内参不准solvePnP 解出来的标定板位姿就会有系统误差这个误差会一路传递到手眼矩阵里。内参标定我会用 9×6 的棋盘格方格边长 30mm打印出来贴在硬纸板上。拍摄时让标定板在相机视野里摆出各种姿态远近变化、左右倾斜、上下俯仰至少拍 20 张然后用 cv2.calibrateCamera 计算。标定完成后重点看重投影误差一般 RMS 能到 0.1 像素以内就算不错。如果误差大于 0.3 像素通常是图像清晰度不够或者角点提取不准需要重拍。Kinect2 和 Astra 的彩色内参都需要单独标定不能直接拿出厂参数用因为出厂参数针对的是深度图彩色图的内参可能略有偏差。这个偏差在近距离视觉引导时会被明显放大。3.3 眼在手外标定流程Kinect2 固定棋盘装在机械臂末端眼在手外的采集流程是这样的把 Kinect2 固定在支架上调整角度让机械臂工作空间主要区域都在视野内。把棋盘格固定在机械臂末端。固定要非常牢固最好用专用的夹具或者打印支架不能有晃动。上位机连接机械臂控制机械臂运动到一个预定姿态确保棋盘在相机视野中清晰可见。记录当前机械臂末端位姿也就是 gripper 相对于 base 的变换。同时拍一张 Kinect2 彩色图像检测棋盘格角点用 solvePnP 求出棋盘格相对于相机的位姿。机械臂移动到下一个姿态重复上述步骤采集 20 组左右数据。采集过程中要注意机械臂运动速度不要设太快到位后等一两秒再拍照避免运动模糊。每次姿态变化要足够大不能只是沿着某个方向平移要包含明显的旋转尤其是绕相机光轴方向的旋转。机械臂移动到不同位置时如果棋盘格出了视野说明当前姿态超出范围要退回重新调整。为了保证采集效率和成功率我会先手动示教几个关键姿态让标定板始终落在视野中心区域然后再用程序在这几个姿态之间做小范围插值运动。3.4 眼在手上标定流程Astra 装末端棋盘固定在工作台眼在手上的采集流程和眼在手外的镜像对称把 Astra 通过转接板固定在机械臂末端法兰盘上注意线束要整理好避免机械臂运动时拉扯。把棋盘格平放在工作台上确保机械臂运动范围内都能看到它。控制机械臂运动到多个姿态每个姿态下棋盘格都应完整出现在 Astra 彩色图像里。记录机械臂末端位姿和棋盘格在相机视野里的位姿。这里有个容易忽略的点眼在手上时相机随机械臂运动机械臂处于不同姿态时相机看到的棋盘格角度变化很大。这会带来两个问题一是棋盘格可能出视野二是距离太近时对焦模糊。我的处理是规划机械臂姿态时始终让棋盘格位于相机图像中央附近并且末端保持在距棋盘格 40~70cm 的范围内。Astra 的彩色分辨率虽然不如 Kinect2但在这个距离范围内做手眼标定完全够用。4. 核心代码实现与关键模块拆解4.1 机械臂位姿读取与运动控制Aubo 机械臂提供了 Python SDK底层通过 RPC 接口和控制器通信。项目中我封装了一个 robot_control.py负责连接机械臂、读取当前位姿、控制机械臂运动到指定姿态。机械臂返回的位姿通常是位置加欧拉角或四元数。在标定里我们需要的是代表末端坐标系相对于基座坐标系的 4×4 齐次矩阵。我在代码里写了一个转换函数把位置和旋转表示转换成齐次矩阵确保后面计算统一。为了自动采集我定义了一组目标姿态的列表。机械臂从当前姿态出发依次运动到每个目标姿态到位后暂停 1 秒同时触发相机采集图像。整个过程可以无人值守但第一次运行前一定要手动确认每个姿态的标定板都在视野内否则白跑一趟。4.2 标定板检测与位姿估计标定板检测的核心是 OpenCV 的 findChessboardCorners 和 solvePnP。前者提取棋盘格角点的像素坐标后者利用角点的像素坐标和对应的三维物体坐标求解标定板相对于相机坐标系的旋转和平移。关键参数是物体坐标的构建。棋盘格 9×6 表示内角点是 9 列 6 行本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价