论文参见https://icwww.epfl.ch/~lepetit/papers/lepetit_ijcv08.pdf
EPNP要理解透彻还是好难啊…… 现在还是只理解了一个大概。
中心思想: 三维空间中一个点可以由其他不共面的四个点的加权和表示
epnp最终求解:一个旋转矩阵,一个平移矩阵(可转成四元数,四元数可再转成欧拉角)
epnp算法原理
编辑公式太耗时间了,手写。
opencv源码流程
opencv calib3d 模块中有好几种pnp的求解
使用eigen3 重写,主要是替换数据结构和矩阵相关的接口
基于https://github.com/slowplayer/EPNP-Eigen
不过感觉原作者没有写完?
我改的版本: 使用了eigen3.2.10
#ifndef PNP_SOLVER_H
#define PNP_SOLVER_H
#include <Eigen/Core>
#include <Eigen/Dense>
#include <Eigen/Geometry>
#include <Eigen/SVD>
#include <Eigen/QR>
#include <vector>
class PnpSolver
{
public:
PnpSolver();
~PnpSolver(){};
//设置相机内参
void set_internal_parameters(double cx,double cy,double fx,double fy);
void set_maximum_number_of_correspondences(int n);
void reset_number_of_correspondences();
//3d点和2d点
void add_correspondence(Eigen::Vector3d point3d,Eigen::Vector2d point2d);
double compute_pose(Eigen::Matrix3d &R,Eigen::Vector3d &T);
//此处的投影是为了可视化
void projectPoints(std::vector<Eigen::Vector3d> &src,std::vector<Eigen::Vector2d> &dst,Eigen::Matrix3d &R,Eigen::Vector3d &T);
double cx,cy,fx,fy;
private:
//选择控制点,第一个点是质心,其他三个点跟主成分方向有关
void choose_control_points();
//计算alpha,灰度质心齐次坐标
void compute_barycentric_coordinates();
inline double dot(const double *v1,const double *v2);
//求beta时的L矩阵
void compute_L_6x10(const Eigen::Matrix<double,12,12> &Ut,Eigen::Matrix<double,6,10> &L_6x10);
//L * beta = rho
void compute_rho(Eigen::Matrix<double,6,1> &Rho);
//N 取不同值的时候,求beta
void find_betas_0(const Eigen::Matrix<double,6,10> &L_6x10,const Eigen::Matrix<double,6,1> &Rho,
Eigen::Vector4d &Betas);
void find_betas_1(const Eigen::Matrix<double,6,10> &L_6x10,const Eigen::Matrix<double,6,1> &Rho,
Eigen::Vector4d &Betas);
void find_betas_2(const Eigen::Matrix<double,6,10> &L_6x10,const Eigen::Matrix<double,6,1> &Rho,
Eigen::Vector4d &Betas);
//计算出beta值和对应的特征向量之后,计算相机坐标系中的控制点
void compute_ccs(const Eigen::Vector4d &Betas,const Eigen::Matrix<double,12,12> &Ut);
//通过相机坐标系中的控制点坐标,计算参考点在相机坐标系下的坐标
void compute_pcs();
检测参考点在相机坐标系下的z轴坐标是否大于零,大于零不做修改。如果小于零,需要按照相机光心(原点)中心对称一下。???
void solve_for_sign();
//horn的绝对定向算法??
void estimate_R_and_t(Eigen::Matrix3d &R,Eigen::Vector3d &T);
double reprojection_error(const Eigen::Matrix3d &R,const Eigen::Vector3d &T);
double compute_R_and_t(const Eigen::Matrix<double,12,12> &Ut,const Eigen::Vector4d &Betas,
Eigen::Matrix3d &R,Eigen::Vector3d &T);
//高斯牛顿法
void gauss_newton(const Eigen::Matrix<double,6,10> &L_6x10,const Eigen::Matrix<double,6,1> &Rho,Eigen::Vector4d &betas);
//高斯牛顿法中的梯度
void compute_A_and_b_gauss_newton(const Eigen::Matrix<double,6,10> &L_6x10,const Eigen::Matrix<double,6,1> &Rho,
Eigen::Vector4d &betas,Eigen::Matrix<double,6,4> &A,Eigen::Matrix<double,6,1> &b);
void qr_solve(Eigen::Matrix<double,6,4> &A, Eigen::Matrix<double,6,1> &B,Eigen::Vector4d &X);
int maximum_number_of_correspondences;
int number_of_correspondences;
//alphas就是论文里的那些alpha,pcs和pws分别时参考点在相机坐标系和世界坐标系中的点,us是,世界坐标系中的点在图像坐标系中的投影
Eigen::MatrixXd pws,us,alphas,pcs;
//cws是参考坐标系中4个3d参考点,ccs是相机坐标系中4个3d参考点
Eigen::Matrix<double,4,3> cws,ccs;
double cws_determinant;
};
#endif
#include "pnp_solver.h"
#include <iostream>
PnpSolver::PnpSolver()
:maximum_number_of_correspondences(),
number_of_correspondences()
{
}
void PnpSolver::set_internal_parameters(double cx, double cy, double fx, double fy)
{
this->cx=cx;
this->cy=cy;
this->fx=fx;
this->fy=fy;
}
void PnpSolver::set_maximum_number_of_correspondences(int n)
{
maximum_number_of_correspondences=n;
pws.resize(maximum_number_of_correspondences,);
us.resize(maximum_number_of_correspondences,);
alphas.resize(maximum_number_of_correspondences,);
pcs.resize(maximum_number_of_correspondences,);
}
void PnpSolver::reset_number_of_correspondences()
{
number_of_correspondences=;
}
void PnpSolver::add_correspondence(Eigen::Vector3d point3d, Eigen::Vector2d point2d)
{
pws.row(number_of_correspondences)=point3d.transpose();
us.row(number_of_correspondences)=point2d.transpose();
number_of_correspondences++;
}
void PnpSolver::choose_control_points()
{
cws.setZero(,);
for(int i=;i<number_of_correspondences;i++)
cws.row()+=pws.row(i);
cws.row()/=number_of_correspondences;
Eigen::MatrixXd PW0(number_of_correspondences,);
Eigen::Matrix3d PW0tPW0;
Eigen::Vector3d DC;
Eigen::Matrix3d UCt;
for(int i=;i<number_of_correspondences;i++)
PW0.row(i)=pws.row(i)-cws.row();
PW0tPW0=PW0.transpose()*PW0;
//TODO:maybe wron
//opencv cvSVD(&PW0tPW0, &DC, &UCt, 0, CV_SVD_MODIFY_A | CV_SVD_U_T);
//lmq
Eigen::JacobiSVD<Eigen::Matrix3d> svd(PW0tPW0,Eigen::ComputeFullU);
DC=svd.singularValues();
UCt=svd.matrixU().transpose();
for(int i=;i<;i++)
{
double k=sqrt(DC(i-)/number_of_correspondences);
cws.row(i)=cws.row()+k*UCt.row(i-);
}
}
void PnpSolver::compute_barycentric_coordinates()
{
Eigen::Matrix3d CC,CC_inv;
for(int i=;i<;i++)
for(int j=;j<;j++)
CC(i,j-)=cws(j,i)-cws(,i);
CC_inv=CC.inverse();
for(int i=;i<number_of_correspondences;i++)
{
for(int j=;j<;j++)
{
alphas(i,+j)=CC_inv(j,)*(pws(i,)-cws(,))+
CC_inv(j,)*(pws(i,)-cws(,))+
CC_inv(j,)*(pws(i,)-cws(,));
}
alphas(i,)=-alphas(i,)-alphas(i,)-alphas(i,);
}
}
inline double PnpSolver::dot(const double *v1,const double *v2)
{
return v1[]*v2[]+v1[]*v2[]+v1[]*v2[];
}
void PnpSolver::compute_L_6x10(const Eigen::Matrix<double,,> &Ut,
Eigen::Matrix<double,,> &L_6x10)
{
int a,b;
double dv[][][];
for(int i=;i<;i++)
{
a=,b=;
for(int j=;j<;j++)
{
for(int k=;k<;k++)
dv[i][j][k]=Ut(-i,*a+k)-Ut(-i,*b+k);
if(++b>)
{
a++;
b=a+;
}
}
}
for(int i=;i<;i++)
{
L_6x10(i,)= dot(dv[][i],dv[][i]);
L_6x10(i,)=*dot(dv[][i],dv[][i]);
L_6x10(i,)= dot(dv[][i],dv[][i]);
L_6x10(i,)=*dot(dv[][i],dv[][i]);
L_6x10(i,)=*dot(dv[][i],dv[][i]);
L_6x10(i,)= dot(dv[][i],dv[][i]);
L_6x10(i,)=*dot(dv[][i],dv[][i]);
L_6x10(i,)=*dot(dv[][i],dv[][i]);
L_6x10(i,)=*dot(dv[][i],dv[][i]);
L_6x10(i,)= dot(dv[][i],dv[][i]);
}
}
void PnpSolver::compute_rho(Eigen::Matrix<double,,> &Rho)
{
Rho[]=(cws.row()-cws.row()).squaredNorm();
Rho[]=(cws.row()-cws.row()).squaredNorm();
Rho[]=(cws.row()-cws.row()).squaredNorm();
Rho[]=(cws.row()-cws.row()).squaredNorm();
Rho[]=(cws.row()-cws.row()).squaredNorm();
Rho[]=(cws.row()-cws.row()).squaredNorm();
}
void PnpSolver::find_betas_0(const Eigen::Matrix<double,,> &L_6x10,const Eigen::Matrix<double,,> &Rho,
Eigen::Vector4d &Betas)
{
Eigen::Matrix<double,,> L_6x4;
Eigen::Vector4d B4;
L_6x4.col()=L_6x10.col();
L_6x4.col()=L_6x10.col();
L_6x4.col()=L_6x10.col();
L_6x4.col()=L_6x10.col();
B4=L_6x4.colPivHouseholderQr().solve(Rho);
if(B4[]<)
{
Betas[]=sqrt(-B4[]);
Betas[]=-B4[]/Betas[];
Betas[]=-B4[]/Betas[];
Betas[]=-B4[]/Betas[];
}
else
{
Betas[]=sqrt(B4[]);
Betas[]=B4[]/Betas[];
Betas[]=B4[]/Betas[];
Betas[]=B4[]/Betas[];
}
}
//to modify
void PnpSolver::find_betas_1(const Eigen::Matrix<double,,> &L_6x10,const Eigen::Matrix<double,,> &Rho,
Eigen::Vector4d &Betas)
{
Eigen::Matrix<double,,> L_6x3;
Eigen::Vector3d B3;
L_6x3.col()=L_6x10.col();
L_6x3.col()=L_6x10.col();
L_6x3.col()=L_6x10.col();
B3=L_6x3.colPivHouseholderQr().solve(Rho);//cv_svd 用fullPivHouseholderQr()呢?或者是使用eigen/svd里面的东西
if(B3[]<)
{
Betas[]=sqrt(-B3[]);
Betas[]=(B3[]<)?sqrt(-B3[]):;
}
else
{
Betas[]=sqrt(B3[]);
Betas[]=(B3[]>)?sqrt(B3[]):;
}
if(B3[]<)
Betas[]=-Betas[];
Betas[]=;
Betas[]=;
}
void PnpSolver::find_betas_2(const Eigen::Matrix<double,,> &L_6x10,const Eigen::Matrix<double,,> &Rho,
Eigen::Vector4d &Betas)
{
Eigen::Matrix<double,,> L_6x5;
Eigen::Matrix<double,,> B5;
L_6x5.col()=L_6x10.col();
L_6x5.col()=L_6x10.col();
L_6x5.col()=L_6x10.col();
L_6x5.col()=L_6x10.col();
L_6x5.col()=L_6x10.col();
B5=L_6x5.colPivHouseholderQr().solve(Rho);
if(B5[]<)
{
Betas[]=sqrt(-B5[]);
Betas[]=(B5[]<)?sqrt(-B5[]):;
}
else
{
Betas[]=sqrt(B5[]);
Betas[]=(B5[]>)?sqrt(B5[]):;
}
if(B5[]<)
B5[]=-B5[];
Betas[]=B5[]/Betas[];
Betas[]=;
}
void PnpSolver::compute_ccs(const Eigen::Vector4d &Betas,const Eigen::Matrix<double,,> &Ut)
{
ccs.setZero(,);
for(int i=;i<;i++)
{
for(int j=;j<;j++)
for(int k=;k<;k++)
ccs(j,k)+=Betas[i]*Ut(-i,*j+k);
}
}
void PnpSolver::compute_pcs()
{
pcs=alphas*ccs;
}
void PnpSolver::solve_for_sign()
{
if(pcs(,)<)
{
ccs=-ccs;
pcs=-pcs;
}
}
void PnpSolver::estimate_R_and_t(Eigen::Matrix3d &R,Eigen::Vector3d &T)
{
Eigen::Vector3d pc0,pw0;
pc0.setZero();
pw0.setZero();
for(int i=;i<number_of_correspondences;i++)
{
pc0+=pcs.row(i);
pw0+=pws.row(i);
}
pc0/=number_of_correspondences;
pw0/=number_of_correspondences;
Eigen::Matrix3d W=Eigen::Matrix3d::Zero();
for(int i=;i<number_of_correspondences;i++)
{
W+= (pcs.row(i).transpose()-pc0)*((pws.row(i).transpose()-pw0).transpose());
}
//SVD on W
Eigen::JacobiSVD<Eigen::Matrix3d> svd(W,Eigen::ComputeFullU|Eigen::ComputeFullV);
Eigen::Matrix3d U=svd.matrixU();
Eigen::Matrix3d V=svd.matrixV();
R=U*(V.transpose());
T=pc0-R*pw0;
}
double PnpSolver::reprojection_error(const Eigen::Matrix3d &R,const Eigen::Vector3d &T)
{
double sum2=;
Eigen::Vector3d point3d;
Eigen::Vector2d point2d;
for(int i=;i<number_of_correspondences;i++)
{
point3d=R*pws.row(i).transpose()+T;
point2d[]=cx+fx*point3d[]/point3d[];
point2d[]=cy+fy*point3d[]/point3d[];
sum2+=(us.row(i).transpose()-point2d).squaredNorm();
}
return sum2/number_of_correspondences;
}
double PnpSolver::compute_R_and_t(const Eigen::Matrix<double,,> &Ut,const Eigen::Vector4d &Betas,
Eigen::Matrix3d &R,Eigen::Vector3d &T)
{
compute_ccs(Betas,Ut);
compute_pcs();
solve_for_sign();
estimate_R_and_t(R,T);
return reprojection_error(R,T);
}
double PnpSolver::compute_pose(Eigen::Matrix3d &R,Eigen::Vector3d &T)
{
choose_control_points();
compute_barycentric_coordinates();
Eigen::MatrixXd M(*number_of_correspondences,);
//fill M function
for(int i=;i<number_of_correspondences;i++)
{
for(int j=;j<;j++)
{
M(*i,*j)=alphas(i,j)*fx;
M(*i,*j+)=;
M(*i,*j+)=alphas(i,j)*(cx-us(i,));
M(*i+,*j)=;
M(*i+,*j+)=alphas(i,j)*fy;
M(*i+,*j+)=alphas(i,j)*(cy-us(i,));
}
}
Eigen::Matrix<double,,> MtM;
Eigen::Matrix<double,,> D;
Eigen::Matrix<double,,> Ut;
MtM=M.transpose()*M;
//opencv cvSVD(&MtM, &D, &Ut, 0, CV_SVD_MODIFY_A | CV_SVD_U_T);
//lmq modified
Eigen::JacobiSVD<Eigen::Matrix<double,,> > svd(MtM,Eigen::ComputeFullU);
D=svd.singularValues().transpose();//todo what?? seem no used
Ut=svd.matrixU().transpose();
Eigen::Matrix<double,,> L_6x10;
Eigen::Matrix<double,,> Rho;
//TODO
compute_L_6x10(Ut,L_6x10);
compute_rho(Rho);
Eigen::Vector4d Betas0,Betas1,Betas2;
Eigen::Matrix3d R0,R1,R2;
Eigen::Vector3d T0,T1,T2;
double errors[];
find_betas_0(L_6x10,Rho,Betas0);
gauss_newton(L_6x10,Rho,Betas0);
errors[]=compute_R_and_t(Ut,Betas0,R0,T0);
find_betas_1(L_6x10,Rho,Betas1);
gauss_newton(L_6x10,Rho,Betas1);
errors[]=compute_R_and_t(Ut,Betas1,R1,T1);
find_betas_2(L_6x10,Rho,Betas2);
gauss_newton(L_6x10,Rho,Betas2);
errors[]=compute_R_and_t(Ut,Betas2,R2,T2);
int N=;
R=R0;T=T0;
if(errors[]<errors[])
{
N=;R=R1;T=T1;
}
if(errors[]<errors[N])
{
N=;R=R2;T=T2;
}
return errors[N];
return ;
}
//attention
void PnpSolver::projectPoints(std::vector <Eigen::Vector3d> &src, std::vector <Eigen::Vector2d> &dst,Eigen::Matrix3d &R,Eigen::Vector3d &T)
{
int size = src.size();
for(int i = ;i < size; ++ i)
{
Eigen::Vector3d point3d = R * src[i]+T;
dst[i][]=cx+fx*point3d[]/point3d[];
dst[i][]=cy+fy*point3d[]/point3d[];
}
}
void PnpSolver::gauss_newton(const Eigen::Matrix<double, , > &L_6x10, const Eigen::Matrix<double, , > &Rho,
Eigen::Vector4d &betas)
{
const int iterations_number = ;//提高迭代次数好像没什么用,具体原因是?
Eigen::Matrix<double,,> A;
Eigen::Matrix<double,,> B;
Eigen::Vector4d X;
for(int k = ; k < iterations_number; k++)
{
compute_A_and_b_gauss_newton(L_6x10, Rho, betas, A, B);
qr_solve(A, B, X);
for(int i = ; i < ; i++)
betas[i] += X[i];
}
}
void PnpSolver::compute_A_and_b_gauss_newton(const Eigen::Matrix<double, , > &L_6x10,
const Eigen::Matrix<double, , > &Rho, Eigen::Vector4d &betas,
Eigen::Matrix<double, , > &A, Eigen::Matrix<double, , > &b)
{
for(int i = ;i < ; ++i) {
Eigen::RowVectorXd rowL = L_6x10.row(i);
Eigen::RowVector4d rowA = A.row(i);
rowA[] = * rowL[] * betas[] + rowL[] * betas[] + rowL[] * betas[] + rowL[] * betas[];
rowA[] = rowL[] * betas[] + * rowL[] * betas[] + rowL[] * betas[] + rowL[] * betas[];
rowA[] = rowL[] * betas[] + rowL[] * betas[] + * rowL[] * betas[] + rowL[] * betas[];
rowA[] = rowL[] * betas[] + rowL[] * betas[] + rowL[] * betas[] + * rowL[] * betas[];
b[i] = Rho[i] -
(
rowL[] * betas[] * betas[] +
rowL[] * betas[] * betas[] +
rowL[] * betas[] * betas[] +
rowL[] * betas[] * betas[] +
rowL[] * betas[] * betas[] +
rowL[] * betas[] * betas[] +
rowL[] * betas[] * betas[] +
rowL[] * betas[] * betas[] +
rowL[] * betas[] * betas[] +
rowL[] * betas[] * betas[]
);
A.row(i) = rowA;
}
}
void PnpSolver::qr_solve(Eigen::Matrix<double, , > &A, Eigen::Matrix<double, , > &B, Eigen::Vector4d &X)
{
X = A.colPivHouseholderQr().solve(B);
}