有对视频ppt讲解不清楚的,可参考下面这个链接(对ICP、PL-ICP、NICP、IMLS-ICP匹配算法的解析)。
首先,第四章的作业之间编译会报错,是应为cmakelist未添加nabo库,添加即可。
报错如下:
CMakeFiles/imlsMatcher_node.dir/src/imls_icp.cpp.o:在函数‘IMLSICPMatcher::setTargetPointCloud(std::vector<Eigen::Matrix<double, 2, 1, 0, 2, 1>, std::allocator<Eigen::Matrix<double, 2, 1, 0, 2, 1> > >&)’中:
/home/zzr/桌面/HW4/imlsMatcherProject/src/imlsMatcher/src/imls_icp.cpp:118:对‘Nabo::NearestNeighbourSearch<double, Eigen::Matrix<double, -1, -1, 0, -1, -1> >::createKDTreeLinearHeap(Eigen::Matrix<double, -1, -1, 0, -1, -1> const&, int, unsigned int, Nabo::Parameters const&)’未定义的引用
CMakeFiles/imlsMatcher_node.dir/src/imls_icp.cpp.o:在函数‘IMLSICPMatcher::ImplicitMLSFunction(Eigen::Matrix<double, 2, 1, 0, 2, 1>, double&)’中:
/home/zzr/桌面/HW4/imlsMatcherProject/src/imlsMatcher/src/imls_icp.cpp:159:对‘Nabo::NearestNeighbourSearch<double, Eigen::Matrix<double, -1, -1, 0, -1, -1> >::knn(Eigen::Matrix<double, -1, 1, 0, -1, 1> const&, Eigen::Matrix<int, -1, 1, 0, -1, 1>&, Eigen::Matrix<double, -1, 1, 0, -1, 1>&, int, double, unsigned int, double) const’未定义的引用
/home/zzr/桌面/HW4/imlsMatcherProject/src/imlsMatcher/src/imls_icp.cpp:144:对‘Nabo::NearestNeighbourSearch<double, Eigen::Matrix<double, -1, -1, 0, -1, -1> >::createKDTreeLinearHeap(Eigen::Matrix<double, -1, -1, 0, -1, -1> const&, int, unsigned int, Nabo::Parameters const&)’未定义的引用
修改方式如下:
关于上面eigen库的使用,可参考官网教程(超级详细),
链接:http://eigen.tuxfamily.org/dox/group__TutorialLinearAlgebra.html
运行效果图:绿色为里程计轨迹,红色为匹配后的轨迹
具体代码文章末
第二题只需要在main函数中添加SetPIICPParams()、laserScanToLDP()、PIICPBetweenTwoFrames(),同时修改回调函数championLaserScanCallback()即可;
第一步:添加SetPIICPParams()
第二步:添加laserScanToLDP()
第三步:添加PIICPBetweenTwoFrames()
第四步:修改championLaserScanCallback()
具体代码文章末
运行截图:
3.阅读 ICP 相关论文,总结课上所学的几种 ICP 及其相关变型并简述其异同(ICPICP,PL-ICP,NICP, IMLSICP)
1)区别
2)思路相同
4. 现在你已经了解了多种 ICP 算法,你是否也能提出一种改进的 ICP 算法,或能提升 ICP 总体匹配精度或速度的技巧?请简述你的改进策略
提升ICP方法精度和速度的一些考虑
精度上:
1)使用其他传感器或方法(如里程计、IMU)提供初值(coarse to fine)。
效率上:
1)选取合适的采样点。IMLS-ICP的论文里就提到了一些去除误差点的方法。
2)使用合适的数据结构(TD-tree),提高程序的运行效率。
第一题源代码:
#include "imls_icp.h"
//pcl::visualization::CloudViewer g_cloudViewer("view-view");
IMLSICPMatcher::IMLSICPMatcher(void )
{
m_r = 0.03;
m_h = 0.10;
m_Iterations = 10;
m_PtID = 0;
m_pTargetKDTree = m_pSourceKDTree = NULL;
}
//构造函数.
IMLSICPMatcher::IMLSICPMatcher(double _r,double _h,int _iter)
{
m_r = _r;
m_h = _h;
m_Iterations = _iter;
}
//析构函数,释放内存.
IMLSICPMatcher::~IMLSICPMatcher(void )
{
if(m_pTargetKDTree != NULL)
delete m_pTargetKDTree;
if(m_pSourceKDTree != NULL)
delete m_pSourceKDTree;
}
void IMLSICPMatcher::setIterations(int _iter)
{
m_Iterations = _iter;
}
//去除非法数据
void IMLSICPMatcher::RemoveNANandINFData(std::vector<Eigen::Vector2d> &_input)
{
//去除非法数据.
for(std::vector<Eigen::Vector2d>::iterator it = _input.begin();it != _input.end();)
{
Eigen::Vector2d tmpPt = *it;
if(std::isnan(tmpPt(0)) || std::isnan(tmpPt(1)) ||
std::isinf(tmpPt(0)) || std::isinf(tmpPt(1)))
{
it = _input.erase(it);
}
else
{
it++;
}
}
}
void IMLSICPMatcher::setSourcePointCloud(pcl::PointCloud<pcl::PointXYZ> &pcl_cloud)
{
std::vector<Eigen::Vector2d> source_ptCloud;
for(int i =0; i < pcl_cloud.size();i++)
{
source_ptCloud.push_back(Eigen::Vector2d(pcl_cloud[i].x,pcl_cloud[i].y));
}
setSourcePointCloud(source_ptCloud);
}
void IMLSICPMatcher::setSourcePointCloud(std::vector<Eigen::Vector2d> &_source_pcloud)
{
m_sourcePointCloud = _source_pcloud;
//去除非法数据
RemoveNANandINFData(m_sourcePointCloud);
// std::cout <<"Source Pt Cloud:"<<m_sourcePointCloud.size()<<std::endl;
}
void IMLSICPMatcher::setSourcePointCloudNormals(std::vector<Eigen::Vector2d> &_normals)
{
m_sourcePtCloudNormals = _normals;
}
//设置目标点云.
void IMLSICPMatcher::setTargetPointCloud(pcl::PointCloud<pcl::PointXYZ> &pcl_cloud)
{
std::vector<Eigen::Vector2d> target_ptCloud;
for(int i =0; i < pcl_cloud.size();i++)
{
target_ptCloud.push_back(Eigen::Vector2d(pcl_cloud[i].x,pcl_cloud[i].y));
}
setTargetPointCloud(target_ptCloud);
}
//本函数会自动重新生成kd树.
void IMLSICPMatcher::setTargetPointCloud(std::vector<Eigen::Vector2d> &_target_pcloud)
{
m_targetPointCloud = _target_pcloud;
if(m_pTargetKDTree != NULL)
{
delete m_pTargetKDTree;
m_pTargetKDTree = NULL;
}
RemoveNANandINFData(m_targetPointCloud);
//构建kd树.
if(m_pTargetKDTree == NULL)
{
m_targetKDTreeDataBase.resize(2,m_targetPointCloud.size());
for(int i = 0; i < m_targetPointCloud.size();i++)
{
m_targetKDTreeDataBase(0,i) = m_targetPointCloud[i](0);
m_targetKDTreeDataBase(1,i) = m_targetPointCloud[i](1);
}
m_pTargetKDTree = Nabo::NNSearchD::createKDTreeLinearHeap(m_targetKDTreeDataBase);
}
//设置需要重新计算法向量
m_isGetNormals = false;
}
//IMLS函数,主要用来进行曲面投影.
//可以认为是xi在曲面上的高度.
//用target_sourcePtcloud构造一个kd树.
bool IMLSICPMatcher::ImplicitMLSFunction(Eigen::Vector2d x,
double& height)
{
double weightSum = 0.0;
double projSum = 0.0;
//创建KD树
if(m_pTargetKDTree == NULL)
{
m_targetKDTreeDataBase.resize(2,m_targetPointCloud.size());
for(int i = 0; i < m_targetPointCloud.size();i++)
{
m_targetKDTreeDataBase(0,i) = m_targetPointCloud[i](0);
m_targetKDTreeDataBase(1,i) = m_targetPointCloud[i](1);
}
m_pTargetKDTree = Nabo::NNSearchD::createKDTreeLinearHeap(m_targetKDTreeDataBase);
}
// 找到位于点x附近(m_r)的所有的点云
int searchNumber = 20;
Eigen::VectorXi nearIndices(searchNumber);
Eigen::VectorXd nearDist2(searchNumber);
//找到某一个点的最近邻.
//搜索searchNumber个最近邻
//下标储存在nearIndices中,距离储存在nearDist2中.
//最大搜索距离为m_r
m_pTargetKDTree->knn(x,nearIndices,nearDist2,searchNumber,0,
Nabo::NNSearchD::SORT_RESULTS | Nabo::NNSearchD::ALLOW_SELF_MATCH|
Nabo::NNSearchD::TOUCH_STATISTICS,
m_h);
std::vector<Eigen::Vector2d> nearPoints;
std::vector<Eigen::Vector2d> nearNormals;
for(int i = 0; i < searchNumber;i++)
{
//说明最近邻是合法的.
if(nearDist2(i) < std::numeric_limits<double>::infinity() &&
std::isinf(nearDist2(i)) == false &&
std::isnan(nearDist2(i)) == false)
{
//该最近邻在原始数据中的下标.
int index = nearIndices(i);
Eigen::Vector2d tmpPt(m_targetKDTreeDataBase(0,index),m_targetKDTreeDataBase(1,index));
//是否为inf
if(std::isinf(tmpPt(0))||std::isinf(tmpPt(1))||
std::isnan(tmpPt(0))||std::isnan(tmpPt(1)))
{
continue;
}
Eigen::Vector2d normal;
normal = m_targetPtCloudNormals[index];
//如果对应的点没有法向量,则不要.
if(std::isinf(normal(0))||std::isinf(normal(1))||
std::isnan(normal(0))||std::isnan(normal(1)))
{
continue;
}
nearPoints.push_back(tmpPt);
nearNormals.push_back(normal);
}
else
{
break;
}
}
//如果nearPoints小于3个,则认为没有匹配点.
if(nearPoints.size() < 3)
{
return false;
}
//根据函数进行投影.计算height,即ppt中的I(x)
double mh2 = m_h * m_h;
for(int i = 0; i < nearPoints.size(); ++i)
{
Eigen::Vector2d delta_p = x - nearPoints[i];
double weight = std::exp(-delta_p.squaredNorm()/mh2);//向量的平方范数
projSum += weight * delta_p.dot(nearNormals[i]); //向量的点积 或者 矩阵积
weightSum += weight;
}
height = projSum / (weightSum + 0.000001);//+ 0.000001防止分母未零
return true;
}
/**
* @brief IMLSICPMatcher::projSourcePtToSurface
* 此函数的目的是把source_cloud中的点云,投影到对应的surface上.
即为每一个当前帧点云in_cloud的激光点计算匹配点和对应的法向量
即in_cloud和out_cloud进行匹配,同时得到out_normal
注意:本函数应该删除in_cloud内部找不到匹配值的点.
* @param in_cloud 当前帧点云
* @param out_cloud 当前帧的匹配点云
* @param out_normal 匹配点云对应的法向量.
*/
void IMLSICPMatcher::projSourcePtToSurface(
std::vector<Eigen::Vector2d> &in_cloud,
std::vector<Eigen::Vector2d> &out_cloud,
std::vector<Eigen::Vector2d> &out_normal)
{
out_cloud.clear();
out_normal.clear();
for(std::vector<Eigen::Vector2d>::iterator it = in_cloud.begin(); it != in_cloud.end();)
{
Eigen::Vector2d xi = *it;
//找到在target_cloud中的最近邻
//包括该点和下标.
int K = 1;
Eigen::VectorXi indices(K);
Eigen::VectorXd dist2(K);
m_pTargetKDTree->knn(xi,indices,dist2);
Eigen::Vector2d nearXi = m_targetKDTreeDataBase.col(indices(0));
//为最近邻计算法向量.--进行投影的时候,使用统一的法向量.
Eigen::Vector2d nearNormal = m_targetPtCloudNormals[indices(0)];
//如果对应的点没有法向量,也认为没有匹配点.因此直接不考虑.
if(std::isinf(nearNormal(0))||std::isinf(nearNormal(1))||
std::isnan(nearNormal(0))||std::isnan(nearNormal(1)))
{
it = in_cloud.erase(it);
continue;
}
//如果距离太远,则说明没有匹配点.因此可以不需要进行投影,直接去除.
if(dist2(0) > m_h * m_h )
{
it = in_cloud.erase(it);
continue;
}
//进行匹配
double height;
if(ImplicitMLSFunction(xi,height) == false)
{
it = in_cloud.erase(it);
continue;
}
if(std::isnan(height))
{
std::cout <<"proj:this is not possible"<<std::endl;
it = in_cloud.erase(it);
continue;
}
if(std::isinf(height))
{
std::cout <<"proj:this is inf,not possible"<<std::endl;
it = in_cloud.erase(it);
continue;
}
Eigen::Vector2d yi;
//计算yi.
yi = xi - height * nearNormal;
out_cloud.push_back(yi);
out_normal.push_back(nearNormal);
it++;
}
}
//已知对应点对和法向量的时候,求解相对位姿.
//source_cloud在ref_cloud下的位姿.
//具体的算法,可以参考point-to-line ICP论文.
bool IMLSICPMatcher::SolveMotionEstimationProblem(std::vector<Eigen::Vector2d> &source_cloud,
std::vector<Eigen::Vector2d> &ref_cloud,
std::vector<Eigen::Vector2d> &ref_normals,
Eigen::Matrix3d& deltaTrans)
{
Eigen::Matrix4d M;
M.setZero();
Eigen::Matrix<double,1,4> gt; //gt是个行向量.
gt.setZero();
for(int i = 0; i < source_cloud.size();i++)
{
//点p-source point
Eigen::Vector2d p = source_cloud[i];
//target-point
Eigen::Vector2d refPt = ref_cloud[i];
//ref对应的normal
Eigen::Vector2d ni = ref_normals[i];
//加权矩阵
//对于p-p来说,Ci =wi * I
//对于point-line来说,Ci =wi * n*n^T
Eigen::Matrix2d Ci =ni * ni.transpose();
//构造M矩阵
Eigen::Matrix<double,2,4> Mi;
Mi << 1,0,p(0),-p(1),
0,1,p(1), p(0);
M += Mi.transpose() * Ci * Mi;
Eigen::Matrix<double,1,4> gti;
gti = -2 * refPt.transpose() * Ci * Mi;
gt += gti;
}
//g是个列向量
Eigen::Matrix<double,4,1> g = gt.transpose();
//构建完了M矩阵和g向量.
//在后续的求解过程中,基本上都使用的是2*M,因此直接令M = 2*M
M = 2*M;
//M(实际是2*M,下面等同)矩阵能分为4个部分
Eigen::Matrix2d A,B,D;
A = M.block(0,0,2,2);
B = M.block(0,2,2,2);
D = M.block(2,2,2,2);
//论文中还引入了S和SA矩阵.
Eigen::Matrix2d S,SA;
S = D - B.transpose() * A.inverse() * B;
SA = S.determinant() * S.inverse();
//目前所有的式子已经可以构建多项式系数了.
//式31右边p(\lambda)的系数
//p(\lambda)的系数为:
Eigen::Vector3d p_coffcient;
p_coffcient << S.determinant(),2*(S(0,0)+S(1,1)) ,4;
//论文中式(31)左边的系数(a x^2 + b x + c)为:
double a,b,c;
Eigen::Matrix4d tmpMatrix;
tmpMatrix.block(0,0,2,2) = A.inverse() * B * SA * SA.transpose()* B.transpose() * A.inverse().transpose();
tmpMatrix.block(0,2,2,2) = -A.inverse() * B * SA * SA.transpose();
tmpMatrix.block(2,0,2,2) = tmpMatrix.block(0,2,2,2).transpose();
tmpMatrix.block(2,2,2,2) = SA * SA.transpose() ;
c = g.transpose() * tmpMatrix * g;
tmpMatrix.block(0,0,2,2) = A.inverse() * B * SA * B.transpose() * A.inverse().transpose();
tmpMatrix.block(0,2,2,2) = -A.inverse() * B * SA;
tmpMatrix.block(2,0,2,2) = tmpMatrix.block(0,2,2,2).transpose();
tmpMatrix.block(2,2,2,2) = SA;
b = 4 * g.transpose() * tmpMatrix * g;
tmpMatrix.block(0,0,2,2) = A.inverse() * B * B.transpose() * A.inverse().transpose();
tmpMatrix.block(0,2,2,2) = -A.inverse() * B;
tmpMatrix.block(2,0,2,2) = tmpMatrix.block(0,2,2,2).transpose();
tmpMatrix.block(2,2,2,2) = Eigen::Matrix2d::Identity();
a = 4 * g.transpose() * tmpMatrix * g;
//把式31的等式两边进行合并,得到一个4次多项式.5个系数.
Eigen::VectorXd poly_coffi(5);
poly_coffi(0) = c - p_coffcient(0) * p_coffcient(0);
poly_coffi(1) = b - 2 * p_coffcient(0) * p_coffcient(1);
poly_coffi(2) = a - (p_coffcient(1)*p_coffcient(1) + 2*p_coffcient(0)*p_coffcient(2));
poly_coffi(3) = -2 * p_coffcient(1)*p_coffcient(2);
poly_coffi(4) = - p_coffcient(2) * p_coffcient(2);
for(int i = 0; i < 5;i++)
{
if(std::isnan(poly_coffi(i)))
{
std::cout <<"Error, This should not happen"<<std::endl;
}
}
//进行多项式的求解,得到对应的lambda.
double lambda;
if(SolverFourthOrderPolynomial(poly_coffi,lambda) == false)
{
std::cout <<"Polynomial Solve Failed"<<std::endl;
return false;
}
//得到lambda之后,根据式24.
Eigen::Matrix4d W;
W.setZero();
W.block(2,2,2,2) = Eigen::Matrix2d::Identity();
//Eigen::Vector4d res = -(M + 2 *lambda * W).inverse().transpose() * g;
Eigen::Vector4d res = -(M + 2 * lambda * W).inverse() * g;
//转换成旋转矩阵
double theta = std::atan2(res(3),res(2));
deltaTrans << cos(theta),-sin(theta),res(0),
sin(theta), cos(theta),res(1),
0, 0, 1;
return true;
}
/**
* @brief IMLSICPMatcher::ComputeNormal
* 计算法向量
* @param nearPoints 某个点周围的所有激光点
* @return
*/
Eigen::Vector2d IMLSICPMatcher::ComputeNormal(std::vector<Eigen::Vector2d> &nearPoints)
{
Eigen::Vector2d normal;
//根据周围的激光点计算法向量,参考ppt中NICP计算法向量的方法
if(nearPoints.size() < 3)
{
normal(0) = normal(1) = std::numeric_limits<double>::infinity();
return normal;
}
Eigen::Vector2d centroid(0,0);
for(auto point : nearPoints){
centroid += point;
}
centroid /= double(nearPoints.size());
Eigen::Matrix2d sigma = Eigen::Matrix2d::Zero();
for(auto point : nearPoints){
Eigen::Vector2d tmp = point - centroid;
sigma = tmp * tmp.transpose();
}
sigma /= double(nearPoints.size());
// 计算法向量,第一个特征向量即为最小特征值对应的特征向量,矩阵的第一列
Eigen::SelfAdjointEigenSolver<Eigen::Matrix2d> eig(sigma);
Eigen::Matrix2d eigVectors = eig.eigenvectors();
normal = eigVectors.col(0);
return normal;
}
/**
* @brief IMLSICPMatcher::Match
* 最终使用的ICP匹配函数.
* @param finalResult
* @param covariance
* @return
*/
bool IMLSICPMatcher::Match(Eigen::Matrix3d& finalResult,
Eigen::Matrix3d& covariance)
{
//如果没有设置法向量,则自动进行计算.
if(m_isGetNormals == false)
{
//自动计算target pointcloud中每个点的法向量
m_targetPtCloudNormals.clear();
for(int i = 0; i < m_targetPointCloud.size();i++)
{
Eigen::Vector2d xi = m_targetPointCloud[i];
int K = 20;
Eigen::VectorXi indices(K);
Eigen::VectorXd dist2(K);
int num = m_pTargetKDTree->knn(xi,indices,dist2,K,0.0,
Nabo::NNSearchD::SORT_RESULTS | Nabo::NNSearchD::ALLOW_SELF_MATCH|
Nabo::NNSearchD::TOUCH_STATISTICS,
0.15);
std::vector<Eigen::Vector2d> nearPoints;
for(int ix = 0; ix < K;ix++)
{
if(dist2(ix) < std::numeric_limits<double>::infinity() &&
std::isinf(dist2(ix)) == false)
{
nearPoints.push_back(m_targetKDTreeDataBase.col(indices(ix)));
}
else break;
}
//计算法向量
Eigen::Vector2d normal;
if(nearPoints.size() > 3)
{
//计算法向量
normal = ComputeNormal(nearPoints);
}
else
{
normal(0) = normal(1) = std::numeric_limits<double>::infinity();
}
m_targetPtCloudNormals.push_back(normal);
}
}
//初始化估计值.
Eigen::Matrix3d result;
result.setIdentity();
covariance.setIdentity();
for(int i = 0; i < m_Iterations;i++)
{
//根据当前估计的位姿对原始点云进行转换.
std::vector<Eigen::Vector2d> in_cloud;
for(int ix = 0; ix < m_sourcePointCloud.size();ix++)
{
Eigen::Vector3d origin_pt;
origin_pt << m_sourcePointCloud[ix],1;
Eigen::Vector3d now_pt = result * origin_pt;
in_cloud.push_back(Eigen::Vector2d(now_pt(0),now_pt(1)));
}
//把sourceCloud中的点投影到targetCloud组成的平面上
//对应的投影点即为sourceCloud的匹配点.
//每次转换完毕之后,都需要重新计算匹配点.
//这个函数会得到对应的匹配点.
//本次匹配会自动删除in_cloud内部的一些找不到匹配点的点.
//因此,这个函数出来之后,in_cloud和ref_cloud是一一匹配的.
std::vector<Eigen::Vector2d> ref_cloud;
std::vector<Eigen::Vector2d> ref_normal;
projSourcePtToSurface(in_cloud,
ref_cloud,
ref_normal);
if(in_cloud.size() < 5 || ref_cloud.size() < 5)
{
std::cout <<"Not Enough Correspondence:"<<in_cloud.size()<<","<<ref_cloud.size()<<std::endl;
std::cout <<"ICP Iterations Failed!!"<<std::endl;
return false;
}
//计算帧间位移.从当前的source -> target
Eigen::Matrix3d deltaTrans;
bool flag = SolveMotionEstimationProblem(in_cloud,
ref_cloud,
ref_normal,
deltaTrans);
if(flag == false)
{
std::cout <<"ICP Iterations Failed!!!!"<<std::endl;
return false;
}
//更新位姿.
result = deltaTrans * result;
//迭代条件是否满足.
double deltadist = std::sqrt( std::pow(deltaTrans(0,2),2) + std::pow(deltaTrans(1,2),2));
double deltaAngle = std::atan2(deltaTrans(1,0),deltaTrans(0,0));
//如果迭代条件允许,则直接退出.
if(deltadist < 0.001 && deltaAngle < (0.01/57.295))
{
break;
}
}
finalResult = result;
return true;
}
//调用Eigen求解四次多项式的第一个非零实根
bool IMLSICPMatcher::SolverFourthOrderPolynomial(Eigen::VectorXd&p_coffi,
double &lambda)
{
Eigen::PolynomialSolver<double,4> polySolve(p_coffi);
Eigen::Matrix<std::complex<double>,4,1,0,4,1> roots = polySolve.roots();
bool isAssigned = false;
double finalRoot = 0.0;
//找到第一个非零实根--有可能不止一个,因为有优化空间.
for(int i = 0; i < roots.size();i++)
{
//如果是虚根,则不要.
if(roots(i).imag() != 0 )continue;
//如果是非零实根,则选择.
if(isAssigned == false || roots(i).real() > finalRoot)
{
isAssigned = true;
finalRoot = roots(i).real();
}
}
lambda = finalRoot;
return isAssigned;
}
第二题源代码:
#include "imls_icp.h"
#include <ros/ros.h>
#include "tf/transform_listener.h"
#include "sensor_msgs/PointCloud.h"
#include "geometry_msgs/Point32.h"
#include "visualization_msgs/Marker.h"
#include "visualization_msgs/MarkerArray.h"
#include "champion_nav_msgs/ChampionNavLaserScan.h"
#include <nav_msgs/Odometry.h>
#include <nav_msgs/Path.h>
#include <rosbag/bag.h>
#include <rosbag/view.h>
#include <boost/foreach.hpp>
#include <csm/csm_all.h>
using namespace Eigen;
//pcl::visualization::CloudViewer g_cloudViewer("cloud_viewer");
//此处bag包的地址需要自行修改
std::string bagfile = "/home/zzr/HW4/imlsMatcherProject/src/bag/imls_icp.bag";
class imlsDebug
{
public:
imlsDebug()
{
SetPIICPParams();
m_imlsPathPub = m_node.advertise<nav_msgs::Path>("imls_path_pub_",1,true);
m_imlsPath.header.stamp = ros::Time::now();
m_imlsPath.header.frame_id = "odom";
m_odomPathPub = m_node.advertise<nav_msgs::Path>("odom_path_pub_",1,true);
m_odomPath.header.stamp = ros::Time::now();
m_odomPath.header.frame_id = "odom";
m_isFirstFrame = true;
rosbag::Bag bag;
bag.open(bagfile, rosbag::bagmode::Read);
std::vector<std::string> topics;
topics.push_back(std::string("/sick_scan"));
topics.push_back(std::string("/odom"));
rosbag::View view(bag, rosbag::TopicQuery(topics));
//按顺序读取bag内激光的消息和里程计的消息
BOOST_FOREACH(rosbag::MessageInstance const m, view)
{
champion_nav_msgs::ChampionNavLaserScanConstPtr scan = m.instantiate<champion_nav_msgs::ChampionNavLaserScan>();
if(scan != NULL)
championLaserScanCallback(scan);
nav_msgs::OdometryConstPtr odom = m.instantiate<nav_msgs::Odometry>();
if(odom != NULL)
odomCallback(odom);
ros::spinOnce();
if(!ros::ok())
break;
}
// m_laserscanSub = m_nh.subscribe("sick_scan",5,&imlsDebug::championLaserScanCallback,this);
}
//将激光消息转换为激光坐标系下的二维点云
void ConvertChampionLaserScanToEigenPointCloud(const champion_nav_msgs::ChampionNavLaserScanConstPtr& msg,
std::vector<Eigen::Vector2d>& eigen_pts)
{
eigen_pts.clear();
for(int i = 0; i < msg->ranges.size(); ++i)
{
if(msg->ranges[i] < msg->range_min || msg->ranges[i] > msg->range_max)
continue;
double lx = msg->ranges[i] * std::cos(msg->angles[i]);
double ly = msg->ranges[i] * std::sin(msg->angles[i]);
if(std::isnan(lx) || std::isinf(ly) ||
std::isnan(ly) || std::isinf(ly))
continue;
eigen_pts.push_back(Eigen::Vector2d(lx,ly));
}
}
void championLaserScanCallback(const champion_nav_msgs::ChampionNavLaserScanConstPtr& msg)
{
if(m_isFirstFrame == true)
{
std::cout <<"First Frame"<<std::endl;
m_isFirstFrame = false;
m_prevLaserPose = Eigen::Vector3d(0, 0, 0);
pubPath(m_prevLaserPose, m_imlsPath, m_imlsPathPub);
#if 0
ConvertChampionLaserScanToEigenPointCloud(msg, m_prevPointCloud);
#else
LaserScanToLDP(msg,m_prevLDP);
#endif
return ;
}
#if 0
std::vector<Eigen::Vector2d> nowPts;
ConvertChampionLaserScanToEigenPointCloud(msg, nowPts);
//调用imls进行icp匹配,并输出结果.
m_imlsMatcher.setSourcePointCloud(nowPts);
m_imlsMatcher.setTargetPointCloud(m_prevPointCloud);
Eigen::Matrix3d rPose,rCovariance;
if(m_imlsMatcher.Match(rPose,rCovariance))
{
std::cout <<"IMLS Match Successful:"<<rPose(0,2)<<","<<rPose(1,2)<<","<<atan2(rPose(1,0),rPose(0,0))*57.295<<std::endl;
Eigen::Matrix3d lastPose;
lastPose << cos(m_prevLaserPose(2)), -sin(m_prevLaserPose(2)), m_prevLaserPose(0),
sin(m_prevLaserPose(2)), cos(m_prevLaserPose(2)), m_prevLaserPose(1),
0, 0, 1;
Eigen::Matrix3d nowPose = lastPose * rPose;
m_prevLaserPose << nowPose(0, 2), nowPose(1, 2), atan2(nowPose(1,0), nowPose(0,0));
pubPath(m_prevLaserPose, m_imlsPath, m_imlsPathPub);
}
else
{
std::cout <<"IMLS Match Failed!!!!"<<std::endl;
}
m_prevPointCloud = nowPts;
#else
LDP currentLDP;
LaserScanToLDP(msg,currentLDP);
Vector3d d_point_scan = PIICPBetweenTwoFrames(currentLDP, Vector3d(0,0,0));
Matrix3d lastPose, rPose;
// 构造旋转矩阵 生成三种位姿
lastPose << cos(m_prevLaserPose(2)), -sin(m_prevLaserPose(2)), m_prevLaserPose(0),
sin(m_prevLaserPose(2)), cos(m_prevLaserPose(2)), m_prevLaserPose(1),
0, 0, 1;
rPose << cos(d_point_scan(2)), -sin(d_point_scan(2)), d_point_scan(0),
sin(d_point_scan(2)), cos(d_point_scan(2)), d_point_scan(1),
0, 0, 1;
Eigen::Matrix3d nowPose = lastPose * rPose;
m_prevLaserPose << nowPose(0, 2), nowPose(1, 2), atan2(nowPose(1,0), nowPose(0,0));
pubPath(m_prevLaserPose, m_imlsPath, m_imlsPathPub);
#endif
}
void odomCallback(const nav_msgs::OdometryConstPtr& msg)
{
if(m_isFirstFrame == true)
return;
pubPath(msg, m_odomPath, m_odomPathPub);
}
//发布路径消息
void pubPath(Eigen::Vector3d& pose, nav_msgs::Path &path, ros::Publisher &mcu_path_pub_)
{
ros::Time current_time = ros::Time::now();
geometry_msgs::PoseStamped this_pose_stamped;
this_pose_stamped.pose.position.x = pose(0);
this_pose_stamped.pose.position.y = pose(1);
geometry_msgs::Quaternion goal_quat = tf::createQuaternionMsgFromYaw(pose(2));
this_pose_stamped.pose.orientation.x = goal_quat.x;
this_pose_stamped.pose.orientation.y = goal_quat.y;
this_pose_stamped.pose.orientation.z = goal_quat.z;
this_pose_stamped.pose.orientation.w = goal_quat.w;
this_pose_stamped.header.stamp = current_time;
this_pose_stamped.header.frame_id = "odom";
path.poses.push_back(this_pose_stamped);
mcu_path_pub_.publish(path);
}
void pubPath(const nav_msgs::OdometryConstPtr& msg, nav_msgs::Path &path, ros::Publisher &mcu_path_pub_)
{
ros::Time current_time = ros::Time::now();
geometry_msgs::PoseStamped this_pose_stamped;
this_pose_stamped.pose.position.x = msg->pose.pose.position.x;
this_pose_stamped.pose.position.y = msg->pose.pose.position.y;
this_pose_stamped.pose.orientation.x = msg->pose.pose.orientation.x;
this_pose_stamped.pose.orientation.y = msg->pose.pose.orientation.y;
this_pose_stamped.pose.orientation.z = msg->pose.pose.orientation.z;
this_pose_stamped.pose.orientation.w = msg->pose.pose.orientation.w;
this_pose_stamped.header.stamp = current_time;
this_pose_stamped.header.frame_id = "odom";
path.poses.push_back(this_pose_stamped);
mcu_path_pub_.publish(path);
}
bool m_isFirstFrame;
ros::NodeHandle m_nh;
IMLSICPMatcher m_imlsMatcher;
Eigen::Vector3d m_prevLaserPose;
std::vector<Eigen::Vector2d> m_prevPointCloud;
nav_msgs::Path m_imlsPath;
nav_msgs::Path m_odomPath;
tf::TransformListener m_tfListener;
ros::NodeHandle m_node;
ros::Subscriber m_laserscanSub;
ros::Publisher m_imlsPathPub;
ros::Publisher m_odomPathPub;
//进行PI-ICP需要的变量
LDP m_prevLDP;
sm_params m_PIICPParams;
sm_result m_OutputResult;
//进行pl-icp的相关函数.
void SetPIICPParams()
{
//设置激光的范围
m_PIICPParams.min_reading = 0.1;
m_PIICPParams.max_reading = 20;
//设置位姿最大的变化范围
m_PIICPParams.max_angular_correction_deg = 20.0;
m_PIICPParams.max_linear_correction = 1;
//设置迭代停止的条件
m_PIICPParams.max_iterations = 50;
m_PIICPParams.epsilon_xy = 0.000001;
m_PIICPParams.epsilon_theta = 0.0000001;
//设置correspondence相关参数
m_PIICPParams.max_correspondence_dist = 1;
m_PIICPParams.sigma = 0.01;
m_PIICPParams.use_corr_tricks = 1;
//设置restart过程,因为不需要restart所以可以不管
m_PIICPParams.restart = 0;
m_PIICPParams.restart_threshold_mean_error = 0.01;
m_PIICPParams.restart_dt = 1.0;
m_PIICPParams.restart_dtheta = 0.1;
//设置聚类参数
m_PIICPParams.clustering_threshold = 0.2;
//用最近的10个点来估计方向
m_PIICPParams.orientation_neighbourhood = 10;
//设置使用PI-ICP
m_PIICPParams.use_point_to_line_distance = 1;
//不进行alpha_test
m_PIICPParams.do_alpha_test = 0;
m_PIICPParams.do_alpha_test_thresholdDeg = 5;
//设置trimmed参数 用来进行outlier remove
m_PIICPParams.outliers_maxPerc = 0.9;
m_PIICPParams.outliers_adaptive_order = 0.7;
m_PIICPParams.outliers_adaptive_mult = 2.0;
//进行visibility_test 和 remove double
m_PIICPParams.do_visibility_test = 1;
m_PIICPParams.outliers_remove_doubles = 1;
m_PIICPParams.do_compute_covariance = 0;
m_PIICPParams.debug_verify_tricks = 0;
m_PIICPParams.use_ml_weights = 0;
m_PIICPParams.use_sigma_weights = 0;
}
void LaserScanToLDP(const champion_nav_msgs::ChampionNavLaserScanConstPtr& msg, LDP& ldp)
{
int nPts = msg->ranges.size();
ldp = ld_alloc_new(nPts);
for(int i = 0;i < nPts;i++)
{
double dist = msg->ranges[i];
if(dist > 0.1 && dist < 20)
{
ldp->valid[i] = 1;
ldp->readings[i] = dist;
}
else
{
ldp->valid[i] = 0;
ldp->readings[i] = -1;
}
ldp->theta[i] = msg->angles[i];
}
ldp->min_theta = ldp->theta[0];
ldp->max_theta = ldp->theta[nPts-1];
ldp->odometry[0] = 0.0;
ldp->odometry[1] = 0.0;
ldp->odometry[2] = 0.0;
ldp->true_pose[0] = 0.0;
ldp->true_pose[1] = 0.0;
ldp->true_pose[2] = 0.0;
}
Eigen::Vector3d PIICPBetweenTwoFrames(LDP& currentLDPScan,
Eigen::Vector3d tmprPose)
{
m_prevLDP->odometry[0] = 0.0;
m_prevLDP->odometry[1] = 0.0;
m_prevLDP->odometry[2] = 0.0;
m_prevLDP->estimate[0] = 0.0;
m_prevLDP->estimate[1] = 0.0;
m_prevLDP->estimate[2] = 0.0;
m_prevLDP->true_pose[0] = 0.0;
m_prevLDP->true_pose[1] = 0.0;
m_prevLDP->true_pose[2] = 0.0;
//设置匹配的参数值
m_PIICPParams.laser_ref = m_prevLDP;
m_PIICPParams.laser_sens = currentLDPScan;
m_PIICPParams.first_guess[0] = tmprPose(0);
m_PIICPParams.first_guess[1] = tmprPose(1);
m_PIICPParams.first_guess[2] = tmprPose(2);
m_OutputResult.cov_x_m = 0;
m_OutputResult.dx_dy1_m = 0;
m_OutputResult.dx_dy2_m = 0;
sm_icp(&m_PIICPParams,&m_OutputResult);
//nowPose在lastPose中的坐标
Eigen::Vector3d rPose;
if(m_OutputResult.valid)
{
//得到两帧激光之间的相对位姿
rPose(0)=(m_OutputResult.x[0]);
rPose(1)=(m_OutputResult.x[1]);
rPose(2)=(m_OutputResult.x[2]);
}
else
{
std::cout <<"PI ICP Failed!!!!!!!"<<std::endl;
rPose = tmprPose;
}
//更新
m_prevLDP = currentLDPScan;
return rPose;
}
};
int main(int argc, char** argv)
{
ros::init(argc, argv, "imls_debug");
imlsDebug imls_debug;
ros::spin();
return (0);
}