ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

机器人视觉感知:手眼标定、深度估计与3D重建实战指南

机器人视觉感知:手眼标定、深度估计与3D重建实战指南 这期Datawhale组队学习做到机器人视觉专题Task02直接把手眼标定、深度估计和3D重建三个硬骨头端上来了。我刚开始看到任务书的时候其实有点心理准备毕竟计算机视觉在机器人领域的落地绕不开这三个方向——视觉感知让机器人“看见”手眼标定让机器人“知道自己在哪看”深度估计和3D重建让机器人“知道物体长什么样、离自己多远”。这三个模块串起来才是完整的“感知-认知-行动”闭环。这篇内容我整理一下整个任务从原理到代码再到排查的完整过程适合正在做机器人视觉入门、准备搞机械臂抓取项目、或者对视觉感知技术栈想系统梳理一遍的朋友直接参考这篇就能把主线跑通。1. 视觉感知与手眼协调机器人靠什么把“看见”变成“做到”1.1 为什么机器人的“眼睛”和“手”必须协调工作先回答一个很多人初学时会困惑的问题计算机视觉在机器人里到底扮演什么角色在纯视觉任务里算法只要输出“图像里有什么、在哪”就结束了但在机器人任务里视觉输出只是一个中间结果真正的终点是机械臂末端执行器能不能准确移到目标位置能不能稳定抓取。这里面的关键跨度就是从“像素坐标”到“机器人基座坐标”的转换。举个例子相机检测到桌面上的螺丝刀返回的是像素坐标(u,v)但机械臂要抓它需要的却是螺丝刀在机器人基座坐标系下的三维位置(x,y,z)。从像素到三维坐标中间隔着相机的内参、相机相对机械臂的外参以及机械臂自身的运动学参数。这一串坐标变换就是手眼标定要解决的问题也是视觉感知和运动控制之间那座桥。1.2 Task02整体链路拆解从图像输入到抓取点输出这次任务把整个流程拆成了三个环节。第一步是手眼标定建立相机坐标系和机械臂坐标系之间的变换关系第二步是深度估计从图像中恢复每个像素离相机的距离信息第三步是3D重建把多帧深度信息融合成物体完整的几何表面。三个环节层层递进前面环节的误差会直接累积到后面所以每一步都必须理解到位再动手。我自己在跑这个任务时把整体链路画成了“图像采集 → 相机标定 → 手眼标定 → 深度图生成 → 点云生成 → 3D模型重建”的流水线。在实际机械臂抓取场景里这个流水线通常还要再接一步位姿估计和抓取规划但Task02的重点是把前面这段感知链路彻底打通。想快速入手的同学建议先在仿真环境里跑通这套链路再上真机能省不少调试时间。2. 手眼标定算法让机器人知道物体到底在哪2.1 两种经典标定构型eye-in-hand与eye-to-hand怎么选手眼标定第一步不是写代码是先搞清楚机械臂和相机的安装关系。相机固定在机械臂末端随动叫eye-in-hand构型这时候要标定的是相机坐标系到机械臂末端坐标系的变换关系相机固定在工作空间外面不动机械臂在相机视野里运动叫eye-to-hand构型这时候要标定的是相机坐标系到机械臂基座坐标系的变换关系。选择哪种构型取决于实际应用场景。眼在手上适合需要近距离观察目标、目标位置不固定的场景因为相机可以跟着机械臂靠近去看透视遮挡少眼在手外适合工作范围固定、相机视野需要覆盖整个工作台的场景。这次任务我在仿真里两种都跑了一遍实测下来眼在手上的标定流程更容易理解新手建议从eye-in-hand开始。2.2 AXXB的数学原理手眼标定到底在解什么方程手眼标定最难理解的部分就是AXXB这个方程。剥开来看其实不复杂。X是待求的相机和机械臂之间的固定变换矩阵A是机械臂从位姿1运动到位姿2时末端执行器的变换矩阵B是相机从角度1看到标定板到位姿2看到标定板时标定板的变换矩阵。因为X是固定不变的所以A和B之间存在AXXB的关系。求解这个方程时工程上通常分两步先解旋转部分再解平移部分。经典算法有Tsai-Lenz、Park-Martin等OpenCV的calibrateHandEye函数已经把这些方法都封装好了只需要传两组数据——机械臂末端位姿序列和标定板在相机坐标系下的位姿序列。理解这个方程背后的物理含义比背代码重要得多A是机械臂自己告诉你的B是视觉算法算出来的X才是连接两者的关键。2.3 实操数据采集要点标定板、位姿数量和动作幅度手眼标定看似是个函数调用的事真正操作起来90%的时间都花在数据采集上。标定板我用的OpenCV自带的棋盘格打印后贴在平整硬板上避免纸张弯曲。相机端用OpenCV的findChessboardCorners检测角点solvePnP求出标定板相对相机的位姿机械臂端直接读末端位姿注意坐标系和单位必须对齐。数据采集有几个容易被忽视的点。第一至少采集15到20组位姿太少的话方程欠约束第二机械臂动作幅度要大让相机从不同角度、不同距离观察标定板纯平移采集出来的数据会让旋转分量解不稳定第三每组数据记录时确保标定板在视野内且角点清晰一旦检测失败立即丢弃重采。这几点做好了标定精度通常能控制在几毫米内对抓取任务够用了。3. 深度估计从二维像素到三维空间的距离3.1 为什么说深度是视觉感知从“识别”跨到“操作”的关键一步平面图像里的物体即使识别得再准确也只是告诉你“图里有个杯子”没有告诉机器人杯子离自己多远、在什么方位。没有深度信息机械臂根本没法规划运动轨迹。所以深度估计在机器人视觉里是承上启下的核心模块——往前承接目标检测与语义分割往后决定抓取点是否可执行。这次任务里我用到了三类深度获取方式各有适用边界。单目深度估计门槛最低成本最低但输出的深度是相对值且需要训练数据支撑双目立体视觉通过左右视图视差计算深度精度可控但需要严密的相机标定深度相机直接输出深度图使用最方便但在透明物体、强光环境容易失效。实际做项目时要根据场景传感器条件做取舍。3.2 三种深度估计方案对比单目、双目和深度相机的取舍单目深度估计现在主流是深度学习方案比如MiDaS这类模型可以直接从单帧RGB图推理出相对深度图。优点是传感器只要一个普通摄像头成本可以压得非常低缺点是绝对尺度缺失想直接用深度值去驱动机械臂还需要额外的先验信息来恢复尺度。这次任务里我用MiDaS做快速验证效果直观但数值不能直接用于抓取。双目视觉靠计算左右图视差来还原深度理论上精度和基线距、相机分辨率有关通过立体匹配可以得到稠密深度图。它不需要主动光源室内外都能用但计算量大而且在纹理稀疏区域容易产生深度空洞。深度相机比如RealSense系列直接硬件输出深度图最适合快速落地但受限于物理原理对反光、透明、深黑物体基本束手无策。三种方式没有绝对好坏只有合不合适。3.3 深度估计的局限性与避坑心得深度估计是个看着简单、实际藏着不少坑的环节。最大的坑是深度图的时间和空间对齐问题——RGB图像和深度图像来自不同传感器它们的分辨率、帧率、视场角都不一样使用前必须要做对齐处理。另一个坑是边缘区域的深度值特别不稳定物体轮廓处经常会有一圈错误的深度跳变下游做点云时会产生飞点。我自己踩过最深的一个坑是深度相机在阳光直射环境下深度值大面积丢失后来调整了相机的曝光参数才有所缓解。如果你在室内做实验建议用遮光帘遮挡强光如果必须在强光环境工作优先考虑双目方案而不是主动光深度相机。深度值滤波也很重要中值滤波和双边滤波都能在保持边缘的同时去掉孤立噪声点。4. 3D重建把稀疏点云变成机器人能用的几何模型4.1 从深度图到点云内参矩阵反投影的完整推导拿到深度图之后第一步是把它转换成点云。这个过程本质上在做相机内参矩阵的反投影对深度图上的每个像素(u,v)已知该像素的深度值d和相机内参(fx, fy, cx, cy)相机坐标系下的三维坐标可以通过公式 (x (u - cx) * d / fx, y (v - cy) * d / fy, z d) 计算出来。公式本身不复杂但有一个细节容易被忽视——深度值d的单位必须和fx、fy的单位统一否则点云整体尺度会错乱。我在实践里习惯把点云生成封装成一个独立的函数输入是深度图和相机内参输出是N×3的点云数组。这样后续做点云滤波、配准、重建时所有环节的数据格式都是一致的。点云这一步是整个3D重建的基础如果基础数据不对后面所有算法都是白搭。4.2 点云预处理与表面重建从噪声点集到三角网格原始点云直接做表面重建结果通常惨不忍睹。所以我每次都会先做预处理统计滤波去掉离群点体素滤波降采样减少数据量再用移动最小二乘平滑表面。这三步做完点云质量会有肉眼可见的提升。体素滤波的体素大小是个关键参数设太小降采样效果不明显设太大又会把细节磨平需要根据物体尺寸反复试。表面重建阶段我用得比较多的是泊松重建算法它能从带法向的点云生成水密网格非常适合物体级重建。使用泊松重建前要先用近邻搜索估算每个点的法向法向方向的一致性会直接影响重建结果。如果你重建出来出现大面积的破洞或反向面片优先检查法向估计环节而不是去调重建深度参数。4.3 3D重建在机器人抓取中的实际应用方式重建出来的3D模型在机器人抓取流程里主要有两个用途。第一是帮助机器人理解目标物体的完整几何形状尤其是从当前视角看不到的背面结构这在抓取规划时能避免碰撞第二是可以把重建模型与CAD模型做配准得到物体的精确六自由度位姿这是很多分拣任务的标配做法。当然也要说句实话Task02里的3D重建更偏基础感知验证真要落到工业级抓取还得融合多视角点云、几何特征匹配、抓取点采样等更重的模块。不过底子打好了后面扩展就是顺理成章的事。我建议学习时先不要贪多把“深度图→点云→网格模型”这条主线跑通比什么都强。5. 实操过程与核心环节实现环境配置、代码框架与实验记录5.1 开发环境与工具链选型这次任务我的环境配置如下Ubuntu 22.04系统Python 3.10OpenCV 4.8用于相机标定和棋盘格角点检测Open3D 0.18用于点云处理和3D重建显示Eigen和Sophus用于矩阵运算与位姿操作。如果你用ROS2做机械臂开发建议把realsense-ros这类驱动也装上深度图采集会方便很多。仿真部分我用的是CoppeliaSim搭配pyRep接口控制机械臂运动比直接用真机调试安全得多。工具链选型这块我的建议是能Python就Python原型验证阶段Python的调试效率远高于C。等算法稳定了再根据需要把关键模块用C重写。不要一上来就用C硬刚不然光是编译依赖就能耗掉大半天。5.2 手眼标定代码流程与关键函数说明手眼标定的核心代码流程可以拆成四步。第一步用cv2.findChessboardCorners检测标定板角点第二步用cv2.solvePnP计算标定板在相机坐标系下的位姿rvec和tvec第三步收集机械臂末端位姿通常以四元数加平移向量的形式表示第四步把所有数据按时间戳对齐后调用cv2.calibrateHandEye得到最终的手眼变换矩阵。import cv2 import numpy as np # 假设已经采集了N组数据 # R_gripper2base: 机械臂末端到基座的旋转矩阵, shape (N, 3, 3) # t_gripper2base: 机械臂末端到基座的平移向量, shape (N, 3) # R_target2cam: 标定板到相机的旋转矩阵, shape (N, 3, 3) # t_target2cam: 标定板到相机的平移向量, shape (N, 3) R_cam2gripper, t_cam2gripper cv2.calibrateHandEye( R_gripper2base, t_gripper2base, R_target2cam, t_target2cam, methodcv2.CALIB_HAND_EYE_TSAI )代码层面就这么简洁但坑在数据准备上。机械臂末端位姿的旋转矩阵必须左乘关系正确标定板位姿的坐标系方向必须与相机坐标系一致。这些只要有一个不对解出来的结果就会差出十万八千里。建议拿到结果后做一次验证用手眼矩阵把标定板角点重投影回图像计算重投影误差一般误差小于1个像素才说明标定质量合格。5.3 深度估计与点云生成的实验记录深度图生成这部分我用RealSense D435i试了原生SDK输出也用MiDaS跑过单目估计对比下来深度相机在近距离0.3米到1.5米范围内精度确实碾压单目测距误差通常在1%到2%之间。实验时我采集了一个马克杯的深度图序列从不同角度拍了60帧每帧分辨率设为640×480帧率15fps保证相邻帧有足够重叠区域。点云生成的实验里我先把每帧深度图反投影成点云再用Open3D的全局配准工具做多帧点云拼接。这个流程跑下来马克杯的表面细节基本都能重建出来但是杯柄内侧因为自遮挡还是有明显空洞。后来我补拍了几个俯视角度的帧才把空洞区域补得差不多。实验结论很直接多视角是3D重建质量的保障拍的视角越全重建效果越好。5.4 手眼标定结果的可视化验证流程标定出来的手眼矩阵是不是对的最好的验证方式就是可视化。我把标定板坐标系、相机坐标系、机械臂基座坐标系在同一个空间里画出来观察三者是否一致。具体做法是取一组标定数据用标定出的变换把标定板角点从相机坐标系转到机械臂基座坐标系然后和机械臂示教器上读到的实际位置对比。这一步多花十分钟能省下后面调试的半天时间。我记得第一次标定时结果看着没什么问题但一可视化就发现标定板点云整体偏了4厘米原因是机械臂返回的末端位姿用的单位是毫米我却在代码里按米处理了。单位不一致这种低级错误不通过可视化根本发现不了。6. 常见问题与排查技巧实录从标定发散到点云空洞6.1 手眼标定结果不收敛或误差巨大先检查这三个地方手眼标定最让人头疼的问题就是解出来的矩阵明显不合理。我总结下来绝大多数情况逃不出三个原因。第一个是机械臂位姿数据坐标系定义搞混有的驱动返回的是末端到基座有的返回的是基座到末端符号反了结果必炸第二个是标定板角点亚像素精度不够图像模糊或者标定板倾斜角度太大都会让solvePnP精度下降第三个是数据组数太少或者机械臂动作幅度单一导致AXXB方程病态。排查顺序建议先从数据可视化入手——把机械臂末端的轨迹和标定板在相机下的位姿轨迹画出来看看运动范围是否覆盖了足够大的空间。轨迹看起来像一条线的话基本上运动幅度就是不够的重新采一组包含大幅度旋转和不同距离的数据问题大概率能解决。6.2 深度图空洞、边缘噪声和尺度漂移的应对策略深度图的坑主要集中在空洞、边缘噪声和尺度漂移这三个方向。空洞通常出现在反光表面、黑色物体和远距离区域标准做法是用深度补全算法做插值简单场景下OpenCV的inpaint或者双边滤波也能应付。边缘噪声集中在物体轮廓附近特征是深度值突然跳变处理方式是结合RGB图做边缘感知滤波把深度边缘和颜色边缘对齐。尺度漂移这个坑比较隐蔽多发于长时间连续采集时深度值缓慢偏离真实值。对策是定期用标定板或者已知尺寸物体做校正。我在实际实验中还发现深度相机预热时间不够时前几分钟的深度值普遍偏大所以每次开机后先空跑五分钟再正式采集能减少很多莫名其妙的误差。6.3 3D重建过程中点云错位与破洞的修复思路多视角点云拼接时最常遇到的问题是错位——两个视角的点云本应重合的地方出现了双重重影。原因一般是初值给得不好全局配准陷入了局部最优。解决办法是先做粗配准比如用特征匹配或者手动选取对应点计算初始变换再执行ICP精配准这样能极大降低错位概率。破洞问题我在前面提过主要靠补充拍摄视角来解决后处理阶段也可以用泊松重建自带的闭合功能做一定程度修复。6.4 一张实用的常见问题速查表问题现象可能原因排查与解决办法手眼标定结果精度差标定板角点检测精度不够提高图像分辨率保证标定板清晰手眼标定结果发散机械臂运动范围过小增加含大幅旋转的位姿至少20组深度图大面积缺失环境光过强或物体反光遮光处理调整相机曝光参数点云边缘出现飞点深度边缘跳变使用边缘感知滤波或统计滤波去离群点拼接点云双重重影配准初值不准先特征粗配准再ICP精配准重建模型表面破洞视角覆盖不全补充多角度拍摄尤其是被遮挡区域7. 从任务到项目这套链路往后还能怎么扩展Task02这套视觉感知链路完成之后可以直接往几个方向继续深挖。第一个方向是机械臂抓取在3D重建基础上加一个抓取点检测网络输入点云输出候选抓取姿态这样从感知到操作就形成了闭环。第二个方向是移动机器人导航深度估计和3D重建可以为机器人构建环境地图帮助机器人避开障碍物规划路径。第三个方向是动态场景下的视觉伺服利用手眼标定的结果把视觉误差直接映射到机械臂关节速度控制上实现实时跟踪抓取。这三个方向现在都有很多开源资料和数据集学习曲线平滑。我自己准备下一步是试着把Task02的代码改造成一个ROS2功能包把视觉感知节点和机械臂控制节点解耦让整个系统的可维护性上一个台阶。回到任务本身我觉得Datawhale这个Task02最值钱的地方是把三个看似独立的视觉技术放在一个机器人的统一框架里讲清楚了。单学手眼标定、深度估计或者3D重建你只会孤立的知识点但放在这个任务里你会自然理解它们为什么必须串在一起以及每个环节的误差会如何影响下游。这种系统性的视角才是做机器人视觉项目最需要的内功。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进