Eye-in-hand和Eye-to-hand問題求解和實驗
2018年12月07日 00:00:40 百川木易 閱讀數 3018
2018/12/5 By Yang Yang(yangyang@ipp.ac.cn)
本文所有源碼和仿真場景文件全部公開,點擊Gitee倉庫鏈接。
?
文章目錄
-
- 問題描述
- Eye-in-hand問題求解公式推導
- Eye-in-hand變換矩陣
- 求解方程
- 仿真實驗
- 搭建手眼系統仿真環境
- 生成棋盤圖
- 生成仿真棋盤標定板
- 編寫手眼系統場景文件
- 編寫基于RobWorkStudio的標定圖像采集模擬程序
- 編寫基于OpenCV的標定計算程序
- 棋盤角點和位姿檢測
- 標定計算
- 標定結果比較
- 搭建手眼系統仿真環境
- Eye-to-Hand問題求解公式推導
- Eye-to-Hand變換矩陣
- 求解方程
?
問題描述
機器人和攝像機的手眼標定問題分為兩類構型:
- eye-to-hand,攝像機固定,與機器人基坐標系相對位置不變。
- eye-in-hand,攝像機安裝在機器人末端,隨著機器人一起移動。
本文主要介紹eye-in-hand構型的標定方法和仿真實驗。對于eye-to-hand構型手眼系統,其標定方法與eye-in-hand構型的手眼系統沒有本質區別。
Eye-in-hand問題求解公式推導
Eye-in-hand變換矩陣
weHi?ecH=wgH?gcHi
ew?Hi??ce?H=gw?H?cg?Hi?
weHj?ecH=wgH?gcHj
ew?Hj??ce?H=gw?H?cg?Hj?
其中:
- weHi
ew?Hi? 和 weHjew?Hj? 表示機器人末端(end)TCP坐標系相對于機器人基座坐標系的齊次變換矩陣,分別對應第 ii 次和第 j
- j 次樣本。
- ecH
- ce?H 表示攝像機(camera)坐標系相對于機器人末端(end)TCP坐標系的齊次變換矩陣,這是手眼標定問題的求解目標。
- wgH
- gw?H 表示棋盤圖(grid)相對于世界坐標系(也就是機器人基坐標系)的齊次變換矩陣,由于在整個標定過程中,棋盤圖的位置不能變動,因此 wgH
- gw?H 是一個常量矩陣。
- gcHi
- cg?Hi? 和 gcHjcg?Hj? 表示攝像機坐標系相對于棋盤圖坐標系的齊次變換矩陣。注意:采用OpenCV的solvePnP函數得到的是棋盤圖相對于攝像機坐標系的齊次矩陣,即 cgHigc?Hi? 和 cgHj
- gc?Hj?,因此代入以上兩個公式時需要進行求逆計算。
齊次變換矩陣求逆方法
對于原齊次矩陣 ABT=[ABR0ApB01]AB?T=[BA?RT0??BA?RT?ApB0??1?]
求解方程
聯立上述的兩個公式,
weHi?ecH?(gcHi)?1=wgH
ew?Hi??ce?H?(cg?Hi?)?1=gw?H
weHj?ecH?(gcHj)?1=wgH
ew?Hj??ce?H?(cg?Hj?)?1=gw?H
消除常量矩陣 wgH
gw?H,
weHi?ecH?(gcHi)?1=weHj?ecH?(gcHj)?1
ew?Hi??ce?H?(cg?Hi?)?1=ew?Hj??ce?H?(cg?Hj?)?1
上式兩邊同時左乘 (weHj)?1
(ew?Hj?)?1 得,
(weHj)?1?weHi?ecH?(gcHi)?1=ecH?(gcHj)?1
(ew?Hj?)?1?ew?Hi??ce?H?(cg?Hi?)?1=ce?H?(cg?Hj?)?1
上式兩邊同時右乘 gcHi
cg?Hi? 得
(weHj)?1?weHi?ecH=ecH?(gcHj)?1?gcHi
(ew?Hj?)?1?ew?Hi??ce?H=ce?H?(cg?Hj?)?1?cg?Hi?
考慮到(gcHj)?1=(cgHj)
(cg?Hj?)?1=(gc?Hj?) 和 (gcHi)=(cgHi)?1(cg?Hi?)=(gc?Hi?)?1,因此上式可以轉換為,
(weHj)?1?weHi?ecH=ecH?cgHj?(cgHi)?1
(ew?Hj?)?1?ew?Hi??ce?H=ce?H?gc?Hj??(gc?Hi?)?1
令X=ecH
X=ce?H,A=(weHj)?1?weHiA=(ew?Hj?)?1?ew?Hi? 以及 B=cgHj?(cgHi)?1B=gc?Hj??(gc?Hi?)?1,則上式可以化簡為,
AX=XB
AX=XB
以上方程為手眼標定的最終求解方程,具體求解方式可以參考Tasi and Lenz,1989的論文A New Technique for Fully Autonomous and Efficient 3D Robotics Hand/Eye Calibration。
仿真實驗
以下描述通過仿真實驗的方式進行手眼標定測試和驗證的方法。大致流程如下:
- 手眼系統仿真場景搭建。
- 標定樣本采集。包括攝像機圖像采集,以及對應的機器人關節構型采集。
- 圖像處理提取棋盤圖角點并計算棋盤相對于攝像機坐標系的位姿。
- 采用基于Tasi-lenz的公式進行計算。
仿真實驗的主要環境配置和使用到的工具有:
- 操作系統:Windows 7 64bit
- 圖像處理工具:Matlab,OpenCV
- 機器人和攝像機仿真環境:RobWork (需先安裝QT、Boost等依賴庫)
- 虛擬棋盤圖編輯:AC3D,用于生成帶貼圖的棋盤標定板,加載到手眼仿真場景中
搭建手眼系統仿真環境
生成棋盤圖
利用如下MATLAB代碼"ChessboardGenerator"生成一個邊長為30mm,7行10列的棋盤圖,圖像最終保存為ppm格式的圖片文件。之所以選擇生成7行10列的棋盤圖,是因為這樣能夠方便的利用OpenCV進行棋盤角點檢測,并且保證每張圖像的棋盤圖坐標系原點都是一致的,角點檢測順序也不會發生改變。不應當使用正方形布置的棋盤圖,例如10行10列,這樣會導致OpenCV無法統一棋盤原點,給下一步的手眼標定工作帶來困難。
以下源碼參考用Matlab編寫的棋盤格圖像生成程序
clc; close all;GridSize = 30; %length of the square m =7; %number of row n = 10; % number of col margin = 10; % white boarder size I = ones(m*GridSize+2*margin,n*GridSize+2*margin)*255;%the first grid is black for i = 1:mif mod(i,2)==1for j = 1:nif mod(j,2)==1I(1+(i-1)*GridSize+margin:i*GridSize+margin,...1+(j-1)*GridSize+margin:j*GridSize+margin) = 0;endendelsefor j = 1:nif mod(j,2)==0I(1+(i-1)*GridSize+margin:i*GridSize+margin,...1+(j-1)*GridSize+margin:j*GridSize+margin) = 0;endendend end imshow(I);imwrite(I,'chessboard.ppm');
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
最終生成棋盤圖如下所示。
處理步驟:
- 首先利用ChessboardGenerator生成棋盤圖,格式為BMP。棋盤圖的長邊為10個方塊,短邊為7個方塊,每個方塊的尺寸為30個像素單位,另外棋盤圖還有10個像素單位的白邊。因此整個圖像的分辨率為320x230。
- 打開AC3D軟件,新建一個Rect矩形,通過MoveTo調整矩形的中心點坐標。
- 通過
, 調整矩形的尺寸。
- 選中矩形對象,在object->Texture設置紋理為步驟(1)中的ppm圖像。(圖像看起來比較模糊,測試:提高生成圖像分辨率,能夠獲得更清晰的虛擬Chessboard Marker)
- 將矩形保存成chessboard.ac格式文件,必要時用文本編輯器打開該.ac格式文件,如有必要,需修改紋理圖像的路徑為相對路徑。
編寫手眼系統場景文件
將上一步生成的chessboard.ac文件添加到場景XML文件中,并且在UR機器人末端設置一個虛擬攝像機,最終得到的機器人運行場景如下圖。
安裝在UR末端的攝像機的定義方式可以參考RobWork官網上的教程,本例中攝像機的定義方式如下如下:
<Frame name="Hand-Eye" refframe="UR.TCP"><RPY>0 -15 0</RPY><Pos>0.1 0 0</Pos> </Frame><Frame name="Hand-Eye-Sim" refframe="Hand-Eye" type="Movable"><RPY>0 0 180</RPY><Pos>0 0 0</Pos><Property name="CameraName">Hand-Eye</Property><Property name="Camera">31.0482 960 720</Property> </Frame>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
編寫基于RobWorkStudio的標定圖像采集模擬程序
需編寫一個RobWorkStudio的插件,用于采集攝像機圖片,以及與圖像對應的機器人TCP相對于機器人基坐標系的齊次變換矩陣,并保存到本地硬盤上。
源碼的運行要求安裝RobWork,安裝方法參考官網鏈接。
編譯本教程提供源碼時,需注意修改加載的機器人場景文件的路徑。
編寫基于OpenCV的標定計算程序
int cornerRow = 6;int cornerColum = 9;double patternLength = 30.0; //mmvector<rw::math::Transform3D<double> > chessboardPose;vector<Point3d> objectPoints;int pointNumber = cornerRow * cornerColum;for (int i = 0; i < cornerRow; i++){for (int j = 0; j < cornerColum; j++){Point3d p;p.x = patternLength * j;p.y = patternLength * i;p.z = 0;objectPoints.push_back(p);}}int imageResolutionX = 960;int imageResolutionY = 720;double fovy = 31.0482 * 3.14159265 / 180;double focalLength = (imageResolutionY / 2.0) / (tan(fovy / 2.0));Mat cameraMatrix = Mat(3, 3, CV_64FC1, 0.0f);cameraMatrix.at<double>(0, 0) = focalLength;cameraMatrix.at<double>(0, 2) = imageResolutionX / 2.0f;cameraMatrix.at<double>(1, 1) = focalLength;cameraMatrix.at<double>(1, 2) = imageResolutionY / 2.0f;cameraMatrix.at<double>(2, 2) = 1.0f;Mat distortionCoff(0, 0, 0, 0);cout << "\nCamera Matrix: \n" << cameraMatrix << endl;//namedWindow("Display Image", WINDOW_AUTOSIZE);int imageNumber = 25;for (int i = 0; i < imageNumber; i++){char imageName[150];sprintf(imageName, "..\\..\\data\\test-data-01\\image%05d.bmp", i);Mat imageColored = imread(imageName, 1);if (!imageColored.data){printf("No image data \n");return -1;}Mat image;cv::cvtColor(imageColored, image, CV_BGR2GRAY);Size patternSize(cornerColum, cornerRow);vector<Point2f> corners;bool patternfound = findChessboardCorners(image, patternSize, corners, CALIB_CB_ADAPTIVE_THRESH);if (patternfound){printf("found chessboard of image%05d!\n", i);cornerSubPix(image, corners, Size(11, 11), Size(-1, -1), TermCriteria(CV_TERMCRIT_EPS + CV_TERMCRIT_ITER, 30, 0.1));}Mat rvec; //reference to camera frameMat tvec; //reference to camera framesolvePnP(objectPoints, corners, cameraMatrix, distortionCoff, rvec, tvec);Mat chessbaordRotationMatrix; //reference to camera frameRodrigues(rvec, chessbaordRotationMatrix);rw::math::Transform3D<double> t;t(0, 0) = chessbaordRotationMatrix.at<double>(0, 0);t(0, 1) = chessbaordRotationMatrix.at<double>(0, 1);t(0, 2) = chessbaordRotationMatrix.at<double>(0, 2);t(0, 3) = tvec.at<double>(0) / 1000; //mm to mt(1, 0) = chessbaordRotationMatrix.at<double>(1, 0);t(1, 1) = chessbaordRotationMatrix.at<double>(1, 1);t(1, 2) = chessbaordRotationMatrix.at<double>(1, 2);t(1, 3) = tvec.at<double>(1) / 1000; //mm to mt(2, 0) = chessbaordRotationMatrix.at<double>(2, 0);t(2, 1) = chessbaordRotationMatrix.at<double>(2, 1);t(2, 2) = chessbaordRotationMatrix.at<double>(2, 2);t(2, 3) = tvec.at<double>(2) / 1000; //mm to m//printTransformation(t);chessboardPose.push_back(t);}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
其中一張棋盤圖的角點檢測結果如下圖:
標定計算
標定計算程序主要參考了經典手眼標定算法之Tsai-Lenz的OpenCV實現,主要源碼如下:
cv::Mat skew(cv::Mat m) {Mat t(3, 3, CV_64FC1);t.at<double>(0, 0) = 0;t.at<double>(0, 1) = - m.at<double>(2, 0);t.at<double>(0, 2) = m.at<double>(1, 0);t.at<double>(1, 0) = m.at<double>(2, 0);t.at<double>(1, 1) = 0;t.at<double>(1, 2) = - m.at<double>(0, 0);t.at<double>(2, 0) = - m.at<double>(1, 0);t.at<double>(2, 1) = m.at<double>(0, 0);t.at<double>(2, 2) = 0;return t; }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 rotationinvert(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; }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
標定結果比較
在自定義的手眼標定場景文件中,攝像機相對于機器人TCP的關系如下:
<Frame name="Hand-Eye" refframe="UR.TCP"><RPY>0 -15 0</RPY><Pos>0.1 0 0</Pos> </Frame>
- 1
- 2
- 3
- 4
轉換成4 ×
× 4的齊次變換矩陣如下:
ecH=??????0.96592600.25881900100?0.25881900.96592600.1001??????
ce?H=?????0.96592600.2588190?0100??0.25881900.9659260?0.1001??????
通過Tasi-Lenz方法計算得出的(不考慮攝像機參數誤差)攝像機相對于機器人TCP的關系如下:
ecH=??????0.9659861360843613?0.00031871107048437940.258593664493272700.00038437325583683220.9999999054504882?0.00020336227483621180?0.25859357522955960.00029584162685441490.965986167254855500.10012491158556857.92132967571962e?053.021520822331239e?051??????ce?H=?????0.9659861360843613?0.00031871107048437940.25859366449327270?0.00038437325583683220.9999999054504882?0.00020336227483621180??0.25859357522955960.00029584162685441490.96598616725485550?0.10012491158556857.92132967571962e?053.021520822331239e?051??????
通過對比以上兩組齊次變換矩陣結果,可以兩者是十分接近的,證明手眼標定結果是正確的。Eye-to-Hand問題求解公式推導
Eye-to-Hand變換矩陣
wcH?cgHi=weHi?egH
cw?H?gc?Hi?=ew?Hi??ge?H
wcH?cgHj=weHj?egHcw?H?gc?Hj?=ew?Hj??ge?H
其中:- wcH
- cw?H表示攝像機(camera)坐標系相對于機器人基坐標系(也是世界坐標,world)的齊次變換矩陣。這是Eye-to-hand問題的求解目標。
- cgHi
- gc?Hi?和cgHigc?Hi?表示棋盤圖(grid)相對于攝像機坐標系的齊次變換矩陣,分別對應第 ii 次和第 j
- j 次樣本。
- weHi
- ew?Hi? 和 weHjew?Hj? 表示機器人末端(end)TCP坐標系相對于機器人基座坐標系的齊次變換矩陣,分別對應第 ii 次和第 j
- j 次樣本。
- egH
- ge?H 表示棋盤圖(grid)相對于機器人末端TCP的齊次變換矩陣,由于在整個標定過程中,棋盤圖固定連接在機器人末端,因此 egH
- ge?H 是一個常量矩陣。
求解方程
改寫以上公式:
(ew?Hi?)?1?cw?H?gc?Hi?=ge?H
(weHi)?1?wcH?cgHi=egH
(weHj)?1?wcH?cgHj=egH(ew?Hj?)?1?cw?H?gc?Hj?=ge?H
消除常量egHge?H,
(weHi)?1?wcH?cgHi=(weHj)?1?wcH?cgHj(ew?Hi?)?1?cw?H?gc?Hi?=(ew?Hj?)?1?cw?H?gc?Hj?
上式兩邊同時左乘weHjew?Hj?得,
weHj?(weHi)?1?wcH?cgHi=wcH?cgHjew?Hj??(ew?Hi?)?1?cw?H?gc?Hi?=cw?H?gc?Hj?
上式兩邊同時右乘(cgHi)?1(gc?Hi?)?1,
weHj?(weHi)?1?wcH=wcH?cgHj?(cgHi)?1ew?Hj??(ew?Hi?)?1?cw?H=cw?H?gc?Hj??(gc?Hi?)?1
令X=wcHX=cw?H,A=weHj?(weHi)?1A=ew?Hj??(ew?Hi?)?1,B=cgHj?(cgHi)?1B=gc?Hj??(gc?Hi?)?1,可得
AX=XBAX=XB
以上方程是Eye-to-Hand標定的最終求解方程,具體求解方式可以參考Tasi and Lenz,1989的論文A New Technique for Fully Autonomous and Efficient 3D Robotics Hand/Eye Calibration。具體的eye-to-hand求解仿真可以參考eye-in-hand標定求解仿真進行,這里不再贅述