1 引言
虽然工业机器人的重复定位精度很高,但提高绝对定位精度可以扩大工业机器人的应用范围。机器人可以在固定位置安装传感器(eye-in-hand),传感器也可以安装在机器人手中,以便通过改变摄像头的视角来获得新的图像eye-to-hand)。为了使机器人能够准确估计零件相对于自身底座的三维位置和方向,有必要了解机器臂及其底座、相机和手臂以及相机和工件的相对位置和方向。这三项任务需要校准机器人、传感器和手臂对传感器(手眼)。校准机器人主要是校准机器人运动参数,根据机器人误差模型获取机器人运动参数误差,将误差补偿到原名义参数,达到机器人校准的目的。校准传感器是相机中相机参数和标定物相对于相机坐标系的转换矩阵。校准机器手臂与传感器即我们所谓的手眼关系标定,主要是确定机器人末端和传感器之间的关系,这也是本篇文章的重点。
2 基础知识
在学习机器人标定之前,首先要了解其对应的坐标系,分别对应下图的机器人基座坐标系、机器臂末端坐标系、相机坐标系以及世界坐标系。 下图是由机器人和固定在机械臂末端的相机组成的常见机器人视觉系统(eye-in-hand),记录了一组机器人的相对运动,导致手眼标定方程:AX = XB。式中,A表示相机两次运动之间的相对位置,B表示机器臂末端两次运动之间的相对位置;X表示相机到终端执行器的相对位置,即手眼关系矩阵。且:A = A2A-1 ; B = B2B-1 ;式中,A1和A2表示相机在两次运动中相对于目标物体的位置矩阵;B1和B2表示机械臂末端两次运动中基座相对末端的位置矩阵。
3 对比分析不同手眼标定方法
手眼标定方法主要分为上述三类AX=XB齐次方程求解法,最小重投影误差法,人工神经网络(ANN)法分别对应下图: 先来分别讲述一下三种方法的优缺点(计算时间和精度仅供参考):
方程齐次变换 | 重投影误差 | 人工神经网络 |
---|---|---|
估计需要明确的相机姿势 | 相机姿势估计是隐式的 | 不需要相机校准或姿态估计 |
适用于可用小孔成像模型描述的相机 | 因为它使用的相机的直接图像可以容纳其他相机型号 | 通用模型意味着它可以适应其他相机模型 |
解决方案中没有过度拟合问题 | 解决方案可能容易过度拟合 | 参数过拟合是一个限制 |
计算时间0.142s | 计算时间0.272s | 计算时间2.355s |
精度1.715mm | 精度1.380mm | 精度0.923mm |
:
旋转和平移参数是否可以同时分为两类:旋转和平移参数和旋转和平移参数。 单独解决旋转和平移参数的方法原理如下:齐次方程AX = XB,展开如下 方法主要包括轴-角法、李代数、克罗内积和四元数法,如下图所示: 同时解决旋转和平移参数的方法原理如下: 同时解决旋转和平移参数的方法可分为基于分析和基于数值优化的方法:
:
该技术的主要优点是直接获取校准对象的图像,而无需对相机进行明确的姿态估计。
:
用于手眼标定ANN手坐标与校准对象的相应图像坐标之间的映射可以视为找到机器人基座的手坐标,可以表示为A=fn(x)。优点:由于以在不了解相机参数或姿态估计的情况下使用,因为它具有很强的泛化变量之间的非线性关系能力,这也使它适合处理噪声。缺点:提供的解决方案通常无法解释,性能取决于所使用的网络结构,并防止过拟合。
4 标定目标
以前介绍的是普通摄像头作为传感器,其校准目标一般有三种,基于棋盘格、圆形格栅等(charuCo),具体形状如下图所示: 三个标定目标的特点如下: 说到这里,我们来谈谈传感器用于其他类型的校准。以线激光轮廓传感器为例,常用的校准目标是标准球。基于球形校准的校准算法通过线激光传感器扫描球形校准,以标准球的球心为校准点,控制机器人驱动线激光传感器多位置扫描标准球,标准球心在线激光传感器坐标系下的坐标是根据球形标定物的几何特性和勾股定理获得的。当然,线激光轮廓传感器的手眼标定也有阶梯形和圆柱形作为标定物。此时使用的手眼关系矩阵可以与上述不同,但手眼关系矩阵是基于标定物的几何特性建立起来的。
5 手眼标定限制因素
6 源码
这里有一些基础AX=XB使用时只需调用齐次方程求解方法的源代码。
// Tis file is part of OpenCV project. // It is subject to the license terms in the LICENSE file found in the top-level directory // of this distribution and at http://opencv.org/license.html. #include "opencv2/core.hpp" #include "opencv2/imgproc.hpp" #include "opencv2/highgui.hpp" #include "opencv2/calib3d.hpp" #include "iostream" using namespace std; namespace cv { static Mat homogeneousInverse(const Mat& T) { CV_Assert(T.rows == 4 && T.cols == 4); Mat R = T(Rect(0, 0, 3, 3)); Mat t = T(Rect(3, 0, 1, 3)); Mat Rt = R.t(); Mat tinv = -Rt * t; Mat Tinv = Mat::eye(4, 4, T.type()); Rt.copyTo(Tinv(Rect(0, 0, 3, 3))); tinv.copyTo(Tinv(Rect(3, 0, 1, 3))); return Tinv; } // q = rot2quatMinimal(R) // // R - 3x3 rotation matrix, or 4x4 homogeneous matrix // q - 3x1 unit quaternion <qx, qy, qz> // q = sin(theta/2) * v // theta - rotation angle // v - unit rotation axis, |v| = 1 // Reference: http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/ static Mat rot2quatMinimal(const Mat& R) { CV_Assert(R.type() == CV_64FC1 && R.rows >= 3 && R.cols >= 3); double m00 = R.at<double>(0,0), m01 = R.at<double>(0,1), m02 = R.at<double>(0,2); double m10 = R.at<double>(1,0), m11 = R.at<double>(1,1), m12 = R.at<double>(1,2); double m20 = R.at<double>(2,0), m21 = R.at<double>(2,1), m22 = R.at<double>(2,2); double trace = m00 + m11 + m22; double qx, qy, qz; if (trace > 0) { double S = sqrt(trace + 1.0) * 2; // S=4*qw qx = (m21 - m12) / S; qy = (m02 - m20) / S; qz = (m10 - m01) / S; } else if (m00 > m11 && m00 > m22) { double S = sqrt(1.0 + m00 - m11 - m22) * 2; // S=4*qx qx = 0.25 * S; qy = (m01 + m10) / S; qz = (m02 + m20) / S; } else if (m11 > m22) { double S = sqrt(1.0 + m11 - m00 - m22) * 2; // S=4*qy qx = (m01 + m10) / S; qy = 0.25 * S; qz = (m12 + m21) / S; } else { double S = sqrt(1.0 + m22 - m00 - m11) * 2; // S=4*qz qx = (m02 + m20) / S; qy = (m12 + m21) / S; qz = 0.25 * S; } return (Mat_<double>(3,1) << qx, qy, qz); } static Mat skew(const Mat& v) { CV_Assert(v.type() == CV_64FC1 && v.rows == 3 && v.cols == 1); double vx = v.at<double>(0,0); double vy = v.at<double>(1,0); double vz = v.at<double>(2,0); return (Mat_<double>(3,3) << 0, -vz, vy, vz, 0, -vx, -vy, vx, 0); } // R = quatMinimal2rot(q) // // q - 3x1 unit quaternion <qx, qy, qz> // R - 3x3 rotation matrix // q = sin(theta/2) * v // theta - rotation angle // v - unit rotation axis, |v| = 1 static Mat quatMinimal2rot(const Mat& q) { CV_Assert(q.type() == CV_64FC1 && q.rows == 3 && q.cols == 1); Mat p = q.t()*q; double w = sqrt(1 - p.at<double>(0,0)); Mat diag_p = Mat::eye(3,3,CV_64FC1)*p.at<double>(0,0); return 2*q*q.t() + 2*w*skew(q) + Mat::eye(3,3,CV_64FC1) - 2*diag_p; } // q = rot2quat(R) // // q - 4x1 unit quaternion <qw, qx, qy, qz> // R - 3x3 rotation matrix // Reference: http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/ static Mat rot2quat(const Mat& R) { CV_Assert(R.type() == CV_64FC1 && R.rows >= 3 && R.cols >= 3); double m00 = R.at<double>(0,0), m01 = R.at<double>(0,1), m02 = R.at<double>(0,2); double m10 = R.at<double>(1,0), m11 = R.at<double>(1,1), m12 = R.at<double>(1,2); double m20 = R.at<double>(2,0), m21 = R.at<double>(2,1), m22 = R.at<double>(2,2); double trace = m00 + m11 + m22; double qw, qx, qy, qz; if (trace > 0) { double S = sqrt(trace + 1.0) * 2; // S=4*qw qw = 0.25 * S; qx = (m21 - m12) / S; qy = (m02 - m20) / S; qz = (m10 - m01) / S; } else if (m00 > m11 && m00 > m22) { double S = sqrt(1.0 + m00 - m11 - m22) * 2; // S=4*qx qw = (m21 - m12) / S; qx = 0.25 * S; qy = (m01 + m10) / S; qz = (m02 + m20) / S; } else if (m11 > m22) { double S = sqrt(1.0 + m11 - m00 - m22) * 2; // S=4*qy qw = (m02 - m20) / S; qx = (m01 + m10) / S; qy = 0.25 * S; qz = (m12 + m21) / S; } else { double S = sqrt(1.0 + m22 - m00 - m11) * 2; // S=4*qz qw = (m10 - m01) / S; qx = (m02 + m20) / S; qy = (m12 + m21) / S; qz = 0.25 * S; } return (Mat_<double>(4,1) << qw, qx, qy, qz); } // R = quat2rot(q) // // q - 4x1 unit quaternion <qw, qx, qy, qz> // R - 3x3 rotation matrix static Mat quat2rot(const Mat& q) { CV_Assert(q.type() == CV_64FC1 && q.rows == 4 && q.cols == 1); double qw = q.at<double>(0,0); double qx = q.at<double>(1,0); double qy = q.at<double>(2,0); double qz = q.at<double>(3,0); Mat R(3, 3, CV_64FC1); R.at<double>(0, 0) = 1 - 2*qy*qy - 2*qz*qz; R.at<double>(0, 1) = 2*qx*qy - 2*qz*qw; R.at<double>(0, 2) = 2*qx*qz + 2*qy*qw; R.at<double>(1, 0) = 2*qx*qy + 2*qz*qw; R.at<double>(1, 1) = 1 - 2*qx*qx - 2*qz*qz; R.at<double>(1, 2) = 2*qy*qz - 2*qx*qw; R.at<double>(2, 0) = 2*qx*qz - 2*qy*qw; R.at<double>(2, 1) = 2*qy*qz + 2*qx*qw; R.at<double>(2, 2) = 1 - 2*qx*qx - 2*qy*qy; return R; } // Kronecker product or tensor product // https://stackoverflow.com/a/36552682 static Mat kron(const Mat& A, const Mat& B) { CV_Assert(A 标签:
m11传感器底座结构对传感器性能的影响fn传感器