手眼標定演算法---Sai-Lenz(A New Technique for Fully Autonomous and Efficient 3D Robotics Hand/Eye Calibrati)
本文主要是講解經典手眼標定問題中的TSAI-LENZ 文獻方法,參考文獻為“A New Technique for Fully Autonomous and Efficient 3D Robotics Hand/Eye Calibration” 轉自:https://blog.csdn.net/YunlinWang/article/details/51622143
在機器人校準測量、機器人手眼協調以及機器人輔助測量等領域,都要求知道機器人執行器末端(抓取臂)座標系和感測器(比如用來測量三維空間中目標位置和方向並固定在機器人手上的攝像機)座標系之間的相互關係,確定這種轉換關係在機器人領域就是通常所說的手眼標定
將手眼標定系統如下圖所示,其中HgijHgij為機器人執行器末端座標系之間相對位置姿態的齊次變換矩陣;HcijHcij為攝像機座標系之間相對位置姿態的齊次變換矩陣;HcgHcg為像機與機器人執行器末端之間的相對位置姿態齊次矩陣。
經過座標系變換,HgijHgij、HcijHcij和HcgHcg滿足如下關係:
將上式展開,可以得到手眼標定的基本方程
因此,手眼標定問題也就轉化為從上述方程組中求解出RcgRcg和TcgTcg,下面就按照TSAI文獻所述求解該方程組。
“兩步法”手眼標定
一般用“兩步法”求解基本方程,即先從基本方程上式求解出RcgRcg,再代入下式求解出TcgTcg。在TSAI文獻中引入旋轉軸-旋轉角系統來描述旋轉運動來進行求解該方程組,具體的公式推導可以檢視原始文獻,這裡只歸納計算步驟,不明白的地方可閱讀文獻,計算步驟如下:
Step1:利用羅德里格斯變換將旋轉矩陣轉換為旋轉向量
Step2:向量歸一化
Step3:修正的羅德里格斯引數表示姿態變化
Step4:計算初始旋轉向量P′cg
其中,skew為反對稱運算,假設一個三維向量V=[vx;vy;vz],其反對稱矩陣為:
Step5:計算旋轉向量Pcg
Step6:計算旋轉矩陣Rcg
Step7:計算平移向量Tcg
演算法原始碼
根據上述基本計算步驟,在利用OpenCV 2.0開源庫的基礎上,編寫Tsai手眼標定方法的c++程式,其實現函式程式碼如下:
void Tsai_HandEye(Mat Hcg, vector<Mat> Hgij, vector<Mat> Hcij)
{
CV_Assert(Hgij.size() == Hcij.size());
int nStatus = Hgij.size();
Mat Rgij(3, 3, CV_64FC1);
Mat Rcij(3, 3, CV_64FC1);
Mat rgij(3, 1, CV_64FC1);
Mat rcij(3, 1, CV_64FC1);
double theta_gij;
double theta_cij;
Mat rngij(3, 1, CV_64FC1);
Mat rncij(3, 1, CV_64FC1);
Mat Pgij(3, 1, CV_64FC1);
Mat Pcij(3, 1, CV_64FC1);
Mat tempA(3, 3, CV_64FC1);
Mat tempb(3, 1, CV_64FC1);
Mat A;
Mat b;
Mat pinA;
Mat Pcg_prime(3, 1, CV_64FC1);
Mat Pcg(3, 1, CV_64FC1);
Mat PcgTrs(1, 3, CV_64FC1);
Mat Rcg(3, 3, CV_64FC1);
Mat eyeM = Mat::eye(3, 3, CV_64FC1);
Mat Tgij(3, 1, CV_64FC1);
Mat Tcij(3, 1, CV_64FC1);
Mat tempAA(3, 3, CV_64FC1);
Mat tempbb(3, 1, CV_64FC1);
Mat AA;
Mat bb;
Mat pinAA;
Mat Tcg(3, 1, CV_64FC1);
for (int i = 0; i < nStatus; i++)
{
Hgij[i](Rect(0, 0, 3, 3)).copyTo(Rgij);
Hcij[i](Rect(0, 0, 3, 3)).copyTo(Rcij);
Rodrigues(Rgij, rgij);
Rodrigues(Rcij, rcij);
theta_gij = norm(rgij);
theta_cij = norm(rcij);
rngij = rgij / theta_gij;
rncij = rcij / theta_cij;
Pgij = 2 * sin(theta_gij / 2)*rngij;
Pcij = 2 * sin(theta_cij / 2)*rncij;
tempA = skew(Pgij + Pcij);
tempb = Pcij - Pgij;
A.push_back(tempA);
b.push_back(tempb);
}
//Compute rotation
invert(A, pinA, DECOMP_SVD);
Pcg_prime = pinA * b;
Pcg = 2 * Pcg_prime / sqrt(1 + norm(Pcg_prime) * norm(Pcg_prime));
PcgTrs = Pcg.t();
Rcg = (1 - norm(Pcg) * norm(Pcg) / 2) * eyeM + 0.5 * (Pcg * PcgTrs + sqrt(4 - norm(Pcg)*norm(Pcg))*skew(Pcg));
//Computer Translation
for (int i = 0; i < nStatus; i++)
{
Hgij[i](Rect(0, 0, 3, 3)).copyTo(Rgij);
Hcij[i](Rect(0, 0, 3, 3)).copyTo(Rcij);
Hgij[i](Rect(3, 0, 1, 3)).copyTo(Tgij);
Hcij[i](Rect(3, 0, 1, 3)).copyTo(Tcij);
tempAA = Rgij - eyeM;
tempbb = Rcg * Tcij - Tgij;
AA.push_back(tempAA);
bb.push_back(tempbb);
}
invert(AA, pinAA, DECOMP_SVD);
Tcg = pinAA * bb;
Rcg.copyTo(Hcg(Rect(0, 0, 3, 3)));
Tcg.copyTo(Hcg(Rect(3, 0, 1, 3)));
Hcg.at<double>(3, 0) = 0.0;
Hcg.at<double>(3, 1) = 0.0;
Hcg.at<double>(3, 2) = 0.0;
Hcg.at<double>(3, 3) = 1.0;
}