Introduction
The homography can be decomposed to retrieve the pose. We consider here that all the points lie in the plane .
Source code
The following source code that uses ViSP is also available in pose-from-homography-dlt-visp.cpp file. It allows to compute the pose of the camera from at least 4 coplanar points.
#include <visp/vpColVector.h>
#include <visp/vpHomogeneousMatrix.h>
#include <visp/vpMatrix.h>
vpMatrix homography_dlt(const std::vector< vpColVector > &x1, const std::vector< vpColVector > &x2)
{
int npoints = (int)x1.size();
vpMatrix A(2*npoints, 9, 0.);
if (npoints == 4)
A.resize(2*npoints+1, 9);
for(int i = 0; i < npoints; i++) {
A[2*i][3] = -x1[i][0];
A[2*i][4] = -x1[i][1];
A[2*i][5] = -x1[i][2];
A[2*i][6] = x2[i][1] * x1[i][0];
A[2*i][7] = x2[i][1] * x1[i][1];
A[2*i][8] = x2[i][1] * x1[i][2];
A[2*i+1][0] = x1[i][0];
A[2*i+1][1] = x1[i][1];
A[2*i+1][2] = x1[i][2];
A[2*i+1][6] = -x2[i][0] * x1[i][0];
A[2*i+1][7] = -x2[i][0] * x1[i][1];
A[2*i+1][8] = -x2[i][0] * x1[i][2];
}
if (npoints == 4) {
for (unsigned int i=0; i < 9; i ++) {
A[2*npoints][i] = 0;
}
}
vpColVector D;
vpMatrix V;
A.svd(D, V);
double smallestSv = D[0];
unsigned int indexSmallestSv = 0 ;
for (unsigned int i = 1; i < D.size(); i++) {
if ((D[i] < smallestSv) ) {
smallestSv = D[i];
indexSmallestSv = i ;
}
}
#if VISP_VERSION_INT >= VP_VERSION_INT(2, 10, 0)
vpColVector h = V.getCol(indexSmallestSv);
#else
vpColVector h = V.column(indexSmallestSv + 1);
#endif
if (h[8] < 0)
h *=-1;
vpMatrix _2H1(3, 3);
for (int i = 0 ; i < 3 ; i++)
for (int j = 0 ; j < 3 ; j++)
_2H1[i][j] = h[3*i+j] ;
return _2H1;
}
vpHomogeneousMatrix pose_from_homography_dlt(const std::vector< vpColVector > &xw, const std::vector< vpColVector > &xo)
{
vpMatrix oHw = homography_dlt(xw, xo);
double norm = sqrt(vpMath::sqr(oHw[0][0]) + vpMath::sqr(oHw[1][0]) + vpMath::sqr(oHw[2][0])) ;
oHw /= norm;
vpColVector c1 = oHw.getCol(0);
vpColVector c2 = oHw.getCol(1);
vpColVector c3 = vpColVector::crossProd(c1, c2);
vpHomogeneousMatrix oTw;
for(int i=0; i < 3; i++) {
oTw[i][0] = c1[i];
oTw[i][1] = c2[i];
oTw[i][2] = c3[i];
oTw[i][3] = oHw[i][2];
}
return oTw;
}
int main()
{
int npoints = 4;
std::vector< vpColVector > wX(npoints);
std::vector< vpColVector > xw(npoints);
std::vector< vpColVector > xo(npoints);
for (int i = 0; i < npoints; i++) {
xw[i].resize(3);
xo[i].resize(3);
wX[i].resize(4);
}
vpHomogeneousMatrix oTw_truth(-0.1, 0.1, 1.2, vpMath::rad(5), vpMath::rad(0), vpMath::rad(45));
double L = 0.2;
wX[0][0] = -L; wX[0][1] = -L; wX[0][2] = 0; wX[0][3] = 1;
wX[1][0] = 2*L; wX[1][1] = -L; wX[1][2] = 0; wX[1][3] = 1;
wX[2][0] = L; wX[2][1] = L; wX[2][2] = 0; wX[2][3] = 1;
wX[3][0] = -L; wX[3][1] = L; wX[3][2] = 0; wX[3][3] = 1;
for(int i = 0; i < npoints; i++) {
vpColVector oX = oTw_truth * wX[i];
xo[i][0] = oX[0] / oX[2];
xo[i][1] = oX[1] / oX[2];
xo[i][2] = 1;
xw[i][0] = wX[i][0];
xw[i][1] = wX[i][1];
xw[i][2] = 1;
}
vpHomogeneousMatrix oTw = pose_from_homography_dlt(xw, xo);
std::cout << "oTw (ground truth):\n" << oTw_truth << std::endl;
std::cout << "oTw (computed with homography DLT):\n" << oTw << std::endl;
return 0;
}
Source code explained
First of all we include ViSP headers that are requested to manipulate vectors and matrices.
#include <visp/vpColVector.h>
#include <visp/vpHomogeneousMatrix.h>
#include <visp/vpMatrix.h>
Then we introduce the function that does the homography estimation from coplanar points. This function is detailed in Homography estimation.
vpMatrix homography_dlt(const std::vector< vpColVector > &x1, const std::vector< vpColVector > &x2)
Then we introduce the function that does the pose from homography estimation.
vpHomogeneousMatrix pose_from_homography_dlt(const std::vector< vpColVector > &xw, const std::vector< vpColVector > &xo)
Based on equation (37) we first estimate the homography .
vpMatrix oHw = homography_dlt(xw, xo);
Then using the constraint that we normalize the homography.
double norm = sqrt(vpMath::sqr(oHw[0][0]) + vpMath::sqr(oHw[1][0]) + vpMath::sqr(oHw[2][0])) ;
oHw /= norm;
Let us denote
Noting that matrix M is also equal to we are able to extract the corresponding vectors.
vpColVector c1 = oHw.getCol(0);
vpColVector c2 = oHw.getCol(1);
The third column of the rotation matrix is computed such as
vpColVector c3 = vpColVector::crossProd(c1, c2);
To finish we update the homogeneous transformation that corresponds to the estimated pose .
vpHomogeneousMatrix oTw;
for(int i=0; i < 3; i++) {
oTw[i][0] = c1[i];
oTw[i][1] = c2[i];
oTw[i][2] = c3[i];
oTw[i][3] = oHw[i][2];
}
Finally we define the main function in which we will initialize the input data before calling the previous function and computing the pose from the estimated homography.
Then we create the data structures that will contain the 3D points coordinates wX in the world frame, their normalized coordinates xw in the world frame and their normalized coordinates xo in the image plane obtained by perspective projection. Note here that at least 4 coplanar points are requested to estimate the 8 parameters of the homography.
int npoints = 4;
std::vector< vpColVector > wX(npoints);
std::vector< vpColVector > xw(npoints);
std::vector< vpColVector > xo(npoints);
for (int i = 0; i < npoints; i++) {
xw[i].resize(3);
xo[i].resize(3);
wX[i].resize(4);
}
For our simulation we then initialize the input data from a ground truth pose oTw_truth. For each point wX[i] we compute the perspective projection xo[i] = (xo, yo, 1). According to equation (37) we also set in xw vector.
vpHomogeneousMatrix oTw_truth(-0.1, 0.1, 1.2, vpMath::rad(5), vpMath::rad(0), vpMath::rad(45));
double L = 0.2;
wX[0][0] = -L; wX[0][1] = -L; wX[0][2] = 0; wX[0][3] = 1;
wX[1][0] = 2*L; wX[1][1] = -L; wX[1][2] = 0; wX[1][3] = 1;
wX[2][0] = L; wX[2][1] = L; wX[2][2] = 0; wX[2][3] = 1;
wX[3][0] = -L; wX[3][1] = L; wX[3][2] = 0; wX[3][3] = 1;
for(int i = 0; i < npoints; i++) {
vpColVector oX = oTw_truth * wX[i];
xo[i][0] = oX[0] / oX[2];
xo[i][1] = oX[1] / oX[2];
xo[i][2] = 1;
xw[i][0] = wX[i][0];
xw[i][1] = wX[i][1];
xw[i][2] = 1;
}
From here we have initialized and . We are now ready to call the function that does the pose estimation.
vpHomogeneousMatrix oTw = pose_from_homography_dlt(xw, xo);
Resulting pose estimation
If you run the previous code, it we produce the following result that shows that the estimated pose is equal to the ground truth one used to generate the input data:
oTw (ground truth):
0.7072945484 -0.706170438 0.03252282796 -0.1
0.706170438 0.7036809008 -0.078463382 0.1
0.03252282796 0.078463382 0.9963863524 1.2
0 0 0 1
oTw (computed with homography DLT):
0.7072945484 -0.706170438 0.03252282796 -0.1
0.706170438 0.7036809008 -0.078463382 0.1
0.03252282796 0.078463382 0.9963863524 1.2
0 0 0 1