我使用openCV函数projectPoints()来旋转,平移和投影一组3D点和solvePnp()来查找此旋转和平移。当镜头失真系数全为零但是否则失败时,这很有效。完全失败只需要很少的失真:
distCoeffs << 0.0, 0.01, 0.0, 0.0, 0.0;
代码如下:
#include <iostream>
#include "opencv.hpp"
using namespace std;
using namespace cv;
#define DEG2RAD (3.1415293/180.0)
#define RAD2DEG (1.0/DEG2RAD)
int main() {
const int npoints = 10; // number of points
// extrinsic
const Point3f tvec(10, 20, 30);
Point3f rvec(3, 5, 7);
cout << "Finding extrinsic parameters (PnP)" << endl;
cout<<"Test transformations: ";
cout<<"Rotation: "<<rvec<<"; translation: "<<tvec<<endl;
rvec*=DEG2RAD;
// intrinsic
Mat_ <double>cameraMatrix(3, 3);
cameraMatrix << 300., 0., 200., 0, 300., 100., 0., 0., 1.;
Mat_ <double>distCoeffs(1, 5); // (k_1, k_2, p_1, p_2[, k_3[, k_4, k_5, k_6]]) of 4, 5, or 8 elements.
//distCoeffs << 1.2, 0.2, 0., 0., 0.; // non-zero distortion
distCoeffs << 0.0, 0.0, 0.0, 0.0, 0.0; // zero distortion
cout<<"distrotion coeff: "<<distCoeffs<<endl;
cout<<"============= Running PnP..."<<endl;
vector<Point3f> objPts(npoints);
vector<Point2f> imagePoints(npoints);
Mat rvec_est, tvec_est;
randu(Mat(objPts), 0.0f, 100.0f);
// project
projectPoints(Mat(objPts), Mat(rvec), Mat(tvec), cameraMatrix, distCoeffs, Mat(imagePoints));
// extrinsic
solvePnP(objPts, imagePoints, cameraMatrix, distCoeffs, rvec_est, tvec_est);
cout<<"Rotation: "<<rvec_est*RAD2DEG<<endl;
cout<<"Translation "<<tvec_est<<endl;
return 0;
}
当所有失真系数均为0时,结果为OK:
寻找外在参数(PnP)
测试变换:旋转:[3,5,7];翻译:[10,20,30]
distrotion coeff:[0,0,0,0,0]
=============运行PnP ...
轮换:[2.999999581709123; 4.999997813985293; 6.999999826089725]
翻译[9.999999792663072; 19.99999648222693; 29.99999699621362]
然而,当它们不为零时,结果完全错误:
寻找外在参数(PnP)
测试变换:旋转:[3,5,7];翻译:[10,20,30]
distrotion coeff:[1.2,0.2,0,0,0]
=============运行PnP ...
轮换:[ - 91.56479629305277; -124.3631985067845; -74.46486950666471]
翻译[-69.72473511009439; -117.7463271636532; -87.27777166027946]
由于人们问,我正在添加中间输入 - 一些3D点及其对非零失真系数的投影。我的相机矩阵是 cameraMatrix&lt;&lt; 300.,0.,200。,0,300。,100.,0.,0.,1。;
3d points [53.0283,19.9259,40.1059];二维投影[1060.34,700.59]
3d points [81.4385,43.7133,24.879];二维投影[6553.88,5344.22]
3d points [77.3105,76.2094,30.7794];二维投影[5143.32,6497.12]
3d points [70.2432,47.8447,79.219];二维投影[771.497,611.726]
另一个有趣的观察结果:当distCoeff非零时应用undistort并不真正起作用(但是当失真系数都为0时它会产生相同的2D点):
cout<<"applying undistort..."<<endl;
vector<Point2f> imagePointsUndistort(npoints);
undistortPoints(Mat(imagePoints), Mat(imagePointsUndistort), cameraMatrix, distCoeffs);
for (int i=0; i<4; i++)
cout<<"2d original "<<imagePoints[i]<<"; 2d undistort "<<imagePointsUndistort[i]<<endl;
应用无瑕疵......
2d原始[1060.34,700.59]; 2d不失真[0,0]
2d原版[6553.88,5344.22]; 2d不失真[0,0]
2d原文[5143.32,6497.12]; 2d不失真[0,0]
2d原始[771.497,611.726]; 2d undistort [0,0]
我尝试undistort()的原因是因为如果释放已知内部参数的影响,PnP只会成为Ax = 0形式的最小方向问题。它需要分钟。 6个点用于近似线性解,可能用LMA(flags = CV_ITERATIVE)进一步改善。从技术上讲,只有6DOF,因此需要3个点,所以其他方法(flags = CV_P3P,CV_EPNP)占用的点数更少。无论如何,无论方法或点数如何,结果仍然无效,具有非零失真系数。我将尝试的最后一件事是将所有点放在3D平面上。它仍然失败:
for (int i=0; i<npoints; i++)
objPts[i].z=0.0f;
寻找外在参数(PnP)
测试变换:旋转:[3,5,7];翻译:[10,20,30]
distrotion coeff:[1.2,0.2,0,0,0]
=============运行PnP ...
轮换:[ - 1830.321574903016; 2542.206083947917; 2532.255948350521]
翻译[1407.918216894239; 1391.373407846455; 556.7108606094299]
答案 0 :(得分:6)
如何让您的代码正常工作?
我可以使用您提供的代码重现所描述的行为,但是,以下两个选项中的任何一个都可以解决问题:
将const Point3f tvec(10, 20, 30);
替换为const Point3f tvec(10, 20, N);
,其中N
低于低于0(例如-300)或更大 > 100(例如300)。
通过致电solvePnP
将来电替换为solvePnPRansac
。
为什么每项更改都会解决不良行为?
首先,考虑原始代码从solvePnP
函数请求的内容。你正在使用相当小的旋转,因此为了简化解释,我将假设旋转是同一性的。然后,将摄像机定位在世界坐标X = 10,Y = 20和Z = 30处,并随机生成物点,其中世界坐标(X,Y,Z)均匀地绘制在[0,100] 3 中。 因此,相机处于对象点可能范围的中间,如下图所示:
这意味着可以非常靠近焦平面(即,穿过光学中心并垂直于光轴的平面)生成物点。此类对象点的摄像机图像中的投影未定义。 然而,实际上undistortPoints
的非线性优化算法即使对于靠近焦平面的物点也不稳定。这种不稳定导致undistortPoints
的迭代算法发散,除非系数全为零,因为在这种情况下初始值在估计期间保持严格不变。
因此,避免这种行为的两种可能的解决方案如下:
避免在相机焦平面附近生成物点,即更改平移矢量或物点坐标的范围。
在使用solvePnPRansac
进行PnP估算之前,消除太靠近相机焦平面的物点,其未失真估计发散(异常值)。
有关undistortPoints
失败原因的详细信息:
注意:正如我们所知道的3D世界点,我使用以下调用来获得真正的未失真坐标,与undistortPoints
的结果无关:
cv::projectPoints(obj_pts, rvec, tvec, cv::Mat_<double>::eye(3,3), cv::Mat_<double>::zeros(5,1), true_norm_pts);
以下函数是undistortPoints
正在做的简化版本:
void simple_undistort_point(const cv::Mat &img_pt,
const cv::Mat_<double> &K,
const cv::Mat_<double> &D,
cv::Mat &norm_pt)
{
// Define temporary variables
double k[8]={D.at<double>(0),
D.at<double>(1),
D.at<double>(2),
D.at<double>(3),
D.at<double>(4)},
fx, fy, ifx, ify, cx, cy;
fx = K.at<double>(0,0);
fy = K.at<double>(1,1);
ifx = 1./fx;
ify = 1./fy;
cx = K.at<double>(0,2);
cy = K.at<double>(1,2);
// Cancel distortion iteratively
const int iters = 5;
double x, y, x0, y0;
x0=x=(img_pt.at<double>(0)-cx)*ifx;
y0=y=(img_pt.at<double>(1)-cy)*ify;
for(int j = 0; j < iters; ++j)
{
double r2 = x*x + y*y;
double icdist = 1/(1 + ((k[4]*r2 + k[1])*r2 + k[0])*r2);
double deltaX = 2*k[2]*x*y + k[3]*(r2 + 2*x*x);
double deltaY = k[2]*(r2 + 2*y*y) + 2*k[3]*x*y;
x = (x0 - deltaX)*icdist;
y = (y0 - deltaY)*icdist;
}
// Store result
norm_pt.create(1,2,CV_64F);
norm_pt.at<double>(0) = x;
norm_pt.at<double>(1) = y;
}
如果您添加代码来检查x
和y
每次迭代的变化情况,您会发现迭代优化因r2
在开始时非常大而分歧。这是一个日志示例:
#0: [2.6383300, 1.7651500] r2=10.0766000, icdist=0.0299408, deltaX=0, deltaY=0
#1: [0.0789937, 0.0528501] r2=0.00903313, icdist=0.9892610, deltaX=0, deltaY=0
#2: [2.6100000, 1.7462000] r2=9.86128000, icdist=0.0309765, deltaX=0, deltaY=0
#3: [0.0817263, 0.0546783] r2=0.00966890, icdist=0.9885120, deltaX=0, deltaY=0
#4: [2.6080200, 1.7448800] r2=9.84637000, icdist=0.0310503, deltaX=0, deltaY=0
end: [0.0819209, 0.0548085]
true: [0.9327440, 0.6240440]
当r2
很大时,r2*r2*r2
很大,因此icdist
非常小,因此下一次迭代以非常小的r2
开始。当r2
非常小时,icdist
接近1,因此x
和y
分别设置为x0
和y0
,我们是返回大r2
等
那么为什么r2
首先如此之大?因为这些点可能靠近焦平面产生,在这种情况下它们远离光轴(因此非常大r2
)。请参阅以下日志示例:
img_pt#0=[991.4992804037340, 629.5460091483255], r2=10.07660, norm(cv_undist-true)=1.0236800
img_pt#1=[5802.666489402056, 4402.387472311543], r2=554.4490, norm(cv_undist-true)=2.1568300
img_pt#2=[5040.551339386630, 5943.173381042060], r2=639.7070, norm(cv_undist-true)=2.1998700
img_pt#3=[741.9742544382640, 572.9513930063181], r2=5.749100, norm(cv_undist-true)=0.8158670
img_pt#4=[406.9101658356062, 403.0152736214052], r2=1.495890, norm(cv_undist-true)=0.1792810
img_pt#5=[516.2079583447821, 1038.026553216831], r2=10.88760, norm(cv_undist-true)=1.0494500
img_pt#6=[1876.220394606081, 8129.280202695572], r2=747.5450, norm(cv_undist-true)=2.2472900
img_pt#7=[236.9935231831764, 329.3418854620716], r2=0.599625, norm(cv_undist-true)=0.0147487
img_pt#8=[1037.586015858139, 1346.494838992490], r2=25.05890, norm(cv_undist-true)=1.2998400
img_pt#9=[499.9808133105154, 715.6213031242644], r2=5.210870, norm(cv_undist-true)=0.7747020
您可以看到,对于大多数积分,r2
非常大,除了少数(#3,#4&amp;#7),这些积分也与最佳失真准确度相关。
这个问题是由于在OpenCV中实现的特定的无失真算法,其已被选择用于其效率。其他非线性优化算法(例如Levenberg-Marquardt)会更准确但速度也更慢,并且在大多数应用中肯定会出现过度杀伤。
答案 1 :(得分:3)
让我通过opencv来源。但首先我提出的“纯”opencv函数在源代码中有效(请在下面阅读我如何得到这一点)与您的代码合并以显示它作为库1:
#include <iostream>
#include <opencv2\opencv.hpp>
using namespace std;
using namespace cv;
#define DEG2RAD (3.1415293/180.0)
#define RAD2DEG (1.0/DEG2RAD)
Point2f Project(Point3f p, double R[], double t[], double k[], double fx, double fy, double cx, double cy) {
double X = p.x, Y = p.y, Z = p.z;
double x = R[0]*X + R[1]*Y + R[2]*Z + t[0];
double y = R[3]*X + R[4]*Y + R[5]*Z + t[1];
double z = R[6]*X + R[7]*Y + R[8]*Z + t[2];
double r2, r4, r6, a1, a2, a3, cdist, icdist2;
double xd, yd;
z = z ? 1./z : 1;
x *= z; y *= z;
r2 = x*x + y*y;
r4 = r2*r2;
r6 = r4*r2;
a1 = 2*x*y;
a2 = r2 + 2*x*x;
a3 = r2 + 2*y*y;
cdist = 1 + k[0]*r2 + k[1]*r4 + k[4]*r6;
icdist2 = 1./(1 + k[5]*r2 + k[6]*r4 + k[7]*r6);
xd = x*cdist*icdist2 + k[2]*a1 + k[3]*a2;
yd = y*cdist*icdist2 + k[2]*a3 + k[3]*a1;
double xRet = xd*fx + cx;
double yRet = yd*fy + cy;
return Point2f(xRet, yRet);
}
int main() {
const int npoints = 10; // number of points
// extrinsic
const Point3f tvec(10, 20, 30);
Point3f rvec(3, 5, 7);
cout << "Finding extrinsic parameters (PnP)" << endl;
cout<<"Test transformations: ";
cout<<"Rotation: "<<rvec<<"; translation: "<<tvec<<endl;
rvec*=DEG2RAD;
// intrinsic
Mat_ <double>cameraMatrix(3, 3);
cameraMatrix << 300., 0., 200., 0, 300., 100., 0., 0., 1.;
Mat_ <double>distCoeffs(1, 5); // (k_1, k_2, p_1, p_2[, k_3[, k_4, k_5, k_6]]) of 4, 5, or 8 elements.
distCoeffs << 1.2, 0.2, 0., 0., 0.; // non-zero distortion
//distCoeffs << 0.0, 0.0, 0.0, 0.0, 0.0; // zero distortion
//distCoeffs << 1.8130418031666484e+000, -1.3285019729932657e+001, -1.6921715019797313e-002, -1.3327183367510961e-001, -5.2725832482783389e+001;
cout<<"distrotion coeff: "<<distCoeffs<<endl;
cout<<"============= Running PnP..."<<endl;
vector<Point3f> objPts(npoints);
vector<Point2f> imagePoints(npoints);
Mat rvec_est, tvec_est;
randu(Mat(objPts), 0.0f, 100.0f);
// project
projectPoints(Mat(objPts), Mat(rvec), Mat(tvec), cameraMatrix, distCoeffs, Mat(imagePoints));
std::cout << objPts << std::endl;
std::cout << imagePoints << std::endl;
double R[9];
Mat matR( 3, 3, CV_64F, R);
Mat_<double> m(1,3);
m << (double)rvec.x, (double)rvec.y, (double)rvec.z;
Rodrigues(m, matR);
std::cout << matR << std::endl;
double t[3] = {tvec.x, tvec.y, tvec.z};
double k[8] = {1.2, 0.2, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
double fx = 300, fy = 300, cx = 200, cy = 100;
for(int i=0;i<objPts.size();i++)
std::cout << Project(objPts[i], R, t, k, fx, fy, cx, cy) << "; ";
std::cout << std::endl;
// extrinsic
solvePnP(objPts, imagePoints, cameraMatrix, distCoeffs, rvec_est, tvec_est);
cout<<"Rotation: "<<rvec_est*RAD2DEG<<endl;
cout<<"Translation "<<tvec_est<<endl;
return 0;
}
R是旋转,t平移,k失真。看看'r2'计算 - 它是x * x + y * y,但x,y是刚应用平移和旋转后的位置(按z缩放)。这个r代表(如wikpedia所说)“图像中的方形距离投影通过理想的针孔模型”。我们可以说projectPoints实现没问题。
我是如何得到这个结果的:
我正在挖掘版本2.4.8。如果您转到calib3d模块中的calibration.cpp,请从
开始void cv::projectPoints( InputArray _opoints,
InputArray _rvec,
InputArray _tvec,
InputArray _cameraMatrix,
InputArray _distCoeffs,
OutputArray _ipoints,
OutputArray _jacobian,
double aspectRatio )
{
Mat opoints = _opoints.getMat();
int npoints = opoints.checkVector(3), depth = opoints.depth();
CV_Assert(npoints >= 0 && (depth == CV_32F || depth == CV_64F));
CvMat dpdrot, dpdt, dpdf, dpdc, dpddist;
CvMat *pdpdrot=0, *pdpdt=0, *pdpdf=0, *pdpdc=0, *pdpddist=0;
_ipoints.create(npoints, 1, CV_MAKETYPE(depth, 2), -1, true);
CvMat c_imagePoints = _ipoints.getMat();
CvMat c_objectPoints = opoints;
Mat cameraMatrix = _cameraMatrix.getMat();
Mat rvec = _rvec.getMat(), tvec = _tvec.getMat();
CvMat c_cameraMatrix = cameraMatrix;
CvMat c_rvec = rvec, c_tvec = tvec;
double dc0buf[5]={0};
Mat dc0(5,1,CV_64F,dc0buf);
Mat distCoeffs = _distCoeffs.getMat();
if( distCoeffs.empty() )
distCoeffs = dc0;
CvMat c_distCoeffs = distCoeffs;
int ndistCoeffs = distCoeffs.rows + distCoeffs.cols - 1;
if( _jacobian.needed() )
{
// cut out, we dont use this part
}
cvProjectPoints2( &c_objectPoints, &c_rvec, &c_tvec, &c_cameraMatrix, &c_distCoeffs,
&c_imagePoints, pdpdrot, pdpdt, pdpdf, pdpdc, pdpddist, aspectRatio );
}
没什么特别的,对吗?根本没有内容操纵。让我们深入一点:
CV_IMPL void cvProjectPoints2( const CvMat* objectPoints,
const CvMat* r_vec,
const CvMat* t_vec,
const CvMat* A,
const CvMat* distCoeffs,
CvMat* imagePoints, CvMat* dpdr,
CvMat* dpdt, CvMat* dpdf,
CvMat* dpdc, CvMat* dpdk,
double aspectRatio )
{
Ptr<CvMat> matM, _m;
Ptr<CvMat> _dpdr, _dpdt, _dpdc, _dpdf, _dpdk;
int i, j, count;
int calc_derivatives;
const CvPoint3D64f* M;
CvPoint2D64f* m;
double r[3], R[9], dRdr[27], t[3], a[9], k[8] = {0,0,0,0,0,0,0,0}, fx, fy, cx, cy;
CvMat _r, _t, _a = cvMat( 3, 3, CV_64F, a ), _k;
CvMat matR = cvMat( 3, 3, CV_64F, R ), _dRdr = cvMat( 3, 9, CV_64F, dRdr );
// some code not important ...
if( r_vec->rows == 3 && r_vec->cols == 3 )
{
_r = cvMat( 3, 1, CV_64FC1, r );
cvRodrigues2( r_vec, &_r );
cvRodrigues2( &_r, &matR, &_dRdr );
cvCopy( r_vec, &matR );
}
else
{
_r = cvMat( r_vec->rows, r_vec->cols, CV_MAKETYPE(CV_64F,CV_MAT_CN(r_vec->type)), r );
cvConvert( r_vec, &_r );
cvRodrigues2( &_r, &matR, &_dRdr );
}
最后一部分很重要,因为我们使用cv :: Rodriguez从旋转矢量创建一个旋转矩阵。后来在函数中我们也创建了转换矩阵,但仍然没有数据操作。在ProjectPoints2中进一步:
fx = a[0]; fy = a[4];
cx = a[2]; cy = a[5];
if( fixedAspectRatio )
fx = fy*aspectRatio;
if( distCoeffs )
{
if( !CV_IS_MAT(distCoeffs) ||
(CV_MAT_DEPTH(distCoeffs->type) != CV_64F &&
CV_MAT_DEPTH(distCoeffs->type) != CV_32F) ||
(distCoeffs->rows != 1 && distCoeffs->cols != 1) ||
(distCoeffs->rows*distCoeffs->cols*CV_MAT_CN(distCoeffs->type) != 4 &&
distCoeffs->rows*distCoeffs->cols*CV_MAT_CN(distCoeffs->type) != 5 &&
distCoeffs->rows*distCoeffs->cols*CV_MAT_CN(distCoeffs->type) != 8) )
CV_Error( CV_StsBadArg, cvDistCoeffErr );
_k = cvMat( distCoeffs->rows, distCoeffs->cols,
CV_MAKETYPE(CV_64F,CV_MAT_CN(distCoeffs->type)), k );
cvConvert( distCoeffs, &_k );
}
这里我们设置来自相机矩阵和主点坐标的焦距。我们还设置了包含失真系数的数组k。现在我们完成了变量设置。我们去计算:
double X = M[i].x, Y = M[i].y, Z = M[i].z;
double x = R[0]*X + R[1]*Y + R[2]*Z + t[0];
double y = R[3]*X + R[4]*Y + R[5]*Z + t[1];
double z = R[6]*X + R[7]*Y + R[8]*Z + t[2];
double r2, r4, r6, a1, a2, a3, cdist, icdist2;
double xd, yd;
z = z ? 1./z : 1;
x *= z; y *= z;
r2 = x*x + y*y;
r4 = r2*r2;
r6 = r4*r2;
a1 = 2*x*y;
a2 = r2 + 2*x*x;
a3 = r2 + 2*y*y;
cdist = 1 + k[0]*r2 + k[1]*r4 + k[4]*r6;
icdist2 = 1./(1 + k[5]*r2 + k[6]*r4 + k[7]*r6);
xd = x*cdist*icdist2 + k[2]*a1 + k[3]*a2;
yd = y*cdist*icdist2 + k[2]*a3 + k[3]*a1;
m[i].x = xd*fx + cx; // here projection
m[i].y = yd*fy + cy;
我们的功能与我在顶部/
上提供的功能完全相同