背景介绍

在opencv相机标定函数calibrateCamera中,根据标定板上特征点的3D坐标,以及对应的图像2D坐标,计算每个拍摄位置的初始位姿,以便后续的优化求解最终的内、外参数。cvFindExtrinsicCameraParams2函数中包含了两种外参数的估计方法(特征点在一个面上、特征点不在一个面上),以及迭代计算。该函数也是solvePnP函数选用CV_ITERATIVE时的主要执行方法。本文对cvFindExtrinsicCameraParams2函数中特征点在一个平面上时的外参估计方法进行解析,这个方法是平面标定板会执行的路线。

原理解析

因为是标定板上的特征点,3D的点近似在一个平面内,而图像2D点也是在平面上,很容易想到利用平面单应性的原理来解算3D-2D之间的关系。因此,第一步将是把3D点的坐标进行降维,变为2D点;然后第二步2D-2D点的单应性计算;最后第三步把降维矩阵和单应性矩阵汇总,得到我们想要的拍摄位姿。下面使用和代码中对应的符号来表达数据或矩阵,只是用于公式表达,和代码实际结构不一定相同,如代码中用齐次坐标。

  1. 倘若标定板上的点坐标一开始定义就是z=0z=0z=0,当然第一步就没有太多意义。而对于通用的情况,世界坐标系不在标定板上时,对于点集{matM}={xi,yi,zi}T\lbrace{matM\rbrace}=\lbrace{x_i,y_i,z_i\rbrace}^T{matM}={xi​,yi​,zi​}T去中心化坐标{matM−Mc}={xi−xˉ,yi−yˉ,zi−zˉ}T\lbrace{matM-Mc\rbrace}=\lbrace{x_i-\bar{x},y_i-\bar{y},z_i-\bar{z}\rbrace}^T{matM−Mc}={xi​−xˉ,yi​−yˉ​,zi​−zˉ}T每个点都减去均值。协方差矩阵为MM=(matM−Mc)T(matM−Mc)MM =(matM-Mc)^T(matM-Mc)MM=(matM−Mc)T(matM−Mc)那么对协方差矩阵MMMMMM进行SVD分解,matVmatVmatV的列就是特征向量,具体理论可以参考这个博文PCA,SVD以及协方差矩阵的关系。我们想把三维的{matM−Mc}\lbrace{matM-Mc\rbrace}{matM−Mc}压缩为二维,就是取特征值最大的前两个特征向量,也就是取matVmatVmatV矩阵的前两列(V11,V21,V31)和(V12,V22,V32)(V_{11},V_{21},V_{31})和(V_{12},V_{22},V_{32})(V11​,V21​,V31​)和(V12​,V22​,V32​)。
    ui=V11(xi−xˉ)+V21(yi−yˉ)+V31(zi−zˉ)vi=V12(xi−xˉ)+V22(yi−yˉ)+V32(zi−zˉ)u_i = V_{11}(x_i-\bar{x})+V_{21}(y_i-\bar{y})+V_{31}(z_i-\bar{z}) \\[2ex] v_i = V_{12}(x_i-\bar{x})+V_{22}(y_i-\bar{y})+V_{32}(z_i-\bar{z}) ui​=V11​(xi​−xˉ)+V21​(yi​−yˉ​)+V31​(zi​−zˉ)vi​=V12​(xi​−xˉ)+V22​(yi​−yˉ​)+V32​(zi​−zˉ)由此,将三维坐标matM={xi,yi,zi}matM=\lbrace{x_i,y_i,z_i\rbrace}matM={xi​,yi​,zi​}转为二维Mxy={ui,vi}Mxy=\lbrace{u_i,v_i\rbrace}Mxy={ui​,vi​}。三维点如果近似在一个平面内,SVD分解得到的对角线矩阵matWmatWmatW上的元素就是特征值,最后一个特征值相对来说很小,对应的matVmatVmatV的第三列特征向量有
    wi=V13(xi−xˉ)+V23(yi−yˉ)+V33(zi−zˉ)w_i =V_{13}(x_i-\bar{x})+V_{23}(y_i-\bar{y})+V_{33}(z_i-\bar{z}) wi​=V13​(xi​−xˉ)+V23​(yi​−yˉ​)+V33​(zi​−zˉ)得到的wiw_iwi​接近于0,就相当于把世界坐标系挪到了标定板上面来,并且zzz轴垂直于标定板。
  2. 根据二维Mxy={ui,vi}Mxy=\lbrace{u_i,v_i\rbrace}Mxy={ui​,vi​}和图像上的特征点去除畸变的坐标mn={ai,bi}mn=\lbrace{a_i,b_i\rbrace}mn={ai​,bi​},求解单应性矩阵HHH,可参考我之前的博文平面单应性变换
  3. 第2步得到了标定板坐标系与像平面的单应性变化HHH,进一步地能得到标定板坐标系与相机坐标系的变换RTCameraCalibPlaneRT^{CalibPlane}_{Camera}RTCameraCalibPlane​(这部分的转换不在这里解析)。第1步虽然名义上的数据降维,其实也可以认为是世界坐标系到标定板坐标系的变换,
    [uiviwi]=[V11V21V31V12V22V32V13V23V33][xiyizi]−[V11V21V31V12V22V32V13V23V33][xˉyˉzˉ]\begin{bmatrix} u_i \\ v_i \\ w_i \\ \end{bmatrix}=\begin{bmatrix} V_{11} & V_{21} & V_{31} \\ V_{12} & V_{22} & V_{32} \\ V_{13} & V_{23} & V_{33} \\ \end{bmatrix}\begin{bmatrix} x_i \\ y_i \\ z_i \\ \end{bmatrix} - \begin{bmatrix} V_{11} & V_{21} & V_{31} \\ V_{12} & V_{22} & V_{32} \\ V_{13} & V_{23} & V_{33} \\ \end{bmatrix}\begin{bmatrix} \bar{x} \\ \bar{y} \\ \bar{z} \\ \end{bmatrix} ⎣⎡​ui​vi​wi​​⎦⎤​=⎣⎡​V11​V12​V13​​V21​V22​V23​​V31​V32​V33​​⎦⎤​⎣⎡​xi​yi​zi​​⎦⎤​−⎣⎡​V11​V12​V13​​V21​V22​V23​​V31​V32​V33​​⎦⎤​⎣⎡​xˉyˉ​zˉ​⎦⎤​用上面的符号来改写成矩阵形式就是
    Mxy=matV∗matM−matV∗McMxy=matV*matM-matV*Mc Mxy=matV∗matM−matV∗Mc写成Mxy=R∗matM+TMxy=R*matM+TMxy=R∗matM+T的形式的话,其中Rtransform=matVRtransform=matVRtransform=matV, Ttransform=−matV∗McTtransform=-matV*McTtransform=−matV∗Mc。由第1步可得到世界坐标系到标定板坐标系的转换RTCalibPlaneWorldRT^{World}_{CalibPlane}RTCalibPlaneWorld​,结合前面的矩阵得到世界坐标系到相机坐标系的转换,也就是得到了我们想要的当前拍摄的位姿
    RTCameraWorld=RTCalibPlaneWorld∗RTCameraCalibPlaneRT^{World}_{Camera}=RT^{World}_{CalibPlane}*RT^{CalibPlane}_{Camera} RTCameraWorld​=RTCalibPlaneWorld​∗RTCameraCalibPlane​

代码注释

代码部分,我暂且将无关的部分删除,对涉及的主要部分添加说明,我加入的注释都用// **xx 的形式

CV_IMPL void cvFindExtrinsicCameraParams2( const CvMat* objectPoints,const CvMat* imagePoints, const CvMat* A,const CvMat* distCoeffs, CvMat* rvec, CvMat* tvec,int useExtrinsicGuess )
{const int max_iter = 20;Ptr<CvMat> matM, _Mxy, _m, _mn, matL;int i, count;double a[9], ar[9]={1,0,0,0,1,0,0,0,1}, R[9];double MM[9], U[9], V[9], W[3];CvScalar Mc;double param[6];CvMat matA = cvMat( 3, 3, CV_64F, a );CvMat _Ar = cvMat( 3, 3, CV_64F, ar );CvMat matR = cvMat( 3, 3, CV_64F, R );CvMat _r = cvMat( 3, 1, CV_64F, param );CvMat _t = cvMat( 3, 1, CV_64F, param + 3 );CvMat _Mc = cvMat( 1, 3, CV_64F, Mc.val );CvMat _MM = cvMat( 3, 3, CV_64F, MM );CvMat matU = cvMat( 3, 3, CV_64F, U );CvMat matV = cvMat( 3, 3, CV_64F, V );CvMat matW = cvMat( 3, 1, CV_64F, W );CvMat _param = cvMat( 6, 1, CV_64F, param );CvMat _dpdr, _dpdt;// **删除了一些判断和初始化           cvConvertPointsHomogeneous( objectPoints, matM );cvConvertPointsHomogeneous( imagePoints, _m );// **删除了一些判断和初始化// normalize image points// **去图像点畸变cvUndistortPoints( _m, _mn, &matA, distCoeffs, 0, &_Ar );if( useExtrinsicGuess ){CvMat _r_temp = cvMat(rvec->rows, rvec->cols,CV_MAKETYPE(CV_64F,CV_MAT_CN(rvec->type)), param );CvMat _t_temp = cvMat(tvec->rows, tvec->cols,CV_MAKETYPE(CV_64F,CV_MAT_CN(tvec->type)), param + 3);cvConvert( rvec, &_r_temp );cvConvert( tvec, &_t_temp );}else{// **3D点的均值中心坐标Mc = cvAvg(matM);cvReshape( matM, matM, 1, count );// **_MM = (matM-_Mc)^T * (matM-_Mc)协方差矩阵cvMulTransposed( matM, &_MM, 1, &_Mc );// SVD分解得到matVcvSVD( &_MM, &matW, 0, &matV, CV_SVD_MODIFY_A + CV_SVD_V_T );// initialize extrinsic parameters// **从前面的初始化可知matW与W是关联的,SVD得到的对角线矩阵的元素是特征值,若3D点在近似平面上,第3个特征值相对来说很小if( W[2]/W[1] < 1e-3){// a planar structure case (all M's lie in the same plane)double tt[3], h[9], h1_norm, h2_norm;// **世界坐标系到标定板坐标系的R矩阵就是SVD分解的V,参见原理解析第3步// **R_transform = matVCvMat* R_transform = &matV;CvMat T_transform = cvMat( 3, 1, CV_64F, tt );CvMat matH = cvMat( 3, 3, CV_64F, h );CvMat _h1, _h2, _h3;if( V[2]*V[2] + V[5]*V[5] < 1e-10 )cvSetIdentity( R_transform );if( cvDet(R_transform) < 0 )cvScale( R_transform, R_transform, -1 );// **世界坐标系到标定板坐标系的T矩阵求解,参见原理解析第3步// **T_transform=-matV*MccvGEMM( R_transform, &_Mc, -1, 0, 0, &T_transform, CV_GEMM_B_T );for( i = 0; i < count; i++ ){const double* Rp = R_transform->data.db;const double* Tp = T_transform.data.db;// **三维坐标点降维,到平面坐标上const double* src = matM->data.db + i*3;double* dst = _Mxy->data.db + i*2;dst[0] = Rp[0]*src[0] + Rp[1]*src[1] + Rp[2]*src[2] + Tp[0];dst[1] = Rp[3]*src[0] + Rp[4]*src[1] + Rp[5]*src[2] + Tp[1];}// **解算标定板平面与像平面的单应性变换cvFindHomography( _Mxy, _mn, &matH );if( cvCheckArr(&matH, CV_CHECK_QUIET) ){cvGetCol( &matH, &_h1, 0 );_h2 = _h1; _h2.data.db++;_h3 = _h2; _h3.data.db++;h1_norm = std::sqrt(h[0]*h[0] + h[3]*h[3] + h[6]*h[6]);h2_norm = std::sqrt(h[1]*h[1] + h[4]*h[4] + h[7]*h[7]);cvScale( &_h1, &_h1, 1./MAX(h1_norm, DBL_EPSILON) );cvScale( &_h2, &_h2, 1./MAX(h2_norm, DBL_EPSILON) );cvScale( &_h3, &_t, 2./MAX(h1_norm + h2_norm, DBL_EPSILON));cvCrossProduct( &_h1, &_h2, &_h3 );// **单应性矩阵求解变换RT的过程(本文不做解析)cvRodrigues2( &matH, &_r );cvRodrigues2( &_r, &matH );// **综合第1步和第2步的变换矩阵,得到拍摄位姿cvMatMulAdd( &matH, &T_transform, &_t, &_t );cvMatMul( &matH, R_transform, &matR );}else{cvSetIdentity( &matR );cvZero( &_t );}// **罗德里格斯转换,矩阵用向量角表达cvRodrigues2( &matR, &_r );}else{// non-planar structure. Use DLT method}}// 删除了迭代计算cvConvert( &_r, rvec );cvConvert( &_t, tvec );
}

OpenCV内部函数cvFindExtrinsicCameraParams2解析(一)相关推荐

  1. OpenCV读写图像文件解析

    OpenCV读写图像文件解析 imdecode 从内存中的缓冲区读取图像. C++: Mat imdecode(InputArray buf, int flags) C++: Mat imdecode ...

  2. 进入opencv内部函数调试

    主要问题参考:http://blog.csdn.net/daven172/article/details/45769129 这篇博客. 我们进入opencv内部函数进行调试,主要是设置断点,然后按下F ...

  3. Opencv 光流法解析

    KLT 什么是光流以及如何求解光流(利用最小二乘法求解) locateROI adjustROI pyrUp pyrDown Opencv 光流法解析 ```cpp /** @brief Calcul ...

  4. C/C++实现双目矫正(不使用OpenCV内部函数)及矫正源码解析

    C/CPP实现双目矫正(不使用OpenCV)及矫正源码解析 这篇文章是之前[要matlab标定数据做双目相机矫正OpenCV C++]的补充,再加上了双目矫正的原理及代码注释.更新中-- 本文所需数据 ...

  5. OpenCV goodFeaturesToTrack特征提取解析笔记

    文章目录 1. goodFeaturesToTrack算法描述 2. goodFeaturesToTrack代码解析 1. goodFeaturesToTrack算法描述 goodFeaturesTo ...

  6. OpenCV之imread解析

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/firstlai/article/det ...

  7. opencv源码解析之(6):hog源码分析

    一.网上一些参考资料     在博客目标检测学习_1(用opencv自带hog实现行人检测) 中已经使用了opencv自带的函数detectMultiScale()实现了对行人的检测,当然了,该算法采 ...

  8. opencv 文件模块 解析

    OpenCV包括以下几个模块,具体功能是: 1.CV:主要的OpenCV函数 2.CVAUX:辅助的(实验性的)OpenCV函数 3.CXCORE:数据结构与线性代数支持 4.HIGHGUI:图像界面 ...

  9. OpenCV编程-无法解析的外部符号 void __cdecl cv::cvtColor

    运行环境:VS2012   &&   OpenCV2.4.8 错误提示: 1>test1.obj : error LNK2019: 无法解析的外部符号 "void __ ...

最新文章

  1. android标题栏(titlebar)显示进度条
  2. 设为首页加入收藏代码
  3. 天问一号火星探测器已飞离地球800多万公里 多个载荷完成自检
  4. 安装MYSQL最低的RAM_安装MySQL后,需要调整的10个性能配置项
  5. 个人学习机器学习笔记--
  6. (44)Verilog HDL 计数器设计
  7. python 类 super_python的类的super()
  8. python批量下载文件只有1kb_(尚有报错、待完善)从一些网站(网易公开课、电影网站)上批量获得相关视频文件的下载地址,并保存在一个x.txt文件中...
  9. Linux上配置Gaussian的方法
  10. (图)关键路径算法 (含AOV AOE网比较)
  11. 苹果开发证书导出P12的问题
  12. 转:sklearn 用户手册之1.12. 多类别与多标签算法
  13. GNN-图卷积模型-2016:PATCHY-SAN【图结构序列化:将图结构转换成了序列结构,然后直接利用卷积神经网络在转化成的序列结构上做卷积】
  14. UNILUX频闪仪维修LITH-O-LIGHT-5频闪灯LOL-5/LOL-10
  15. 2月14日机构龙虎榜和知名游资操作情况
  16. 不知怎么选,用RFM模型看舔狗质量!
  17. Unity3D中场景烘培步骤分享
  18. [网摘]---有关int,Int32的疑惑解答
  19. I/O复用:select、poll和epoll函数
  20. 智能手机巨头Oppo加快AR的发展

热门文章

  1. 华为交换机、路由器配置静态路由实现不同网段通信
  2. 一段代码实现WordPress彩色标签云
  3. 开一个水果店如何进货呢,水果店进货多少
  4. CommMonitor监控串口数据
  5. JavaScript 简介 JavaScript 插入 HTML 页面后,可由所有的现代浏览器执行。
  6. Linux c fopen() 与fclose() 使用
  7. EDID是什么?为什么要使用它?和DDC的关系?
  8. 如何快速打胖包和瘦包
  9. 《社交媒体大数据分析——理解并影响消费者行为》一第1章 市场营销
  10. html字体颜色反色,HTML5:画布上的反色文本颜色