当前位置:   article > 正文

激光SLAM源码解析S-LOAM(二)激光里程计的计算_eigen::map t_last_curr(para_t);

eigen::map t_last_curr(para_t);

10Hz激光雷达点云帧,相临帧的时间间隔是0.1秒。在这0.1秒内激光雷达的位姿变化(平移和旋转)可由这两帧点云的配准计算出来。

以某一时刻为起点,累计帧间位姿变换,得到各帧时刻激光雷达相对起点的位姿(位置和方向),这就是里程计。

配准是求取帧间变换的关键,帧帧配准是指相邻帧点云的配准,比如,配对的双方分别是当前帧与上一帧。

点云共面算法是点云帧帧配准的主要方法。当一个点到一个平面的距离为0时,就认为这个点就与此平面共面。

lidarOdometry.cpp

代码注释较详细,如下所示。

  1. /**
  2. * Created by haocaichao on 2021/8/29
  3. *
  4. * 激光里程计的计算(帧帧配准)
  5. * (1)接收帧平面特征点云
  6. * (2)计算当前帧与上一帧之间位姿变换(帧帧配准,ceres优化)
  7. * (3)累计帧间变换,得到从起始帧开始的里程计
  8. * (4)发布点云、里程计、轨迹、地图
  9. */
  10. #include "header.h"
  11. typedef pcl::PointXYZI PointType;
  12. int isDone=1; //标识运算是否完成
  13. float planeMax=0.5; //平面判断门槛值
  14. std::mutex mLock; //多线程锁
  15. pcl::VoxelGrid<PointType> downSizeFilterMap; //定义点云下采样对象,用于点云抽稀
  16. /**
  17. * 定义ceres优化中的代价函数
  18. * 点到平面的距离计算用向量表示
  19. * 在同一坐标系中,两点间(po,pa)向量值与平面法向量(norm)的点乘可得到距离
  20. */
  21. struct PlaneFeatureCost{
  22. const Eigen::Vector3d _po,_pa,_norm;
  23. PlaneFeatureCost(Eigen::Vector3d po,Eigen::Vector3d pa,Eigen::Vector3d norm):
  24. _po(po),_pa(pa),_norm(norm){}
  25. template <typename T>
  26. bool operator()(const T* q,const T* t,T* residual) const {
  27. Eigen::Matrix<T,3,1> po_curr{T(_po[0]),T(_po[1]),T(_po[2])};
  28. Eigen::Matrix<T,3,1> pa_last{T(_pa[0]),T(_pa[1]),T(_pa[2])};
  29. Eigen::Matrix<T,3,1> p_norm{T(_norm[0]),T(_norm[1]),T(_norm[2])};
  30. Eigen::Quaternion<T> q_last_curr{q[3],q[0],q[1],q[2]}; //用于坐标系变换统一
  31. Eigen::Matrix<T,3,1> t_last_curr{t[0],t[1],t[2]}; //用于坐标系变换统一
  32. Eigen::Matrix<T,3,1> po_last;
  33. po_last=q_last_curr*po_curr+t_last_curr;
  34. residual[0]=((po_last-pa_last).dot(p_norm));
  35. return true;
  36. }
  37. };
  38. ros::Subscriber sub_plane_frame_cloud; //接收平面特征点云
  39. ros::Publisher pub_plane_frame_cloud; //发布平面特征点云
  40. ros::Publisher pub_frame_odometry; //发布激光雷达里程计,由帧帧配准计算得到
  41. ros::Publisher pub_frame_odom_path; //发布激光雷达运动轨迹
  42. ros::Publisher pub_sum_lidar_odom_cloud; //发布拼接后的点云地图
  43. //存储当前帧点云
  44. pcl::PointCloud<PointType>::Ptr lastFramePlanePtr(new pcl::PointCloud<PointType>());
  45. //存储上一帧点云
  46. pcl::PointCloud<PointType>::Ptr currFramePlanePtr(new pcl::PointCloud<PointType>());
  47. //存储拼接后总点云
  48. pcl::PointCloud<PointType>::Ptr sumPlaneCloudPtr(new pcl::PointCloud<PointType>());
  49. std::queue<sensor_msgs::PointCloud2> planeQueue; //定义点云消息队列
  50. nav_msgs::Path lidarPathInOdom; //定义激光雷达运动轨迹
  51. std_msgs::Header currHead; //定义ros消息头变量
  52. double timePlane=0; //定义平面点云帧时间戳变量
  53. int numFrame=0; //定义帧计数变量
  54. int flagStart=0; //定义是否开始标志
  55. double para_q[4]={0,0,0,1}; //定义长度为4的数组,用于构成四元数
  56. double para_t[3]={0,0,0}; //定义长度为3的数组,用于构成位移
  57. Eigen::Quaterniond q_0_curr(1,0,0,0); //起点到当前帧,四元数
  58. Eigen::Vector3d t_0_curr(0,0,0); //起点到当前帧,位移
  59. Eigen::Quaterniond q_0_last(1,0,0,0); //起点到上一帧,四元数
  60. Eigen::Vector3d t_0_last(0,0,0); //起点到上一帧,位移
  61. //上一帧到当前帧,四元数
  62. Eigen::Quaterniond q_last_curr=Eigen::Map<Eigen::Quaterniond>(para_q);
  63. //上一帧到当前帧,位移
  64. Eigen::Vector3d t_last_curr=Eigen::Map<Eigen::Vector3d>(para_t);
  65. //将当前点坐标变换到上一帧坐标系中
  66. void transformToLast(PointType const *const pi,PointType *const po){
  67. Eigen::Vector3d curr_point(pi->x,pi->y,pi->z);
  68. Eigen::Vector3d proj_point;
  69. proj_point=q_last_curr*curr_point+t_last_curr;
  70. po->x=proj_point.x();
  71. po->y=proj_point.y();
  72. po->z=proj_point.z();
  73. po->intensity=pi->intensity;
  74. }
  75. //发布点云、里程计、轨迹、地图
  76. void publishResult(){
  77. //累计帧间变换,得到从起点开始的里程计
  78. q_0_curr = q_0_last * q_last_curr ;
  79. t_0_curr = t_0_last + q_0_last * t_last_curr;
  80. q_0_last = q_0_curr;
  81. t_0_last = t_0_curr;
  82. //发布里程计
  83. nav_msgs::Odometry lidarOdometry;
  84. lidarOdometry.header.frame_id = "map";
  85. lidarOdometry.child_frame_id = "map_child";
  86. lidarOdometry.header.stamp = currHead.stamp;
  87. lidarOdometry.pose.pose.position.x = t_0_curr[0];
  88. lidarOdometry.pose.pose.position.y = t_0_curr[1];
  89. lidarOdometry.pose.pose.position.z = t_0_curr[2];
  90. lidarOdometry.pose.pose.orientation.x = q_0_curr.x();
  91. lidarOdometry.pose.pose.orientation.y = q_0_curr.y();
  92. lidarOdometry.pose.pose.orientation.z = q_0_curr.z();
  93. lidarOdometry.pose.pose.orientation.w = q_0_curr.w();
  94. pub_frame_odometry.publish(lidarOdometry);
  95. //发布里轨迹
  96. geometry_msgs::PoseStamped pose_stamped;
  97. pose_stamped.header.stamp = lidarOdometry.header.stamp;
  98. pose_stamped.header.frame_id = "map";
  99. pose_stamped.pose = lidarOdometry.pose.pose;
  100. lidarPathInOdom.poses.push_back(pose_stamped);
  101. lidarPathInOdom.header.stamp=lidarOdometry.header.stamp;
  102. lidarPathInOdom.header.frame_id="map";
  103. pub_frame_odom_path.publish(lidarPathInOdom);
  104. //发布平面特征点云
  105. sensor_msgs::PointCloud2 plane_frame_cloud_msgs;
  106. pcl::toROSMsg(*currFramePlanePtr, plane_frame_cloud_msgs);
  107. plane_frame_cloud_msgs.header.stamp = lidarOdometry.header.stamp;
  108. plane_frame_cloud_msgs.header.frame_id = "map";
  109. pub_plane_frame_cloud.publish(plane_frame_cloud_msgs);
  110. //发布拼接点云地图
  111. // if(numFrame % 10 == 0){
  112. // double r,p,y;
  113. // tf::Quaternion tq(q_0_curr.x(),q_0_curr.y(),q_0_curr.z(),q_0_curr.w());
  114. // tf::Matrix3x3(tq).getRPY(r,p,y);
  115. // Eigen::Affine3d transCurd ;
  116. // pcl::getTransformation(t_0_curr.x(), t_0_curr.y(), t_0_curr.z(), r,p,y,transCurd);
  117. // pcl::PointCloud<PointType>::Ptr cloud_res(new pcl::PointCloud<PointType>());
  118. // pcl::transformPointCloud(*currFramePlanePtr, *cloud_res, transCurd);
  119. // *sumPlaneCloudPtr += *cloud_res;
  120. // pcl::PointCloud<PointType>::Ptr cloud_temp(new pcl::PointCloud<PointType>());
  121. // downSizeFilterMap.setInputCloud(sumPlaneCloudPtr);
  122. // downSizeFilterMap.filter(*cloud_temp);
  123. // sensor_msgs::PointCloud2 res_cloud_msgs;
  124. // pcl::toROSMsg(*cloud_temp, res_cloud_msgs);
  125. // res_cloud_msgs.header.stamp = lidarOdometry.header.stamp;
  126. // res_cloud_msgs.header.frame_id = "map";
  127. // pub_sum_lidar_odom_cloud.publish(res_cloud_msgs);
  128. // }
  129. }
  130. //计算帧帧配准,得到帧间位姿变换
  131. void frameRegistration(){
  132. //定义ceres优化对象与参数
  133. ceres::LossFunction *loss_function=new ceres::HuberLoss(0.1);
  134. ceres::LocalParameterization *q_parameterization=new ceres::EigenQuaternionParameterization();
  135. ceres::Problem problem;
  136. problem.AddParameterBlock(para_q,4,q_parameterization);
  137. problem.AddParameterBlock(para_t,3);
  138. pcl::KdTreeFLANN<PointType> kdTreePlanLast;
  139. int last_plane_num=lastFramePlanePtr->points.size();
  140. int curr_plane_num=currFramePlanePtr->points.size();
  141. if(last_plane_num>10){
  142. kdTreePlanLast.setInputCloud(lastFramePlanePtr);
  143. for(int i_opt=0;i_opt<2;i_opt++){
  144. for (int i = 0; i < curr_plane_num; ++i) { //遍历当前帧各平面点
  145. PointType pointSeed;
  146. //将当前帧此平面点坐标变换到上一帧坐标系中
  147. transformToLast(&currFramePlanePtr->points[i],&pointSeed);
  148. std::vector<float> pointSearchSqDis1;
  149. std::vector<int> indx1;
  150. //将变换后的此点作为种子点,查找上一帧中距离此点最近点的索引
  151. kdTreePlanLast.nearestKSearch(pointSeed,1,indx1,pointSearchSqDis1);
  152. int p_ind_a=indx1[0];
  153. std::vector<float> pointSearchSqDis2;
  154. std::vector<int> indx2;
  155. //将上面最近点作为种子点,查找上一帧中距离最近点的索引和距离
  156. kdTreePlanLast.nearestKSearch(lastFramePlanePtr->points[p_ind_a],30,indx2,pointSearchSqDis2);
  157. std::vector<int> v_indx5;
  158. std::vector<int> v_indx_row;
  159. int p_row=-1;
  160. if(indx2.size()<5) continue;
  161. int n=5;
  162. //挑选5个最近点,尽量满足有2个点不属于同一扫描线
  163. for (size_t i_kd = 0; i_kd < indx2.size(); ++i_kd) {
  164. float f_indx=lastFramePlanePtr->points[indx2[i_kd]].intensity;
  165. int i_indx=int(f_indx);
  166. int row=100*(f_indx-i_indx+0.002); //获取点索引
  167. if(i_kd==0){
  168. p_row=row;
  169. }
  170. if(i_kd<5){ //先将最近5个点选入
  171. v_indx5.push_back(indx2[i_kd]);
  172. }else{ //从第6个点开始,寻找与记录不同线的最近2个点
  173. if(row != p_row && row>=0 && row <=63){
  174. v_indx_row.push_back(indx2[i_kd]);
  175. n=i_kd;
  176. if(v_indx_row.size()>=2){
  177. break;
  178. }
  179. }
  180. }
  181. }
  182. if(v_indx_row.size()==1){ //如果不同线的只有1个点
  183. v_indx5[4]=v_indx_row[0];
  184. }
  185. if(v_indx_row.size()==2){ //如果不同线的有2个点
  186. v_indx5[3]=v_indx_row[0];
  187. v_indx5[4]=v_indx_row[1];
  188. }
  189. if(pointSearchSqDis2[n]<1){ //如果5个点中最远的点小于1米,则利用5点估算平面法线
  190. Eigen::Matrix<float, 5, 3> matA0;
  191. Eigen::Matrix<float, 5, 1> matB0;
  192. Eigen::Vector3f matX0;
  193. matA0.setZero();
  194. matB0.fill(-1);
  195. matX0.setZero();
  196. for (int j = 0; j < 5; ++j) {
  197. matA0(j,0)=lastFramePlanePtr->points[v_indx5[j]].x;
  198. matA0(j,1)=lastFramePlanePtr->points[v_indx5[j]].y;
  199. matA0(j,2)=lastFramePlanePtr->points[v_indx5[j]].z;
  200. }
  201. matX0=matA0.colPivHouseholderQr().solve(matB0);
  202. matX0.normalize(); //norm
  203. bool planeValid = true;
  204. for (int k = 0; k < 4; ++k) { //利用法向量计算各点到平面的距离
  205. Eigen::Vector3d v_temp(
  206. lastFramePlanePtr->points[v_indx5[k]].x-lastFramePlanePtr->points[v_indx5[k+1]].x,
  207. lastFramePlanePtr->points[v_indx5[k]].y-lastFramePlanePtr->points[v_indx5[k+1]].y,
  208. lastFramePlanePtr->points[v_indx5[k]].z-lastFramePlanePtr->points[v_indx5[k+1]].z
  209. );
  210. if(fabs(matX0(0)*v_temp[0]+matX0(1)*v_temp[1]+matX0(2)*v_temp[2])>planeMax){
  211. planeValid=false; //如果有点到平面的距离太大,则说明此5点不共面
  212. break;
  213. }
  214. }
  215. Eigen::Vector3d po(currFramePlanePtr->points[i].x,currFramePlanePtr->points[i].y,currFramePlanePtr->points[i].z);
  216. Eigen::Vector3d pa(lastFramePlanePtr->points[p_ind_a].x,lastFramePlanePtr->points[p_ind_a].y,lastFramePlanePtr->points[p_ind_a].z);
  217. Eigen::Vector3d norm(matX0[0],matX0[1],matX0[2]);
  218. if(planeValid){ //当找到了共面点,就利用种子点、最近点、平面法向量,构造点与平面共面的优化条件
  219. problem.AddResidualBlock(new ceres::AutoDiffCostFunction<PlaneFeatureCost,1,4,3>
  220. (new PlaneFeatureCost(po,pa,norm)),loss_function,para_q,para_t);
  221. }
  222. }
  223. }
  224. }
  225. //设置优化参数,并优化
  226. ceres::Solver::Options options;
  227. options.linear_solver_type=ceres::DENSE_QR;
  228. options.max_num_iterations=8;
  229. options.minimizer_progress_to_stdout=false;
  230. ceres::Solver::Summary summary;
  231. ceres::Solve(options,&problem,&summary);
  232. //得到优化结果,构成帧间变换的方向四元数与位移
  233. q_last_curr=Eigen::Map<Eigen::Quaterniond>(para_q);
  234. t_last_curr=Eigen::Map<Eigen::Vector3d>(para_t);
  235. }
  236. }
  237. //接收平面特征点云,添加到消息队列中
  238. void cloudHandler(const sensor_msgs::PointCloud2ConstPtr &cldMsg) {
  239. mLock.lock();
  240. planeQueue.push(*cldMsg);
  241. mLock.unlock();
  242. }
  243. //点云处理多线程函数
  244. void cloudThread(){
  245. ros::Rate rate(10); //控制处理频率为10hz,为最终频率
  246. while(ros::ok()){
  247. rate.sleep();
  248. ros::Rate rate2(50); //内层处理频率为50hz
  249. if(isDone==0) continue;
  250. while (planeQueue.size()>0){
  251. if(isDone==0) continue;
  252. isDone=0;
  253. numFrame++;
  254. rate2.sleep();
  255. mLock.lock(); //锁线程,取数据
  256. currFramePlanePtr->clear();
  257. currHead=planeQueue.front().header;
  258. timePlane=planeQueue.front().header.stamp.toSec();
  259. pcl::fromROSMsg(planeQueue.front(),*currFramePlanePtr);
  260. planeQueue.pop();
  261. mLock.unlock();
  262. if(flagStart==0){ //标志起始帧
  263. flagStart=1;
  264. }else{ //从第二帧开始计算帧帧配准
  265. frameRegistration();
  266. publishResult();
  267. }
  268. *lastFramePlanePtr=*currFramePlanePtr;
  269. isDone=1;
  270. }
  271. }
  272. }
  273. int main(int argc, char **argv) {
  274. if(N_SCAN_ROW==16){
  275. planeMax=0.15;
  276. }
  277. if(N_SCAN_ROW==64){
  278. planeMax=0.05;
  279. }
  280. downSizeFilterMap.setLeafSize(0.4,0.4,0.4);
  281. ros::init(argc, argv, "LidarOdometry");
  282. ros::NodeHandle nh;
  283. sub_plane_frame_cloud = nh.subscribe<sensor_msgs::PointCloud2>("/plane_frame_cloud1", 10, cloudHandler);
  284. pub_plane_frame_cloud=nh.advertise<sensor_msgs::PointCloud2>("/plane_frame_cloud2",100);
  285. pub_frame_odometry = nh.advertise<nav_msgs::Odometry>("/frame_odom2", 100);
  286. pub_frame_odom_path = nh.advertise<nav_msgs::Path>("/frame_odom_path2", 100);
  287. pub_sum_lidar_odom_cloud = nh.advertise<sensor_msgs::PointCloud2>("/sum_lidar_odom_cloud2", 10);
  288. ROS_INFO("\033[1;32m----> LidarOdometry Started.\033[0m");
  289. std::thread t_cloud_thread(cloudThread);
  290. ros::spin();
  291. t_cloud_thread.join();
  292. return 0;
  293. }

代码地址为(GitHub - haocaichao/S-LOAM: S-LOAM(Simple LOAM) 是一种简单易学的激光SLAM算法,整个程序只有几百行代码,十分方便学习与试验分析。)。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/102983
推荐阅读
相关标签
  

闽ICP备14008679号