基于opencv3的本质矩阵(essential matrix)估计算例

至今opencv3面世快两年了,五一期间抽空看了下新增的几个关于双目视觉标定算法的使用。


关于双目的标定有一类方法是基于本质矩阵(Essential matrix)的标定方法,本质方程与基础方程都是刻画双目视觉极几何关系的两个方程,只不过它们所使用的坐标系不一样而已,本质方程是在相机光心坐标系下描述的双目成像几何关系,而基础方程是本质方程在相机像素坐标系下的表示。


由于本质矩阵是平移量t叉乘旋转矩阵R的结果。所以通过本质方程估计出本质矩阵,然后再对本质矩阵进行运动分解,即可得到双目摄像头之间的相互位置关系,即双目的标定。值得一提的是,由于本质矩阵的秩为2,分解出来的平移向量t只能得到方向,其模需要使用其它方法来获取。


理论上可以直接使用估计基础矩阵(Fundamental matrix)的方法来估计本质矩阵,如:8点算法,7点算法,最小中值法,RANSAC法等。但是实际中使用这些方法估计出的本质矩阵的结果并不太令人满意,尤其当匹配点对趋于共面时,这些方法获得的结果完全就不能用。如果用于标定的匹配点对,是在一个平面上的话,这些方法将完全失效,因为平面是一种退化结构或者叫临界曲线(Critical surfaces)。而且在opencv3之前的opencv版本只提供了求取基础矩阵F的API,虽然cv::stereoCalibrate(...)可以得到双目标定结果以及本质矩阵E,但该方法是使用平面标定版辅助的plane2plane估计出各相机的外参数,然后由外参数直接计算出旋转矩阵R和平移量t,此时获取的刚体运动参数自由度为6。


此外双目标定还有一些方法并不是基于本质矩阵来完成的,如:光束法平差(Bundle adjustment)、结构重投影法等。在此就不进行讨论了。


由于基础矩阵的自由度为7,而本质矩阵的自由度为5。直接使用基础矩阵的估计方法来估计本质矩阵并不适合,而且本质矩阵还有一种叫做5点算法的方法可以估计本质矩阵,该算法比较复杂,涉及到高次多项式方程的求解。但是对于估计本质矩阵,5点算法会得到比7点或8点算法更好的结果,而且不受匹配点集共面的影响。该方法的具体原理可见论文David Nistér. An efficient solution to the five-point relative pose problem. Pattern Analysis and Machine Intelligence, IEEE Transactions on, 26(6):756–770, 2004.和Henrik Stewenius. Calibrated fivepoint solver.


5点算法的大概思路就是把本质方程展开,再加上本质矩阵的充要条件。化简后得到一个高次多项式方程,其实数解中的一个确定出的本质矩阵是系统的真实本质矩阵,对伪解进行剔除。然后对获得的本质矩阵进行使用SVD分解而得到旋转矩阵R和单位平移量t,即完成双目的相互位置标定。


opencv3中的cv::findEssentialMat(...)即是基于该五点算法实现的本质矩阵的估计,附上使用代码


[cpp]  view plain  copy
  1. /************************************************************** 
  2. * an example to estimate essential matrix and relative pose of  
  3. * binocular vision sensor using the API function of opencv3.0.0 
  4. *   
  5. * by jah10527@126.com 
  6. * May, 1st, 2016 
  7.  
  8. ***************************************************************/  
  9.   
  10.   
  11. #include <opencv2/opencv.hpp>  
  12. #include <iostream>  
  13.   
  14. using namespace cv;  
  15. using namespace std;  
  16.   
  17.   
  18. static void calcChessboardCorners(Size boardSize, float squareSize, vector<Point3f>& corners)  
  19. {  
  20.     corners.resize(0);  
  21.   
  22.     forint i = 0; i < boardSize.height; i++ )  
  23.         forint j = 0; j < boardSize.width; j++ )  
  24.             corners.push_back(Point3f(float(j*squareSize),  
  25.                                       float(i*squareSize), 0));  
  26. }  
  27.   
  28. int main()  
  29. {  
  30.     int i, point_count = 54;  
  31.     vector<Point2f> rpoints(point_count);  
  32.     vector<Point2f> lpoints(point_count);  
  33.       
  34.     Mat Ml(3,3,CV_64F);  
  35.     Mat Mr(3,3,CV_64F);  
  36.       
  37.     FileStorage fs("camera_l.yml", FileStorage::READ);  
  38.     if( !fs.isOpened() )  
  39.         return -1;  
  40.     fs["camera_matrix"] >> Ml;  
  41.     fs.release();   
  42.       
  43.     fs.open("camera_r.yml", FileStorage::READ);  
  44.     if( !fs.isOpened() )  
  45.         return -1;  
  46.     fs["camera_matrix"] >> Mr;  
  47.     fs.release();   
  48.       
  49.     cout << "Mr:" << endl << Mr << endl;  
  50.     cout << "Ml:" << endl << Ml << endl;  
  51.       
  52.     Mat rim=imread("fight01.jpg", IMREAD_GRAYSCALE);  
  53.     Mat lim=imread("feft01.jpg", IMREAD_GRAYSCALE);  
  54.     findChessboardCorners( rim, Size(6,9), rpoints,  
  55.                     CALIB_CB_ADAPTIVE_THRESH | CALIB_CB_FAST_CHECK | CALIB_CB_NORMALIZE_IMAGE);  
  56.     findChessboardCorners( lim, Size(6,9), lpoints,  
  57.                     CALIB_CB_ADAPTIVE_THRESH | CALIB_CB_FAST_CHECK | CALIB_CB_NORMALIZE_IMAGE);  
  58. /*  cout << "right:" << endl; 
  59.     for (i=0;i<point_count;i++) 
  60.         cout << rpoints[i] << endl; 
  61.     cout << "left:" << endl; 
  62.     for (i=0;i<point_count;i++) 
  63.         cout << lpoints[i] << endl;*/  
  64.     cornerSubPix( rim, rpoints, Size(8,8),  
  65.             Size(-1,-1), TermCriteria( TermCriteria::EPS+TermCriteria::COUNT, 30, 0.1 ));  
  66.     cornerSubPix( lim, lpoints, Size(8,8),  
  67.             Size(-1,-1), TermCriteria( TermCriteria::EPS+TermCriteria::COUNT, 30, 0.1 ));  
  68.               
  69.     for (i=0;i<point_count;i++)  
  70.     {  
  71.         rpoints[i].x = (rpoints[i].x-*((double*)Mr.data+2))/ *((double*)Mr.data);  
  72.         rpoints[i].y = (rpoints[i].y-*((double*)Mr.data+5))/ *((double*)Mr.data+4);  
  73.         lpoints[i].x = (lpoints[i].x-*((double*)Ml.data+2))/ *((double*)Ml.data);  
  74.         lpoints[i].y = (lpoints[i].y-*((double*)Ml.data+5))/ *((double*)Ml.data+4);  
  75. //      cout << "r:" << rpoints[i] << endl;  
  76. //      cout << "l:" << lpoints[i] << endl;  
  77.     }  
  78.       
  79.     Mat E_mat = findEssentialMat(rpoints, lpoints, 1, Point2d(0, 0), RANSAC, 0.999, 1.f);  
  80.     cout << "E:" << endl << E_mat << endl;  
  81.   
  82. /*  Mat rr(3,1,CV_64F); 
  83.     Mat ll(1,3,CV_64F); 
  84.     *((double*)rr.data+2)=1; 
  85.     *((double*)ll.data+2)=1; 
  86.     for (i=0;i<8;i++){ 
  87.         *((double*)rr.data) = rpoints[i].x; 
  88.         *((double*)rr.data+1) = rpoints[i].y; 
  89.         *((double*)ll.data) = lpoints[i].x; 
  90.         *((double*)ll.data+1) = lpoints[i].y; 
  91.         cout << ll*E_mat*rr << endl; 
  92.         cout << rr.t()*E_mat*ll.t() << endl; 
  93.     } 
  94.     Mat R1, R2, t; 
  95.     decomposeEssentialMat(E_mat, R1, R2, t); 
  96.     cout << "R1:" << endl << R1 << endl; 
  97.     cout << "R2:" << endl << R2 << endl; 
  98.     cout << "t:" << endl << t << endl;*/  
  99.       
  100.     Mat R, t;  
  101.     recoverPose(E_mat, rpoints, lpoints, R, t);  
  102.     cout << "t:" << endl << t << endl;  
  103.     cout << "R:" << endl << R << endl;  
  104.     Mat om;  
  105.     Rodrigues(R, om);  
  106.     cout << "om:" << endl << om << endl;  
  107.       
  108.       
  109.     vector<vector<Point3f> > objpt(1);  
  110.     vector<vector<Point2f> > imgpt(1);  
  111.     vector<vector<Point2f> > imgpt_right(1);  
  112.     for (i=0;i<point_count;i++)  
  113.     {  
  114.         imgpt[0].push_back(rpoints[i]);  
  115.         imgpt_right[0].push_back(lpoints[i]);  
  116.     }  
  117.     calcChessboardCorners(Size(6,9), 0.03, objpt[0]);  
  118.   
  119.     Mat RR, TT, E, F;  
  120.     Mat eye3, zeros5;  
  121.     eye3 = Mat::eye(3,3,CV_64F);  
  122.     zeros5 = Mat::zeros(1,5,CV_64F);  
  123.     double err = stereoCalibrate(objpt, imgpt, imgpt_right, eye3, zeros5, eye3, zeros5,  
  124.                                 Size(640,480), RR, TT, E, F, CALIB_FIX_INTRINSIC,  
  125.                                 TermCriteria( TermCriteria::EPS+TermCriteria::COUNT, 30, 0.1 ));  
  126.     cout << "Pair calibration reprojection error = " << err << endl;  
  127.     Rodrigues(RR, om);  
  128.     cout << "TT:" << endl << TT << endl;  
  129.     cout << "RR:" << endl << RR << endl;  
  130.     cout << "om:" << endl << om <<endl;  
  131.     cout << "E:" << endl << E << endl;  
  132.   
  133.     return 0;  
  134. }  



可以看出两种标定方法获取的结果不太一样。


重构出的标定版的三维坐标如下图所示,红色为本质矩阵方法获得的标定版空间情况,蓝色为stereoCalibrate(...)的运动参数三维重构标定版角点坐标的结果。



源码及数据下载点击打开链接

  • 2
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
SFM (Structure-from-Motion)算法可以用于从一系列2D图像中恢复出3D场景和相机的内外参矩阵。在这个过程中,相机的内参矩阵是一个非常重要的参数,因为它描述了相机的畸变、焦距、主点等信息。 在Python中,可以使用OpenCV库来实现SFM算法,并获取相机的内参矩阵。具体步骤如下: 1. 读取图像序列,并进行特征点提取和匹配。 ```python import cv2 # 读取图像序列 img1 = cv2.imread('image1.jpg') img2 = cv2.imread('image2.jpg') # 特征点提取和匹配 sift = cv2.xfeatures2d.SIFT_create() kp1, des1 = sift.detectAndCompute(img1, None) kp2, des2 = sift.detectAndCompute(img2, None) bf = cv2.BFMatcher() matches = bf.knnMatch(des1, des2, k=2) ``` 2. 通过Essential Matrix计算相机的内外参矩阵。 ```python # 通过Essential Matrix计算相机的内外参矩阵 pts1 = [] pts2 = [] for m, n in matches: if m.distance < 0.75 * n.distance: pts1.append(kp1[m.queryIdx].pt) pts2.append(kp2[m.trainIdx].pt) pts1 = np.int32(pts1) pts2 = np.int32(pts2) F, mask = cv2.findFundamentalMat(pts1, pts2, cv2.FM_RANSAC) E, _ = cv2.findEssentialMat(pts1, pts2, focal=1.0, pp=(0, 0)) _, R, t, mask = cv2.recoverPose(E, pts1, pts2) K = np.array([[fx, 0, cx], [0, fy, cy], [0, 0, 1]]) ``` 其中,`fx`和`fy`是相机的焦距,`cx`和`cy`是主点的坐标。 3. 获取相机的内参矩阵。 ```python # 获取相机的内参矩阵 fx = K[0, 0] fy = K[1, 1] cx = K[0, 2] cy = K[1, 2] ``` 这样,就可以使用SFM算法获取相机的内参矩阵了。需要注意的是,这里的相机内参矩阵是基于单应性(Homography)的恢复方法,可能存在误差。如果需要更高精度的相机内参矩阵,可以使用标定板等方法进行相机标定。
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值