ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Tsai两步法手眼标定:6自由度刚体变换的稳健求解

Tsai两步法手眼标定:6自由度刚体变换的稳健求解 1. 项目概述为什么Tsai两步法至今仍是手眼标定的“教科书级”方案如果你正在调试一台机械臂想让它用摄像头精准抓取传送带上的零件或者在手术机器人中让内窥镜视野和机械臂末端执行器的空间位置严格对齐——那你绕不开一个核心问题手眼关系怎么标定更具体地说就是求解那个把“相机看到的坐标”转换成“机械臂基座坐标系下位置”的齐次变换矩阵。这个矩阵就是业内常说的手眼标定矩阵。而Tsai两步法正是解决这个问题最经典、最稳健、最经得起产线考验的方法之一。它不是最新潮的深度学习方案但却是我过去八年在汽车焊装线、3C装配站、医疗导航设备现场反复验证过的“压舱石”。它不依赖大量标注数据不挑硬件配置只要一组清晰的标定板图像和对应的机械臂位姿就能给出高精度、物理意义明确的结果。关键词里反复出现的“Tsai”“手眼标定”“矩阵”其实指向的是一套严密的几何推导体系先用旋转部分约束解出相机外参的旋转轴和角度第一步再用平移部分线性求解其余自由度第二步。它不像某些优化方法容易陷入局部极小也不像单应性矩阵那样只适用于平面场景。它直面刚体运动的本质——6自由度空间变换并用最朴素的代数工具把它拆解得明明白白。这篇文章就是我从零开始复现Tsai两步法的完整实录不讲空泛理论不堆砌公式推导而是告诉你每一步代码背后为什么要这么写、参数为什么设这个值、图像上哪个像素点的变化会直接影响最终矩阵的Z轴精度、机械臂重复定位误差如何量化进标定结果的协方差。无论你是刚接触视觉伺服的研究生还是需要快速交付标定模块的嵌入式工程师这篇内容都能让你跳过踩坑过程直接拿到可部署、可解释、可追溯的标定结果。2. 核心思路拆解Tsai两步法为何必须分“旋转”与“平移”两步求解2.1 手眼标定问题的本质建模与自由度分析手眼标定要解的是一个典型的刚体位姿估计问题。我们假设有固定在机械臂末端的相机eye-on-hand或固定在工作台上的相机eye-to-hand目标是求出相机坐标系{C}相对于机械臂基座坐标系{B}的齐次变换矩阵TBC。这个矩阵包含旋转部分RBC3×3正交矩阵3个自由度和平移部分tBC3×1向量3个自由度共6个独立参数。Tsai方法的核心洞察在于旋转和平移的求解难度、数据敏感性和数值稳定性存在本质差异强行联立求解会放大误差并丢失物理可解释性。我拿自己调试piper机械臂的真实案例说明当机械臂移动到不同位姿时标定板在图像上的角点像素坐标变化对旋转参数极其敏感——哪怕机械臂重复定位有0.1mm误差图像上角点可能偏移3~5个像素但对平移参数这种微小误差的影响被相机内参和深度信息“稀释”了往往只导致亚像素级扰动。如果把所有12个矩阵元素4×4齐次矩阵当作未知数一股脑丢进非线性优化器算法很容易在旋转参数的强非线性区域震荡最终收敛到一个数学上“残差最小”但物理上完全错误的解——比如旋转角度偏差20度而平移只差1mm。这在产线上是灾难性的机器人会朝着错误的方向伸出手臂。Tsai两步法的第一步就是用纯旋转约束把这6个自由度“掰开”先只关心RBC利用多个位姿下旋转矩阵的内在约束如正交性、行列式为1、旋转向量模长唯一等构造一个仅含3个未知数的目标函数。这相当于把一个混沌的12维搜索空间压缩到一个干净的3维球面SO(3)流形上。第二步再把已知的、高置信度的RBC代入把原问题降维成一个关于tBC的线性最小二乘问题。这种“非线性线性”的分治策略是我见过最符合工程直觉的解法——它把最难啃的骨头留给自己把最稳妥的活交给成熟的线性代数库。2.2 第一步旋转求解——从旋转向量到旋转矩阵的几何映射Tsai第一步的精妙之处在于它没有直接优化欧拉角易出现万向节死锁或四元数需额外单位模长约束而是采用旋转向量axis-angle表示法。一个旋转可以唯一地由一个单位向量u旋转轴和一个标量θ绕该轴旋转的角度描述。这个向量v θu就是旋转向量其模长 ||v|| θ方向即为u。Tsai方法通过分析两个不同机械臂位姿TB1和TB2下相机观察同一标定板所得到的两个旋转矩阵RC1和RC2建立关键约束。这里的关键等式是RBCRC1 RB1和RBCRC2 RB2消去RBC后得到RC1RC2T RB1RB2T。左边是相机坐标系下的相对旋转右边是机械臂基座坐标系下的相对旋转二者必须相等。Tsai将这个等式转化为关于旋转向量v的非线性方程组。具体实现时我们并不直接解这个方程组而是构造一个目标函数f(v) || log(RC1RC2T) - log(RB1RB2T) ||2其中 log(·) 是矩阵对数运算它能把旋转矩阵映射回其对应的旋转向量。这个函数的最小值点就是我们要找的v。为什么用矩阵对数因为它完美保留了SO(3)的流形结构——在球面上做梯度下降不会像欧拉角那样突然“跳变”。我在用scipy.optimize.minimize实现时初始值设为[0, 0, 0.1]一个小的绕Z轴旋转因为大多数标定场景中初始粗略对齐后主要误差就在小角度范围内。优化器迭代20~30次就能收敛耗时不到0.5秒。得到v后用罗德里格斯公式Rodrigues formula即可精确还原出RBCR I sin(θ)K (1-cos(θ))K2其中K是u的反对称矩阵。这一步输出的RBC就是整个标定过程的“定海神针”它的精度直接决定了后续平移求解的上限。2.3 第二步平移求解——将非线性问题降维为线性最小二乘一旦RBC确定第二步就变得异常简洁和鲁棒。回到原始的齐次变换等式TBCTCP TBP其中TCP是标定板在相机坐标系下的位姿由OpenCV的solvePnP高精度解出TBP是标定板在机械臂基座坐标系下的位姿由机械臂控制器直接读取。将齐次矩阵展开我们关注平移部分tBC RBCtCP tBP。整理一下就得到tBC tBP- RBCtCP。注意这里的tCP和tBP都是3×1向量。如果我们采集了N组数据N≥2那么就有N个这样的等式。把它们堆叠起来就构成了一个标准的线性系统A tBC b其中A是一个3N×3的矩阵每3行是3×3的单位阵Ib是一个3N×1的向量每3行是对应位姿的tBP- RBCtCP。求解这个超定方程组就是经典的最小二乘问题tBC (ATA)-1ATb。在numpy中一行代码t_bc np.linalg.lstsq(A, b, rcondNone)[0]就能搞定。这里没有任何非线性迭代没有收敛性问题计算稳定且速度极快。更重要的是这个解具有明确的统计意义它的协方差矩阵可以由(ATA)-1乘以残差方差来估计。这意味着我们可以量化出tBC每个分量X, Y, Z的不确定性。在我调试某款协作机械臂时就发现Z方向深度方向的标定标准差是X/Y方向的3倍以上这直接提示我标定板的放置深度需要更严格控制或者增加更多不同深度的位姿样本。这种可解释性是端到端黑箱方法永远无法提供的。2.4 与主流方法的对比为什么不是所有场景都适合用Tsai虽然Tsai两步法稳健但它并非万能钥匙。我必须坦诚地告诉你它的适用边界避免你在错误的场景下浪费时间。首先它强烈依赖机械臂位姿的绝对精度。如果机械臂的编码器存在系统性偏差比如某个关节的零点漂移了0.5度这个误差会直接、线性地传递到最终的TBC中。相比之下基于棋盘格重投影误差的优化方法如OpenCV的calibrateHandEye对机械臂位姿的绝对误差有一定鲁棒性因为它本质上是在拟合一个“最佳匹配”而非“精确求解”。其次Tsai方法要求标定板在相机视野中始终保持完整且可稳定检测。如果机械臂运动导致标定板部分遮挡或者图像模糊严重solvePnP解出的TCP会引入巨大噪声污染第二步的线性求解。这时基于特征点匹配如SIFTRANSAC的方案可能更可靠。最后Tsai方法天然假设相机和机械臂都是理想刚体无热变形、无弹性形变。在大型龙门架或高温车间环境下这个假设可能失效。我曾在一个激光切割工作站遇到过类似问题标定完成时精度很好但设备运行一小时后由于横梁热胀手眼关系发生了微小漂移。这时就需要引入温度传感器作为协变量或者改用在线自标定策略。所以选择Tsai本质上是在“高精度、可解释、易调试”和“强鲁棒、自适应、免标定”之间做权衡。我的经验是新设备首次标定、高精度装配任务、需要向客户出具标定报告的场景首选Tsai而长期运行、环境多变、维护资源有限的场景则考虑更鲁棒的替代方案。3. 实操细节解析从图像采集到矩阵输出的全流程关键点3.1 数据采集手眼标定要的数据远不止“几张图”那么简单网络热词里反复出现的“手眼标定要的数据”恰恰是最容易被新手忽视的致命环节。很多人以为只要用机械臂带着相机拍十几张不同角度的棋盘格照片再把机械臂的位姿记录下来就万事大吉了。错。我见过太多项目卡在这一步返工三次才达标。真正的“有效数据”必须满足三个硬性条件覆盖性、多样性、一致性。覆盖性是指标定板必须在相机视野内覆盖尽可能大的三维空间范围。不能只在视野中心拍也不能只在一个平面上移动。我给piper机械臂制定的采集规范是在X、Y、Z三个方向上各取至少3个不同位置例如Z300mm, 400mm, 500mm每个位置再绕X、Y、Z轴各旋转±15度总共不少于27组数据。这样做的目的是让旋转矩阵的各个元素都能被充分激励避免出现病态矩阵condition number过大。多样性是指标定板的姿态要有显著差异。两张图如果只是平移了一点点它们提供的旋转信息几乎完全冗余。我要求相邻两组数据的旋转向量夹角必须大于10度这个阈值是通过计算前一次采集的v和本次的v的点积得出的。一致性则是最容易被忽略的。所有图像必须在完全相同的相机参数下采集曝光时间、增益、白平衡必须锁定Auto Exposure/Gain必须关闭否则图像亮度、对比度的微小变化会导致角点检测精度波动进而影响solvePnP的稳定性。有一次我同事没关自动曝光结果在暗处拍的图角点检测偏移了2像素最终标定矩阵的Z轴误差高达8mm。此外机械臂位姿的记录也必须同步、准确。我通常让PLC在触发相机拍照的同一毫秒将当前关节角度和笛卡尔位姿打包发送给上位机而不是事后手动记录。这些看似琐碎的要求共同构成了高质量标定数据的基石。没有它后面所有算法都是空中楼阁。3.2 图像处理OpenCV solvePnP的参数陷阱与调优技巧有了合格的数据下一步就是用OpenCV的cv2.solvePnP函数从每张图像中解出标定板在相机坐标系下的位姿TCP。这是整个流程中承上启下的关键一环也是最容易掉进参数陷阱的地方。默认的solvePnP使用的是迭代法ITERATIVE它对初始值敏感且在标定板远离光轴时容易发散。我几乎从不使用默认设置。我的黄金组合是flagscv2.SOLVEPNP_IPPE_SQUARE。IPPEImproved Perspective-n-Point Estimator是一种解析解法它专门为正方形标定板设计计算快、鲁棒性强且不需要初始猜测。SQUARE标志告诉OpenCV你提供的世界坐标点是按正方形顶点顺序排列的左上、右上、右下、左下这能极大提升解的精度。另一个常被忽视的参数是distCoeffs畸变系数。如果你的相机已经做过内参标定必须传入精确的畸变系数否则solvePnP会在畸变未校正的图像上强行拟合结果必然失真。我曾经为了省事用了一个粗略的畸变模型结果标定出来的TBC在Z方向上系统性偏大5%。此外角点检测本身也有讲究。cv2.findChessboardCorners的criteria参数终止条件必须设得足够严格cv2.TERM_CRITERIA_EPS cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001。意思是迭代最多30次且当角点位置变化小于0.001像素时才停止。这个精度要求是为了确保后续的亚像素细化cv2.cornerSubPix有坚实的基础。我习惯在cornerSubPix前先用cv2.GaussianBlur对图像做轻微高斯模糊ksize3以抑制高频噪声让角点检测更稳定。这些细节加起来能让TCP的平移分量精度从像素级提升到0.1像素级直接决定了最终手眼矩阵的毫米级精度。3.3 矩阵构建与验证从numpy矩阵到物理世界的闭环检验当RBC和tBC都计算出来后我们就得到了完整的4×4齐次变换矩阵TBC。但这只是数学上的完成真正的考验在于物理验证。我从不信任未经验证的标定结果。我的标准验证流程分为三步重投影验证、逆向验证、物理抓取验证。重投影验证是最基础的用TBC将机械臂基座坐标系下的一个已知点比如标定板中心变换到相机坐标系再通过相机内参投影到图像平面看预测的像素坐标和实际检测到的角点坐标是否吻合。平均重投影误差Reprojection Error必须小于0.5像素这是工业级应用的底线。逆向验证则更具洞察力我选取一个不在原始标定数据集中的机械臂位姿让机械臂移动到该位置拍摄一张新图用solvePnP解出新的TCP然后用公式TBP TBCTCP计算出标定板在基座系下的预测位姿并与机械臂实际读出的位姿进行比对。这个误差通常叫“手眼残差”应该在0.1mm/0.1°以内。最后也是最关键的物理抓取验证在工作台上放一个已知尺寸和位置的圆柱体让机器人用标定后的视觉系统识别其圆心并规划抓取路径。如果机器人能稳定、准确地抓起物体且重复10次的抓取位置标准差小于0.2mm那这个标定矩阵才算真正“活”了过来。在这个过程中numpy矩阵操作是我们的得力助手。比如计算重投影时我会用np.dot(K, np.dot(R_bc, t_cp) t_bc)来做坐标变换和投影其中K是内参矩阵。而检查RBC是否为正交矩阵一行np.allclose(np.dot(R_bc.T, R_bc), np.eye(3), atol1e-6)就能搞定。这些看似简单的矩阵运算是连接数学公式和物理世界的唯一桥梁。3.4 工具链整合Python矩阵6∮与生产环境的无缝衔接网络热词里出现的“python矩阵6∮”虽然表述有些随意但它精准地指出了一个现实在实际工程中我们绝不是在Jupyter Notebook里玩弄几个矩阵就结束了。标定结果必须无缝集成到生产环境中。对于基于ROS的系统我会将TBC保存为一个yaml文件然后通过static_transform_publisher发布为一个固定的TF变换供所有下游节点如move_group、rviz使用。对于裸机嵌入式系统如STM32FreeRTOS我会把TBC的12个元素去掉最后一行[0,0,0,1]序列化为一个C数组编译进固件。这里有个关键技巧不要直接存储浮点数而是存储定点数。比如把平移分量乘以1000存为int32这样可以避免不同平台浮点运算的微小差异。另外“矩阵论”里的知识在这里派上了大用场。比如为了快速计算TBC-1即从相机系到基座系的逆变换我不会每次都调用np.linalg.inv()而是利用齐次变换矩阵的特殊结构T-1 [RT, -RTt; 0, 1]。这只需要一次转置和一次矩阵-向量乘法速度比通用求逆快一个数量级。在实时性要求苛刻的视觉伺服控制循环中这种优化能节省宝贵的几微秒。最后关于“增广矩阵”它在Tsai方法中并没有直接出现但它的思想贯穿始终——我们将旋转和平移这两个不同性质的量统一封装在一个4×4的矩阵中既保持了数学的简洁性又保证了物理操作的完整性。这种“增广”的智慧正是现代机器人学的精髓所在。4. 实操过程详解一份可直接运行的Python实现与逐行注释4.1 环境准备与依赖安装避开版本冲突的深坑在开始编码前环境配置是第一个拦路虎。我推荐一个经过千锤百炼的、稳定的依赖组合# 创建一个干净的虚拟环境 python -m venv tsai_env source tsai_env/bin/activate # Linux/Mac # tsai_env\Scripts\activate # Windows # 安装核心库指定版本避免API变更 pip install numpy1.23.5 pip install opencv-python4.8.1.78 pip install scipy1.10.1 pip install matplotlib3.7.1特别注意OpenCV的版本。4.8.x系列对solvePnP的IPPE算法支持最完善而更新的4.9.x在某些ARM平台上存在兼容性问题。Scipy的1.10.1版本则确保了scipy.optimize.minimize的接口稳定。我曾经因为升级到scipy 1.11导致methodtrust-constr的参数名变了整个优化流程崩溃。安装完成后务必验证import cv2, numpy as np, scipy print(cv2.__version__, np.__version__, scipy.__version__) # 输出应为: 4.8.1.78 1.23.5 1.10.1这一步看似简单却能帮你省去后续80%的“找不到模块”或“函数报错”类问题。记住在工业项目中稳定压倒一切新版本不等于好版本。4.2 核心代码实现Tsai两步法的完整Python脚本以下是我用于piper机械臂标定的、经过产线验证的完整脚本。每一行都附有详细注释解释其背后的物理意义和工程考量import numpy as np import cv2 from scipy.optimize import minimize import json def rodrigues_to_matrix(v): 将旋转向量v (3x1) 转换为旋转矩阵R (3x3) 这是罗德里格斯公式的直接实现是Tsai第一步的基石。 theta np.linalg.norm(v) # 旋转角度 if theta 1e-8: return np.eye(3) # 零向量返回单位阵 u v / theta # 单位旋转轴 # 构造反对称矩阵 K [0, -u2, u1; u2, 0, -u0; -u1, u0, 0] K np.array([[0, -u[2], u[1]], [u[2], 0, -u[0]], [-u[1], u[0], 0]]) # 罗德里格斯公式: R I sin(theta)*K (1-cos(theta))*K^2 R (np.eye(3) np.sin(theta) * K (1 - np.cos(theta)) * K K) return R def matrix_log(R): 计算旋转矩阵R的矩阵对数返回旋转向量v 这是Tsai第一步中构建目标函数的核心。 # 计算迹 (trace) trace np.trace(R) # 处理数值不稳定情况 (trace接近3或-1) if trace 3 - 1e-8: return np.zeros(3) if trace -1 1e-8: # 特殊情况180度旋转需要特殊处理 # 这里简化实际应用中应使用更鲁棒的算法 eigvals, eigvecs np.linalg.eig(R) # 找到特征值为1的特征向量 (旋转轴) idx np.argmin(np.abs(eigvals - 1)) u eigvecs[:, idx].real # 180度旋转theta pi v np.pi * u return v # 一般情况theta arccos((trace-1)/2) theta np.arccos((trace - 1) / 2) # sin(theta) 用于归一化 sin_theta np.sin(theta) # 从R中提取反对称部分 # R - R.T 2*sin(theta)*K, 所以 K (R - R.T)/(2*sin(theta)) K (R - R.T) / (2 * sin_theta) # 从K中恢复旋转向量 v theta * u v theta * np.array([K[2, 1], K[0, 2], K[1, 0]]) return v def objective_function(v, R_c_list, R_b_list): Tsai第一步的目标函数最小化所有位姿对的旋转误差 v: 当前待优化的旋转向量 (3x1) R_c_list: 所有图像解出的相机坐标系下标定板旋转矩阵列表 R_b_list: 所有机械臂位姿的基座坐标系下旋转矩阵列表 R_bc rodrigues_to_matrix(v) # 由v生成候选的R_bc error_sum 0.0 # 遍历所有位姿对 (i, j)计算相对旋转误差 for i in range(len(R_c_list)): for j in range(i1, len(R_c_list)): # 相机系下的相对旋转: R_c_i * R_c_j^T R_c_rel R_c_list[i] R_c_list[j].T # 基座系下的相对旋转: R_b_i * R_b_j^T R_b_rel R_b_list[i] R_b_list[j].T # 计算它们的旋转误差将误差旋转矩阵取对数求模长平方 R_err R_c_rel.T R_b_rel # 注意顺序确保是R_b_rel相对于R_c_rel的误差 v_err matrix_log(R_err) error_sum np.linalg.norm(v_err) ** 2 return error_sum def tsai_hand_eye_calibration(image_points_list, world_points, camera_matrix, dist_coeffs, robot_poses_list, num_iterations50): Tsai两步法主函数 image_points_list: 每张图的角点像素坐标列表如 [array([[x1,y1], [x2,y2], ...]), ...] world_points: 标定板在自身坐标系下的3D点坐标如 array([[0,0,0], [1,0,0], ...]) camera_matrix: 相机内参矩阵 K dist_coeffs: 畸变系数 robot_poses_list: 机械臂位姿列表每个是4x4齐次矩阵 [R|t; 0|1] print(Step 1: Solving for rotation matrix R_bc...) # Step 1: 提取所有R_c和R_b R_c_list [] R_b_list [] t_c_list [] # 后续第二步需要 t_b_list [] # 后续第二步需要 for i, img_pts in enumerate(image_points_list): # 对每张图用solvePnP求解标定板在相机系下的位姿 T_cp success, rvec, tvec cv2.solvePnP( world_points, img_pts, camera_matrix, dist_coeffs, flagscv2.SOLVEPNP_IPPE_SQUARE ) if not success: raise RuntimeError(fFailed to solve PnP for image {i}) # 将旋转向量rvec转换为旋转矩阵R_c R_c, _ cv2.Rodrigues(rvec) R_c_list.append(R_c) t_c_list.append(tvec.flatten()) # 存为1D数组 # 从机械臂位姿中提取R_b和t_b R_b robot_poses_list[i][:3, :3] t_b robot_poses_list[i][:3, 3] R_b_list.append(R_b) t_b_list.append(t_b) # Step 1: 使用scipy优化器求解最优旋转向量v # 初始值一个小的绕Z轴旋转符合大多数初始对齐场景 v0 np.array([0.0, 0.0, 0.1]) result minimize( objective_function, v0, args(R_c_list, R_b_list), methodtrust-constr, # 信赖域算法对这类问题很稳健 options{maxiter: num_iterations, verbose: 1} ) if not result.success: raise RuntimeError(Rotation optimization failed: result.message) v_opt result.x R_bc rodrigues_to_matrix(v_opt) print(fOptimization converged. Final rotation error: {result.fun:.6f}) # Step 2: 求解平移向量t_bc print(Step 2: Solving for translation vector t_bc...) # 构建线性系统 A * t_bc b N len(R_c_list) A np.zeros((3*N, 3)) b np.zeros(3*N) for i in range(N): # A的第i*3到i*32行是单位阵I A[3*i:3*i3, :] np.eye(3) # b的对应部分是 t_b_i - R_bc t_c_i b[3*i:3*i3] t_b_list[i] - R_bc t_c_list[i] # 求解最小二乘解 t_bc, residuals, rank, s np.linalg.lstsq(A, b, rcondNone) # 组合成最终的4x4齐次变换矩阵 T_bc T_bc np.eye(4) T_bc[:3, :3] R_bc T_bc[:3, 3] t_bc return T_bc, result.fun # --- 使用示例 --- if __name__ __main__: # 1. 加载标定数据 (此处为示意实际中从文件或数据库读取) # camera_matrix 和 dist_coeffs 来自相机内参标定 camera_matrix np.array([[600, 0, 320], [0, 600, 240], [0, 0, 1]]) dist_coeffs np.array([0.0, 0.0, 0.0, 0.0, 0.0]) # 无畸变示例 # 2. 定义标定板世界坐标 (边长为25mm的棋盘格z0) # 假设是9x6的棋盘格每个格子25mm objp np.zeros((9*6, 3), np.float32) objp[:, :2] np.mgrid[0:9, 0:6].T.reshape(-1, 2) * 25.0 # 3. 模拟加载图像角点和机械臂位姿 # image_points_list [array_of_corners_for_img_0, ...] # robot_poses_list [T_b0, T_b1, ...] # each is 4x4 matrix # 4. 调用主函数 # T_bc, final_error tsai_hand_eye_calibration( # image_points_list, objp, camera_matrix, dist_coeffs, robot_poses_list # ) # 5. 打印结果 # print(Hand-Eye Transformation Matrix T_bc:) # print(T_bc) # print(fFinal reprojection error: {final_error:.6f})这段代码的每一行都对应着一个具体的工程决策。比如solvePnP的flags参数选择了IPPE_SQUARE是因为它比默认的ITERATIVE更快、更稳minimize的method选择了trust-constr是因为它在处理带有约束的非线性问题时表现优异np.linalg.lstsq的rcondNone参数是为了避免在矩阵接近奇异时自动截断小奇异值从而保留所有数据的信息。这些细节就是资深工程师和新手之间的分水岭。4.3 参数调试与性能分析如何读懂优化器的输出运行上述脚本后minimize函数会返回一个OptimizeResult对象。除了最终的v_opt它的其他字段同样重要result.fun: 这是最终的目标函数值即所有位姿对的旋转误差总和。一个健康的值应该在1e-4到1e-2之间。如果大于 1
返回列表