当前位置:   article > 正文

LIO-SAM_based_relocalization运行kitti回环序列并保存轨迹评估(一)——————源码的分析_liosam轨迹输出

liosam轨迹输出

LIO-SAM_based_relocalization是由哈尔滨工业大学GaoChao开发的基于lio-sam改版的机器人重定位系统,依赖于先验地图信息。github链接如下:

https://github.com/Gaochao-hit/LIO-SAM_based_relocalization

LIO-SAM源码包含imageProjection、imuPreintegration、featureExtraction、mapOptmization四个cpp文件。GaoChao在此基础上增加了globalLocalize代码,并修改简化了源代码。

LIO-SAM源码的分析网络上比比皆是,但对于LIO-SAM_based_relocalization的分析确没有。笔者花费大半个月的时间分析并且测试了LIO-SAM_based_relocalization,从网上下载kitti原始数据并制作成bag包,整个过程较为曲折,踩了不少坑,特此记录。

该算法中imageProjection主要进行数据预处理,移动机器人小车在一帧lidar数据获取过程中也在不断的运动,导致lidar获取的数据不在同一个坐标系下,所以需要除去雷达帧的平移和旋转畸变,即这部分代码的作用就是把雷达旋转一周时,所有的点云都转换到同一个位姿下,也就是该帧初始时刻的lidar坐标系下;featureExtraction提取特征点云;imuPreintegration用来imu预积分。

最为核心的部分就是mapOptmization模块,代码量也是其余之和。Gao对其进行了修改和简化,貌似是去掉了回环检测的功能(笔者的bag包测试也证实了这一点)。下文详细注释了代码的过程,并增加了tum格式的轨迹输出(此处踩坑较多:

  1. #include "utility.h"
  2. #include "lio_sam/cloud_info.h"
  3. #include <gtsam/geometry/Rot3.h>
  4. #include <gtsam/geometry/Pose3.h>
  5. #include <gtsam/slam/PriorFactor.h>
  6. #include <gtsam/slam/BetweenFactor.h>
  7. #include <gtsam/navigation/GPSFactor.h>
  8. #include <gtsam/navigation/ImuFactor.h>
  9. #include <gtsam/navigation/CombinedImuFactor.h>
  10. #include <gtsam/nonlinear/NonlinearFactorGraph.h>
  11. #include <gtsam/nonlinear/LevenbergMarquardtOptimizer.h>
  12. #include <gtsam/nonlinear/Marginals.h>
  13. #include <gtsam/nonlinear/Values.h>
  14. #include <gtsam/inference/Symbol.h>
  15. #include <gtsam/nonlinear/ISAM2.h>
  16. using namespace gtsam;
  17. using symbol_shorthand::X; // Pose3 (x,y,z,r,p,y)
  18. using symbol_shorthand::V; // Vel (xdot,ydot,zdot)
  19. using symbol_shorthand::B; // Bias (ax,ay,az,gx,gy,gz)
  20. using symbol_shorthand::G; // GPS pose
  21. /*
  22. * A point cloud type that has 6D pose info ([x,y,z,roll,pitch,yaw] intensity is time stamp)
  23. */
  24. struct PointXYZIRPYT //6D位姿点云结构体定义
  25. {
  26. PCL_ADD_POINT4D
  27. PCL_ADD_INTENSITY; // preferred way of adding a XYZ+padding
  28. float roll;
  29. float pitch;
  30. float yaw;
  31. double time;
  32. EIGEN_MAKE_ALIGNED_OPERATOR_NEW // make sure our new allocators are aligned
  33. } EIGEN_ALIGN16; // enforce SSE padding for correct memory alignment
  34. POINT_CLOUD_REGISTER_POINT_STRUCT (PointXYZIRPYT,
  35. (float, x, x) (float, y, y)
  36. (float, z, z) (float, intensity, intensity)
  37. (float, roll, roll) (float, pitch, pitch) (float, yaw, yaw)
  38. (double, time, time))
  39. typedef PointXYZIRPYT PointTypePose;
  40. class mapOptimization : public ParamServer
  41. {
  42. public:
  43. // gtsam
  44. NonlinearFactorGraph gtSAMgraph;
  45. Values initialEstimate;
  46. Values optimizedEstimate;
  47. ISAM2 *isam;
  48. Values isamCurrentEstimate;
  49. Eigen::MatrixXd poseCovariance;
  50. ros::Publisher pubLaserCloudSurround;
  51. ros::Publisher pubOdomAftMappedROS;
  52. ros::Publisher pubKeyPoses;
  53. ros::Publisher pubPath;
  54. ros::Publisher pubHistoryKeyFrames;
  55. ros::Publisher pubIcpKeyFrames;
  56. ros::Publisher pubRecentKeyFrames;
  57. ros::Publisher pubRecentKeyFrame;
  58. ros::Publisher pubCloudRegisteredRaw;
  59. ros::Subscriber subLaserCloudInfo;
  60. ros::Subscriber subGPS;
  61. std::deque<nav_msgs::Odometry> gpsQueue;
  62. lio_sam::cloud_info cloudInfo;
  63. //历史所有关键帧的角点集合(降采样)
  64. vector<pcl::PointCloud<PointType>::Ptr> cornerCloudKeyFrames;
  65. //历史所有关键帧的平面点集合(降采样)
  66. vector<pcl::PointCloud<PointType>::Ptr> surfCloudKeyFrames;
  67. //历史关键帧位姿(位置)
  68. pcl::PointCloud<PointType>::Ptr cloudKeyPoses3D;
  69. //历史关键帧位姿
  70. pcl::PointCloud<PointTypePose>::Ptr cloudKeyPoses6D;
  71. // * pcl::PointCloud<PointType>::Ptr copy_cloudKeyPoses3D;
  72. // * pcl::PointCloud<PointTypePose>::Ptr copy_cloudKeyPoses6D;
  73. //当前激光帧角点集合
  74. pcl::PointCloud<PointType>::Ptr laserCloudCornerLast; // corner feature set from odoOptimization
  75. //当前激光帧平面点集合
  76. pcl::PointCloud<PointType>::Ptr laserCloudSurfLast; // surf feature set from odoOptimization
  77. //当前激光帧角点集合,降采样 DS:down sample
  78. pcl::PointCloud<PointType>::Ptr laserCloudCornerLastDS; // downsampled corner featuer set from odoOptimization
  79. //当前降采样平面点集合
  80. pcl::PointCloud<PointType>::Ptr laserCloudSurfLastDS; // downsampled surf featuer set from odoOptimization
  81. // 当前帧与局部map匹配上的角点,平面点加入同一集合;后面是对应的参数
  82. pcl::PointCloud<PointType>::Ptr laserCloudOri;
  83. pcl::PointCloud<PointType>::Ptr coeffSel;
  84. // 当前帧与局部map匹配上的角点、参数、标记;
  85. std::vector<PointType> laserCloudOriCornerVec; // corner point holder for parallel computation
  86. std::vector<PointType> coeffSelCornerVec;
  87. std::vector<bool> laserCloudOriCornerFlag;
  88. // 当前帧与局部map匹配上的平面点、参数、标记
  89. std::vector<PointType> laserCloudOriSurfVec; // surfstampMapDS;
  90. std::vector<PointType> coeffSelSurfVec;
  91. std::vector<bool> laserCloudOriSurfFlag;
  92. // 局部角点集合
  93. pcl::PointCloud<PointType>::Ptr laserCloudCornerFromMap;
  94. // 局部平面点集合
  95. pcl::PointCloud<PointType>::Ptr laserCloudSurfFromMap;
  96. // 局部map角点集合,降采样
  97. pcl::PointCloud<PointType>::Ptr laserCloudCornerFromMapDS;
  98. // 局部map平面点集合,降采样
  99. pcl::PointCloud<PointType>::Ptr laserCloudSurfFromMapDS;
  100. // 局部关键帧构建的map点云,对应kd-tree,用于scan-to-map找相邻点
  101. pcl::KdTreeFLANN<PointType>::Ptr kdtreeCornerFromMap;
  102. pcl::KdTreeFLANN<PointType>::Ptr kdtreeSurfFromMap;
  103. pcl::KdTreeFLANN<PointType>::Ptr kdtreeSurroundingKeyPoses;
  104. pcl::KdTreeFLANN<PointType>::Ptr kdtreeHistoryKeyPoses;
  105. pcl::PointCloud<PointType>::Ptr latestKeyFrameCloud;
  106. pcl::PointCloud<PointType>::Ptr nearHistoryKeyFrameCloud;
  107. // 降采样
  108. pcl::VoxelGrid<PointType> downSizeFilterCorner;
  109. pcl::VoxelGrid<PointType> downSizeFilterSurf;
  110. pcl::VoxelGrid<PointType> downSizeFilterICP;
  111. pcl::VoxelGrid<PointType> downSizeFilterSurroundingKeyPoses; // for surrounding key poses of scan-to-map optimization
  112. ros::Time timeLaserInfoStamp;
  113. double timeLaserCloudInfoLast;
  114. float transformTobeMapped[6];
  115. std::mutex mtx;
  116. double timeLastProcessing = -1;
  117. // * std::mutex mtxLoopInfo
  118. bool isDegenerate = false; //退化
  119. Eigen::Matrix<float, 6, 6> matP;
  120. // 局部map降采样角点数量
  121. int laserCloudCornerFromMapDSNum = 0;
  122. // 局部map降采样平面点数量
  123. int laserCloudSurfFromMapDSNum = 0;
  124. // 当前激光帧降采样角点数量
  125. int laserCloudCornerLastDSNum = 0;
  126. // 当前激光帧降采样平面点数量
  127. int laserCloudSurfLastDSNum = 0;
  128. bool aLoopIsClosed = false;
  129. int imuPreintegrationResetId = 0;
  130. // * vector<pair<int, int>> loopIndexQueue;
  131. // * vector<gtsam::Pose3> loopPoseQueue;
  132. // * vector<gtsam::noiseModel::Diagonal::shared_ptr> loopNoiseQueue;
  133. // * deque<std_msgs::Float64MultiArray> loopInfoVec;
  134. nav_msgs::Path globalPath;
  135. // 当前帧位姿
  136. Eigen::Affine3f transPointAssociateToMap;
  137. // 前一帧位姿
  138. // * Eigen::Affine3f incrementalOdometryAffineFront;
  139. // 当前帧位姿
  140. // * Eigen::Affine3f incrementalOdometryAffineBack;
  141. mapOptimization()
  142. {
  143. // ISM2参数
  144. ISAM2Params parameters;
  145. parameters.relinearizeThreshold = 0.1;
  146. parameters.relinearizeSkip = 1;
  147. isam = new ISAM2(parameters);
  148. // 发布历史关键帧里程计
  149. pubKeyPoses = nh.advertise<sensor_msgs::PointCloud2>("lio_sam/mapping/trajectory", 1);
  150. // 发布局部关键帧map的特征点云
  151. pubLaserCloudSurround = nh.advertise<sensor_msgs::PointCloud2>("lio_sam/mapping/map_global", 1);
  152. // 发布激光里程计,rviz中表现为坐标轴
  153. // pubLaserOdometryGlobal = nh.advertise<nav_msgs::Odometry> ("lio_sam/mapping/odometry", 1);
  154. // 发布激光里程计,它与上面的激光里程计基本一样,只是roll、pitch用imu数据加权平均了一下,z做了限制
  155. // pubLaserOdometryIncremental = nh.advertise<nav_msgs::Odometry> ("lio_sam/mapping/odometry_incremental", 1);
  156. pubOdomAftMappedROS = nh.advertise<nav_msgs::Odometry> ("lio_sam/mapping/odometry", 1);
  157. // 发布激光里程及路径,rviz中表现为小车的运行轨迹
  158. pubPath = nh.advertise<nav_msgs::Path>("lio_sam/mapping/path", 1);
  159. subLaserCloudInfo = nh.subscribe<lio_sam::cloud_info>("lio_sam/feature/cloud_info", 10, &mapOptimization::laserCloudInfoHandler, this, ros::TransportHints().tcpNoDelay());
  160. // 订阅gps里程计
  161. subGPS = nh.subscribe<nav_msgs::Odometry> (gpsTopic, 200, &mapOptimization::gpsHandler, this, ros::TransportHints().tcpNoDelay());
  162. // 订阅来自外部闭环检测程序提供的闭环数据,本程序没有提供,这里实际没用上
  163. // subLoop = nh.subscribe<std_msgs::Float64MultiArray>("lio_loop/loop_closure_detection", 1, &mapOptimization::loopInfoHandler, this, ros::TransportHints().tcpNoDelay());
  164. // 发布地图保存服务
  165. // srvSaveMap = nh.advertiseService("lio_sam/save_map", &mapOptimization::saveMapService, this);
  166. pubHistoryKeyFrames = nh.advertise<sensor_msgs::PointCloud2>("lio_sam/mapping/icp_loop_closure_history_cloud", 1);
  167. pubIcpKeyFrames = nh.advertise<sensor_msgs::PointCloud2>("lio_sam/mapping/icp_loop_closure_corrected_cloud", 1);
  168. // 发布局部map的降采样平面点集合
  169. pubRecentKeyFrames = nh.advertise<sensor_msgs::PointCloud2>("lio_sam/mapping/map_local", 1);
  170. // 发布历史帧(累加的)的角点、平面点降采样集合
  171. pubRecentKeyFrame = nh.advertise<sensor_msgs::PointCloud2>("lio_sam/mapping/cloud_registered", 1);
  172. // 发布当前帧原始点云配准之后的点云
  173. pubCloudRegisteredRaw = nh.advertise<sensor_msgs::PointCloud2>("lio_sam/mapping/cloud_registered_raw", 1);
  174. downSizeFilterCorner.setLeafSize(mappingCornerLeafSize, mappingCornerLeafSize, mappingCornerLeafSize);
  175. downSizeFilterSurf.setLeafSize(mappingSurfLeafSize, mappingSurfLeafSize, mappingSurfLeafSize);
  176. downSizeFilterICP.setLeafSize(mappingSurfLeafSize, mappingSurfLeafSize, mappingSurfLeafSize);
  177. downSizeFilterSurroundingKeyPoses.setLeafSize(surroundingKeyframeDensity, surroundingKeyframeDensity, surroundingKeyframeDensity); // for surrounding key poses of scan-to-map optimization
  178. allocateMemory();
  179. }
  180. // 内存分配
  181. void allocateMemory()
  182. {
  183. cloudKeyPoses3D.reset(new pcl::PointCloud<PointType>());
  184. cloudKeyPoses6D.reset(new pcl::PointCloud<PointTypePose>());
  185. kdtreeSurroundingKeyPoses.reset(new pcl::KdTreeFLANN<PointType>());
  186. kdtreeHistoryKeyPoses.reset(new pcl::KdTreeFLANN<PointType>());
  187. laserCloudCornerLast.reset(new pcl::PointCloud<PointType>()); // corner feature set from odoOptimization
  188. laserCloudSurfLast.reset(new pcl::PointCloud<PointType>()); // surf feature set from odoOptimization
  189. laserCloudCornerLastDS.reset(new pcl::PointCloud<PointType>()); // downsampled corner featuer set from odoOptimization
  190. laserCloudSurfLastDS.reset(new pcl::PointCloud<PointType>()); // downsampled surf featuer set from odoOptimization
  191. laserCloudOri.reset(new pcl::PointCloud<PointType>());
  192. coeffSel.reset(new pcl::PointCloud<PointType>());
  193. laserCloudOriCornerVec.resize(N_SCAN * Horizon_SCAN);
  194. coeffSelCornerVec.resize(N_SCAN * Horizon_SCAN);
  195. laserCloudOriCornerFlag.resize(N_SCAN * Horizon_SCAN);
  196. laserCloudOriSurfVec.resize(N_SCAN * Horizon_SCAN);
  197. coeffSelSurfVec.resize(N_SCAN * Horizon_SCAN);
  198. laserCloudOriSurfFlag.resize(N_SCAN * Horizon_SCAN);
  199. std::fill(laserCloudOriCornerFlag.begin(), laserCloudOriCornerFlag.end(), false);
  200. std::fill(laserCloudOriSurfFlag.begin(), laserCloudOriSurfFlag.end(), false);
  201. laserCloudCornerFromMap.reset(new pcl::PointCloud<PointType>());
  202. laserCloudSurfFromMap.reset(new pcl::PointCloud<PointType>());
  203. laserCloudCornerFromMapDS.reset(new pcl::PointCloud<PointType>());
  204. laserCloudSurfFromMapDS.reset(new pcl::PointCloud<PointType>());
  205. kdtreeCornerFromMap.reset(new pcl::KdTreeFLANN<PointType>());
  206. kdtreeSurfFromMap.reset(new pcl::KdTreeFLANN<PointType>());
  207. latestKeyFrameCloud.reset(new pcl::PointCloud<PointType>());
  208. nearHistoryKeyFrameCloud.reset(new pcl::PointCloud<PointType>());
  209. for (int i = 0; i < 6; ++i){
  210. transformTobeMapped[i] = 0;
  211. }
  212. matP.setZero();
  213. }
  214. void laserCloudInfoHandler(const lio_sam::cloud_infoConstPtr& msgIn)
  215. {
  216. // extract time stamp
  217. //获取当前激光帧时间戳
  218. timeLaserInfoStamp = msgIn->header.stamp;
  219. timeLaserCloudInfoLast = msgIn->header.stamp.toSec();
  220. // extract info and feature cloud
  221. // 提取来自featureExtraction的当前激光帧角点、平面点集合
  222. cloudInfo = *msgIn;
  223. pcl::fromROSMsg(msgIn->cloud_corner, *laserCloudCornerLast); //将ros的角点点云消息数据转换成pcl数据
  224. pcl::fromROSMsg(msgIn->cloud_surface, *laserCloudSurfLast); //将ros的平面点点云消息转换成pcl数据
  225. std::lock_guard<std::mutex> lock(mtx);
  226. // mapping执行频率控制
  227. // static double timeLastProcessing = -1;
  228. if (timeLaserCloudInfoLast - timeLastProcessing >= mappingProcessInterval) {//gc:control the rate of mapping process
  229. timeLastProcessing = timeLaserCloudInfoLast;
  230. // 当前帧位姿初始化
  231. // 1、如果是第一帧,用原始imu数据的RPY初始化当前帧位姿(旋转部分)
  232. // 2、后续帧,用imu里程计计算两帧之间的增量位姿变换,作用于前一帧的激光位姿,得到当前帧激光初始位姿
  233. updateInitialGuess();//gc: update initial value for states
  234. // 获得当前帧的点云,降采样角点和平面点
  235. downsampleCurrentScan();//gc:down sample the current corner points and surface points
  236. // 然后和全局地图去匹配(scan to map),得到当前帧的一个更好的位姿。
  237. scan2MapOptimization();//gc: calculate the tranformtion using lidar measurement with the Imu preintegration as initial values
  238. //and then interpolate roll and pitch angle using IMU measurement and above measurement
  239. //但不可能拿全局地图来匹配,所以选取和当前帧比较近的点云,获取局部点云图
  240. extractSurroundingKeyFrames();//gc:
  241. //但上述匹配仍不够完美,需要一个后端融合所有信息获取真正准确的位姿。设置当前帧为关键帧,并执行因子图优化
  242. saveKeyFramesAndFactor();//gc: save corner cloud and surface cloud of this scan, and add odom and GPS factors
  243. //随后更新所有的状态量,即更新因子图中所有变量节点的位姿,也就是所有历史关键帧的位姿,更新里程计轨迹
  244. correctPoses();
  245. //最后把结果给下游模块发布
  246. publishOdometry();
  247. // 发布里程计、点云、轨迹
  248. // 1、发布历史关键帧位姿集合
  249. // 2、发布局部map的降采样平面点集合
  250. // 3、发布历史帧(累加的)的角点、平面点降采样集合
  251. // 4、发布里程计轨迹
  252. publishFrames();
  253. }
  254. }
  255. void gpsHandler(const nav_msgs::Odometry::ConstPtr& gpsMsg)
  256. {
  257. gpsQueue.push_back(*gpsMsg);
  258. }
  259. // 根据当前帧位姿,变换到世界坐标系(map)下
  260. void pointAssociateToMap(PointType const * const pi, PointType * const po)
  261. {
  262. po->x = transPointAssociateToMap(0,0) * pi->x + transPointAssociateToMap(0,1) * pi->y + transPointAssociateToMap(0,2) * pi->z + transPointAssociateToMap(0,3);
  263. po->y = transPointAssociateToMap(1,0) * pi->x + transPointAssociateToMap(1,1) * pi->y + transPointAssociateToMap(1,2) * pi->z + transPointAssociateToMap(1,3);
  264. po->z = transPointAssociateToMap(2,0) * pi->x + transPointAssociateToMap(2,1) * pi->y + transPointAssociateToMap(2,2) * pi->z + transPointAssociateToMap(2,3);
  265. po->intensity = pi->intensity;
  266. }
  267. pcl::PointCloud<PointType>::Ptr transformPointCloud(pcl::PointCloud<PointType>::Ptr cloudIn, PointTypePose* transformIn) //输入两个形参,分别为点云和变换,返回变换到世界坐标系下的点
  268. {
  269. pcl::PointCloud<PointType>::Ptr cloudOut(new pcl::PointCloud<PointType>());
  270. PointType *pointFrom;
  271. int cloudSize = cloudIn->size();
  272. cloudOut->resize(cloudSize);
  273. Eigen::Affine3f transCur = pcl::getTransformation(transformIn->x, transformIn->y, transformIn->z, transformIn->roll, transformIn->pitch, transformIn->yaw);
  274. for (int i = 0; i < cloudSize; ++i){
  275. pointFrom = &cloudIn->points[i];
  276. cloudOut->points[i].x = transCur(0,0) * pointFrom->x + transCur(0,1) * pointFrom->y + transCur(0,2) * pointFrom->z + transCur(0,3);
  277. cloudOut->points[i].y = transCur(1,0) * pointFrom->x + transCur(1,1) * pointFrom->y + transCur(1,2) * pointFrom->z + transCur(1,3);
  278. cloudOut->points[i].z = transCur(2,0) * pointFrom->x + transCur(2,1) * pointFrom->y + transCur(2,2) * pointFrom->z + transCur(2,3);
  279. cloudOut->points[i].intensity = pointFrom->intensity;
  280. }
  281. return cloudOut;
  282. }
  283. gtsam::Pose3 pclPointTogtsamPose3(PointTypePose thisPoint)
  284. {
  285. return gtsam::Pose3(gtsam::Rot3::RzRyRx(double(thisPoint.roll), double(thisPoint.pitch), double(thisPoint.yaw)),
  286. gtsam::Point3(double(thisPoint.x), double(thisPoint.y), double(thisPoint.z)));
  287. }
  288. gtsam::Pose3 trans2gtsamPose(float transformIn[])
  289. {
  290. return gtsam::Pose3(gtsam::Rot3::RzRyRx(transformIn[0], transformIn[1], transformIn[2]),
  291. gtsam::Point3(transformIn[3], transformIn[4], transformIn[5]));
  292. }
  293. Eigen::Affine3f pclPointToAffine3f(PointTypePose thisPoint)
  294. {
  295. return pcl::getTransformation(thisPoint.x, thisPoint.y, thisPoint.z, thisPoint.roll, thisPoint.pitch, thisPoint.yaw);
  296. }
  297. // Eigen格式的位姿变换
  298. Eigen::Affine3f trans2Affine3f(float transformIn[])
  299. {
  300. return pcl::getTransformation(transformIn[3], transformIn[4], transformIn[5], transformIn[0], transformIn[1], transformIn[2]);
  301. }
  302. PointTypePose trans2PointTypePose(float transformIn[])
  303. {
  304. PointTypePose thisPose6D;
  305. thisPose6D.x = transformIn[3];
  306. thisPose6D.y = transformIn[4];
  307. thisPose6D.z = transformIn[5];
  308. thisPose6D.roll = transformIn[0];
  309. thisPose6D.pitch = transformIn[1];
  310. thisPose6D.yaw = transformIn[2];
  311. return thisPose6D;
  312. }
  313. void visualizeGlobalMapThread()
  314. {
  315. ros::Rate rate(0.2);
  316. while (ros::ok()){
  317. rate.sleep();
  318. publishGlobalMap();
  319. }
  320. if (savePCD == false)
  321. return;
  322. cout << "****************************************************" << endl;
  323. cout << "Saving map to pcd files ..." << endl;
  324. // create directory and remove old files;
  325. savePCDDirectory = std::getenv("HOME") + savePCDDirectory;
  326. int unused = system((std::string("exec rm -r ") + savePCDDirectory).c_str());
  327. unused = system((std::string("mkdir ") + savePCDDirectory).c_str());
  328. // save key frame transformations
  329. pcl::io::savePCDFileASCII(savePCDDirectory + "trajectory.pcd", *cloudKeyPoses3D);
  330. pcl::io::savePCDFileASCII(savePCDDirectory + "transformations.pcd", *cloudKeyPoses6D);
  331. // extract global point cloud map
  332. //提取历史关键帧角点平面点集合
  333. pcl::PointCloud<PointType>::Ptr globalCornerCloud(new pcl::PointCloud<PointType>());
  334. pcl::PointCloud<PointType>::Ptr globalCornerCloudDS(new pcl::PointCloud<PointType>());
  335. pcl::PointCloud<PointType>::Ptr globalSurfCloud(new pcl::PointCloud<PointType>());
  336. pcl::PointCloud<PointType>::Ptr globalSurfCloudDS(new pcl::PointCloud<PointType>());
  337. pcl::PointCloud<PointType>::Ptr globalMapCloud(new pcl::PointCloud<PointType>());
  338. for (int i = 0; i < (int)cloudKeyPoses3D->size(); i++) {
  339. *globalCornerCloud += *transformPointCloud(cornerCloudKeyFrames[i], &cloudKeyPoses6D->points[i]);
  340. *globalSurfCloud += *transformPointCloud(surfCloudKeyFrames[i], &cloudKeyPoses6D->points[i]);
  341. cout << "\r" << std::flush << "Processing feature cloud " << i << " of " << cloudKeyPoses6D->size() << " ...";
  342. }
  343. // down-sample and save corner cloud
  344. downSizeFilterCorner.setInputCloud(globalCornerCloud);
  345. downSizeFilterCorner.filter(*globalCornerCloudDS);
  346. pcl::io::savePCDFileASCII(savePCDDirectory + "cloudCorner.pcd", *globalCornerCloudDS);
  347. // down-sample and save surf cloud
  348. downSizeFilterSurf.setInputCloud(globalSurfCloud);
  349. downSizeFilterSurf.filter(*globalSurfCloudDS);
  350. pcl::io::savePCDFileASCII(savePCDDirectory + "cloudSurf.pcd", *globalSurfCloudDS);
  351. // down-sample and save global point cloud map
  352. *globalMapCloud += *globalCornerCloud;
  353. *globalMapCloud += *globalSurfCloud;
  354. pcl::io::savePCDFileASCII(savePCDDirectory + "cloudGlobal.pcd", *globalMapCloud);
  355. cout << "****************************************************" << endl;
  356. cout << "Saving map to pcd files completed" << endl;
  357. }
  358. void publishGlobalMap()
  359. {
  360. if (pubLaserCloudSurround.getNumSubscribers() == 0)
  361. return;
  362. if (cloudKeyPoses3D->points.empty() == true)
  363. return;
  364. pcl::KdTreeFLANN<PointType>::Ptr kdtreeGlobalMap(new pcl::KdTreeFLANN<PointType>());;
  365. pcl::PointCloud<PointType>::Ptr globalMapKeyPoses(new pcl::PointCloud<PointType>());
  366. pcl::PointCloud<PointType>::Ptr globalMapKeyPosesDS(new pcl::PointCloud<PointType>());
  367. pcl::PointCloud<PointType>::Ptr globalMapKeyFrames(new pcl::PointCloud<PointType>());
  368. pcl::PointCloud<PointType>::Ptr globalMapKeyFramesDS(new pcl::PointCloud<PointType>());
  369. // kd-tree to find near key frames to visualize
  370. // kdtree查找最近一帧关键帧相邻的关键帧集合
  371. std::vector<int> pointSearchIndGlobalMap;
  372. std::vector<float> pointSearchSqDisGlobalMap;
  373. // search near key frames to visualize
  374. mtx.lock();
  375. kdtreeGlobalMap->setInputCloud(cloudKeyPoses3D);
  376. kdtreeGlobalMap->radiusSearch(cloudKeyPoses3D->back(), globalMapVisualizationSearchRadius, pointSearchIndGlobalMap, pointSearchSqDisGlobalMap, 0);
  377. mtx.unlock();
  378. for (int i = 0; i < (int)pointSearchIndGlobalMap.size(); ++i)
  379. globalMapKeyPoses->push_back(cloudKeyPoses3D->points[pointSearchIndGlobalMap[i]]);
  380. // downsample near selected key frames
  381. pcl::VoxelGrid<PointType> downSizeFilterGlobalMapKeyPoses; // for global map visualization
  382. // 降采样全局地图关键帧
  383. downSizeFilterGlobalMapKeyPoses.setLeafSize(globalMapVisualizationPoseDensity, globalMapVisualizationPoseDensity, globalMapVisualizationPoseDensity); // for global map visualization
  384. downSizeFilterGlobalMapKeyPoses.setInputCloud(globalMapKeyPoses);
  385. downSizeFilterGlobalMapKeyPoses.filter(*globalMapKeyPosesDS);
  386. // extract visualized and downsampled key frames
  387. // 提取局部关键帧特征点云
  388. for (int i = 0; i < (int)globalMapKeyPosesDS->size(); ++i){
  389. // 剔除过远点
  390. if (pointDistance(globalMapKeyPosesDS->points[i], cloudKeyPoses3D->back()) > globalMapVisualizationSearchRadius)
  391. continue;
  392. int thisKeyInd = (int)globalMapKeyPosesDS->points[i].intensity;
  393. *globalMapKeyFrames += *transformPointCloud(cornerCloudKeyFrames[thisKeyInd], &cloudKeyPoses6D->points[thisKeyInd]);
  394. *globalMapKeyFrames += *transformPointCloud(surfCloudKeyFrames[thisKeyInd], &cloudKeyPoses6D->points[thisKeyInd]);
  395. }
  396. // downsample visualized points
  397. // 体素降采样,发布
  398. pcl::VoxelGrid<PointType> downSizeFilterGlobalMapKeyFrames; // for global map visualization
  399. downSizeFilterGlobalMapKeyFrames.setLeafSize(globalMapVisualizationLeafSize, globalMapVisualizationLeafSize, globalMapVisualizationLeafSize); // for global map visualization
  400. downSizeFilterGlobalMapKeyFrames.setInputCloud(globalMapKeyFrames);
  401. downSizeFilterGlobalMapKeyFrames.filter(*globalMapKeyFramesDS);
  402. publishCloud(&pubLaserCloudSurround, globalMapKeyFramesDS, timeLaserInfoStamp, "odom");
  403. }
  404. void loopClosureThread()
  405. {
  406. if (loopClosureEnableFlag == false)
  407. return;
  408. ros::Rate rate(0.1);
  409. while (ros::ok())
  410. {
  411. rate.sleep();
  412. performLoopClosure();
  413. }
  414. }
  415. bool detectLoopClosure(int *latestID, int *closestID)
  416. {
  417. int latestFrameIDLoopCloure;
  418. int closestHistoryFrameID;
  419. latestKeyFrameCloud->clear(); //当前帧点云集合
  420. nearHistoryKeyFrameCloud->clear(); //回环帧点云集合
  421. std::lock_guard<std::mutex> lock(mtx);
  422. // find the closest history key frame
  423. std::vector<int> pointSearchIndLoop;
  424. std::vector<float> pointSearchSqDisLoop;
  425. kdtreeHistoryKeyPoses->setInputCloud(cloudKeyPoses3D);
  426. //gc:search the last keyframe's nearest keyframes,输入:索要查询的点,搜索距离 输出:搜索完领域内对应点索引、欧式距离,0表示返回全部查询结果
  427. kdtreeHistoryKeyPoses->radiusSearch(cloudKeyPoses3D->back(), historyKeyframeSearchRadius, pointSearchIndLoop, pointSearchSqDisLoop, 0);
  428. closestHistoryFrameID = -1;
  429. for (int i = 0; i < (int)pointSearchIndLoop.size(); ++i)
  430. {
  431. int id = pointSearchIndLoop[i];
  432. //gc: find the nearest keyframe whose time is not close
  433. if (abs(cloudKeyPoses6D->points[id].time - timeLaserCloudInfoLast) > historyKeyframeSearchTimeDiff)
  434. {
  435. closestHistoryFrameID = id;
  436. break; //结束整个for循环
  437. }
  438. }
  439. if (closestHistoryFrameID == -1)
  440. return false;
  441. //gc: if the closest keyframe is 当前帧
  442. if ((int)cloudKeyPoses3D->size() - 1 == closestHistoryFrameID)
  443. return false;
  444. // save latest key frames 将当前帧保存进最新关键帧集合
  445. latestFrameIDLoopCloure = cloudKeyPoses3D->size() - 1;
  446. *latestKeyFrameCloud += *transformPointCloud(cornerCloudKeyFrames[latestFrameIDLoopCloure], &cloudKeyPoses6D->points[latestFrameIDLoopCloure]);
  447. *latestKeyFrameCloud += *transformPointCloud(surfCloudKeyFrames[latestFrameIDLoopCloure], &cloudKeyPoses6D->points[latestFrameIDLoopCloure]);
  448. // save history near key frames
  449. //gc: combine the time_close frame of the nearset frame
  450. // 提取key索引的关键帧前后相邻若干帧的关键帧特征点集合
  451. bool nearFrameAvailable = false;
  452. for (int j = -historyKeyframeSearchNum; j <= historyKeyframeSearchNum; ++j)
  453. {
  454. // 防止回环帧过早或者过晚
  455. if (closestHistoryFrameID + j < 0 || closestHistoryFrameID + j > latestFrameIDLoopCloure)
  456. continue;
  457. *nearHistoryKeyFrameCloud += *transformPointCloud(cornerCloudKeyFrames[closestHistoryFrameID+j], &cloudKeyPoses6D->points[closestHistoryFrameID+j]);
  458. *nearHistoryKeyFrameCloud += *transformPointCloud(surfCloudKeyFrames[closestHistoryFrameID+j], &cloudKeyPoses6D->points[closestHistoryFrameID+j]);
  459. nearFrameAvailable = true;
  460. }
  461. if (nearFrameAvailable == false)
  462. return false;
  463. // 找到两个回环帧的id后
  464. *latestID = latestFrameIDLoopCloure;
  465. *closestID = closestHistoryFrameID;
  466. return true;
  467. }
  468. void performLoopClosure()
  469. {
  470. // 找到两帧回环帧点云后,开始icp匹配
  471. if (cloudKeyPoses3D->points.empty() == true)
  472. return;
  473. int latestFrameIDLoopCloure;
  474. int closestHistoryFrameID;
  475. if (detectLoopClosure(&latestFrameIDLoopCloure, &closestHistoryFrameID) == false)
  476. return;
  477. // ICP Settings
  478. // ICP参数设置
  479. pcl::IterativeClosestPoint<PointType, PointType> icp; //创建icp实例类
  480. icp.setMaxCorrespondenceDistance(100); //设置对应点对之间的最大距离(此值对配准结果影响较大)。
  481. icp.setMaximumIterations(100); //最大迭代次数
  482. icp.setTransformationEpsilon(1e-6); // 设置两次变化矩阵之间的差值(一般设置为1e-10即可);
  483. icp.setEuclideanFitnessEpsilon(1e-6); //设置收敛条件是均方误差和小于阈值, 停止迭代;
  484. icp.setRANSACIterations(0);
  485. // Downsample map cloud
  486. pcl::PointCloud<PointType>::Ptr cloud_temp(new pcl::PointCloud<PointType>());
  487. downSizeFilterICP.setInputCloud(nearHistoryKeyFrameCloud); //回环帧点云降采样
  488. downSizeFilterICP.filter(*cloud_temp);
  489. *nearHistoryKeyFrameCloud = *cloud_temp;
  490. // publish history near key frames
  491. publishCloud(&pubHistoryKeyFrames, nearHistoryKeyFrameCloud, timeLaserInfoStamp, "odom");
  492. // Align clouds
  493. //icp.setin
  494. icp.setInputSource(latestKeyFrameCloud);
  495. icp.setInputTarget(nearHistoryKeyFrameCloud);
  496. pcl::PointCloud<PointType>::Ptr unused_result(new pcl::PointCloud<PointType>());
  497. icp.align(*unused_result); //经过配准后的点云,保存经过icp算法应用过后的结果
  498. // std::cout << "ICP converg flag:" << icp.hasConverged() << ". Fitness score: " << icp.getFitnessScore() << std::endl;
  499. // icp输入和目标点云对齐正确,hasConverged为true
  500. // getFitnessScore用来表示两份点云之间的误差,小于阈值表示匹配成功
  501. if (icp.hasConverged() == false || icp.getFitnessScore() > historyKeyframeFitnessScore)
  502. return;
  503. // publish corrected cloud
  504. // getNumSubscribers判断订阅者是否连接
  505. if (pubIcpKeyFrames.getNumSubscribers() != 0){
  506. pcl::PointCloud<PointType>::Ptr closed_cloud(new pcl::PointCloud<PointType>());
  507. // 输入点云,输出点云,变换矩阵
  508. pcl::transformPointCloud(*latestKeyFrameCloud, *closed_cloud, icp.getFinalTransformation());
  509. // 发布当前帧经过回环优化后的位姿变换的点云
  510. publishCloud(&pubIcpKeyFrames, closed_cloud, timeLaserInfoStamp, "odom");
  511. }
  512. // 匹配成功的话,更新位姿
  513. // Get pose transformation
  514. // 闭环优化得到的当前关键帧
  515. float x, y, z, roll, pitch, yaw;
  516. Eigen::Affine3f correctionLidarFrame;
  517. //gc: realtive transformation
  518. // 闭环关键帧之间的位姿变换
  519. correctionLidarFrame = icp.getFinalTransformation();
  520. // transform from world origin to wrong pose
  521. // 闭环优化前当前帧位姿
  522. Eigen::Affine3f tWrong = pclPointToAffine3f(cloudKeyPoses6D->points[latestFrameIDLoopCloure]);
  523. // transform from world origin to corrected pose
  524. // 闭环优化后当前帧位姿
  525. Eigen::Affine3f tCorrect = correctionLidarFrame * tWrong;// pre-multiplying -> successive rotation about a fixed frame
  526. pcl::getTranslationAndEulerAngles (tCorrect, x, y, z, roll, pitch, yaw);
  527. gtsam::Pose3 poseFrom = Pose3(Rot3::RzRyRx(roll, pitch, yaw), Point3(x, y, z));
  528. // 闭环匹配帧的位姿
  529. gtsam::Pose3 poseTo = pclPointTogtsamPose3(cloudKeyPoses6D->points[closestHistoryFrameID]);
  530. gtsam::Vector Vector6(6);
  531. // 误差
  532. float noiseScore = icp.getFitnessScore();//gc: noise right??
  533. Vector6 << noiseScore, noiseScore, noiseScore, noiseScore, noiseScore, noiseScore;
  534. // 使用icp的得分作为他们的约束噪声项
  535. noiseModel::Diagonal::shared_ptr constraintNoise = noiseModel::Diagonal::Variances(Vector6);
  536. // Add pose constraint
  537. std::lock_guard<std::mutex> lock(mtx);
  538. // 添加闭环因子需要的数据
  539. gtSAMgraph.add(BetweenFactor<Pose3>(latestFrameIDLoopCloure, closestHistoryFrameID, poseFrom.between(poseTo), constraintNoise));
  540. isam->update(gtSAMgraph);
  541. isam->update();
  542. gtSAMgraph.resize(0);
  543. isamCurrentEstimate = isam->calculateEstimate();
  544. Pose3 latestEstimate = isamCurrentEstimate.at<Pose3>(isamCurrentEstimate.size()-1);
  545. transformTobeMapped[0] = latestEstimate.rotation().roll();
  546. transformTobeMapped[1] = latestEstimate.rotation().pitch();
  547. transformTobeMapped[2] = latestEstimate.rotation().yaw();
  548. transformTobeMapped[3] = latestEstimate.translation().x();
  549. transformTobeMapped[4] = latestEstimate.translation().y();
  550. transformTobeMapped[5] = latestEstimate.translation().z();
  551. correctPoses();
  552. aLoopIsClosed = true;
  553. }
  554. // 当前帧位姿初始化
  555. // 2、后续帧,用imu里程计计算两帧之间的增量位姿变换,作用于前一帧的激光位姿,得到当前帧激光初始位姿
  556. void updateInitialGuess()
  557. {
  558. // save current transformation before any processing
  559. // 前一帧的位姿,注:这里指lidar的位姿,后面都简写成位姿
  560. // incrementalOdometryAffineFront = trans2Affine3f(transformTobeMapped);
  561. // 前一帧的初始化姿态角(来自cloudInfo的IMU数据)
  562. static Eigen::Affine3f lastImuTransformation;//gc: note that this is static type
  563. // initialization
  564. // 如果是第一帧,用原始imu数据的RPY初始化当前帧位姿(旋转部分)
  565. if (cloudKeyPoses3D->points.empty())//gc: there is no key pose 初始化
  566. {
  567. transformTobeMapped[0] = cloudInfo.imuRollInit;
  568. transformTobeMapped[1] = cloudInfo.imuPitchInit;
  569. transformTobeMapped[2] = cloudInfo.imuYawInit;
  570. if (!useImuHeadingInitialization)//gc: if not use the heading of init_IMU as Initialization //GPS
  571. transformTobeMapped[2] = 0;
  572. lastImuTransformation = pcl::getTransformation(0, 0, 0, cloudInfo.imuRollInit, cloudInfo.imuPitchInit, cloudInfo.imuYawInit); // save imu before return;
  573. return;
  574. }
  575. // 用当前帧和前一帧对应的imu里程计计算相对位姿变换,再用前一帧的位姿与相对变换,计算当前帧的位姿,存transformTobeMapped
  576. // * static bool lastImuPreTransAvailable = false;
  577. // * static Eigen::Affine3f lastImuPreTransformation;
  578. //odomAvailable和imuAvailable均来源于imageProjection.cpp中赋值,
  579. //imuAvailable是遍历激光帧前后起止时刻0.01s之内的imu数据,
  580. //如果都没有那就是false,因为imu频率一般比激光帧快,因此这里应该是都有的。
  581. //odomAvailable同理,是监听imu里程计的位姿,如果没有紧挨着激光帧的imu里程计数据,那么就是false;
  582. //这俩应该一般都有
  583. // use imu pre-integration estimation for pose guess
  584. //
  585. if (cloudInfo.odomAvailable == true && cloudInfo.imuPreintegrationResetId == imuPreintegrationResetId) //该段代码与原文相差较大
  586. {
  587. // cloudInfo来自featureExtraction.cpp发布的liox-sam/feature/cloud_info
  588. // 直接使用imu里程计数据
  589. transformTobeMapped[0] = cloudInfo.initialGuessRoll;
  590. transformTobeMapped[1] = cloudInfo.initialGuessPitch;
  591. transformTobeMapped[2] = cloudInfo.initialGuessYaw;
  592. transformTobeMapped[3] = cloudInfo.initialGuessX;
  593. transformTobeMapped[4] = cloudInfo.initialGuessY;
  594. transformTobeMapped[5] = cloudInfo.initialGuessZ;
  595. lastImuTransformation = pcl::getTransformation(0, 0, 0, cloudInfo.imuRollInit, cloudInfo.imuPitchInit, cloudInfo.imuYawInit); // save imu before return;
  596. return;
  597. }
  598. // use previous pose for pose guess
  599. // if (cloudKeyPoses6D->points.size() >= 2)
  600. // {
  601. // int oldId = cloudKeyPoses6D->points.size() - 2;
  602. // int preId = cloudKeyPoses6D->points.size() - 1;
  603. // Eigen::Affine3f transOld = pclPointToAffine3f(cloudKeyPoses6D->points[oldId]);
  604. // Eigen::Affine3f transPre = pclPointToAffine3f(cloudKeyPoses6D->points[preId]);
  605. // double deltaTimePre = cloudKeyPoses6D->points[preId].time - cloudKeyPoses6D->points[oldId].time;
  606. // double deltaTimeNow = timeLaserCloudInfoLast - cloudKeyPoses6D->points[preId].time;
  607. // double alpha = deltaTimeNow / deltaTimePre;
  608. // Eigen::Affine3f transIncPre = transOld.inverse() * transPre;
  609. // float x, y, z, roll, pitch, yaw;
  610. // pcl::getTranslationAndEulerAngles (transIncPre, x, y, z, roll, pitch, yaw);
  611. // Eigen::Affine3f transIncNow = pcl::getTransformation(alpha*x, alpha*y, alpha*z, alpha*roll, alpha*pitch, alpha*yaw);
  612. // Eigen::Affine3f transTobe = trans2Affine3f(transformTobeMapped);
  613. // Eigen::Affine3f transFinal = transTobe * transIncNow;
  614. // pcl::getTranslationAndEulerAngles(transFinal, transformTobeMapped[3], transformTobeMapped[4], transformTobeMapped[5],
  615. // transformTobeMapped[0], transformTobeMapped[1], transformTobeMapped[2]);
  616. // lastImuTransformation = pcl::getTransformation(0, 0, 0, cloudInfo.imuRollInit, cloudInfo.imuPitchInit, cloudInfo.imuYawInit); // save imu before return;
  617. // return;
  618. // }
  619. // use imu incremental estimation for pose guess (only rotation)
  620. // 如果是第二帧
  621. if (cloudInfo.imuAvailable == true)
  622. {
  623. // transBack是这一帧时刻的imu位姿
  624. Eigen::Affine3f transBack = pcl::getTransformation(0, 0, 0, cloudInfo.imuRollInit, cloudInfo.imuPitchInit, cloudInfo.imuYawInit);
  625. // lastImuTransFormation是最新的上一刻数据,求逆后与transback相乘才是增量
  626. Eigen::Affine3f transIncre = lastImuTransformation.inverse() * transBack;//gc: the transform of IMU between two scans
  627. //前一帧的位姿
  628. Eigen::Affine3f transTobe = trans2Affine3f(transformTobeMapped);
  629. // 当前帧姿态
  630. Eigen::Affine3f transFinal = transTobe * transIncre;
  631. //将transFinal传入,结果输出至transformTobeMapped
  632. pcl::getTranslationAndEulerAngles(transFinal, transformTobeMapped[3], transformTobeMapped[4], transformTobeMapped[5],
  633. transformTobeMapped[0], transformTobeMapped[1], transformTobeMapped[2]);
  634. lastImuTransformation = pcl::getTransformation(0, 0, 0, cloudInfo.imuRollInit, cloudInfo.imuPitchInit, cloudInfo.imuYawInit); // save imu before return;
  635. return;
  636. }
  637. }
  638. void extractForLoopClosure()
  639. {
  640. pcl::PointCloud<PointType>::Ptr cloudToExtract(new pcl::PointCloud<PointType>());
  641. int numPoses = cloudKeyPoses3D->size();
  642. for (int i = numPoses-1; i >= 0; --i)
  643. {
  644. if ((int)cloudToExtract->size() <= surroundingKeyframeSize)
  645. cloudToExtract->push_back(cloudKeyPoses3D->points[i]);
  646. else
  647. break;
  648. }
  649. extractCloud(cloudToExtract);
  650. }
  651. //gc:search nearby key poses and downsample them and then extract the local map points
  652. void extractNearby()
  653. {
  654. pcl::PointCloud<PointType>::Ptr surroundingKeyPoses(new pcl::PointCloud<PointType>());
  655. // 降采样后的关键帧
  656. pcl::PointCloud<PointType>::Ptr surroundingKeyPosesDS(new pcl::PointCloud<PointType>());//gc: the key poses after downsample
  657. std::vector<int> pointSearchInd;
  658. std::vector<float> pointSearchSqDis;
  659. // extract all the nearby key poses and downsample them
  660. // kd-tree的输入,全局关键帧位姿集合(历史所有关键帧)
  661. kdtreeSurroundingKeyPoses->setInputCloud(cloudKeyPoses3D); // create kd-tree
  662. //gc: kd-tree is used for nearby key poses
  663. // 创建kd-tree。固定半径近邻搜索,pointSearchInd返回index,pointSearchSqDis依次距离中心点距离
  664. kdtreeSurroundingKeyPoses->radiusSearch(cloudKeyPoses3D->back(), (double)surroundingKeyframeSearchRadius, pointSearchInd, pointSearchSqDis);
  665. for (int i = 0; i < (int)pointSearchInd.size(); ++i)
  666. {
  667. int id = pointSearchInd[i];
  668. surroundingKeyPoses->push_back(cloudKeyPoses3D->points[id]);
  669. }
  670. // 降采样,再滤波,存入surroundingKeyPosesDS
  671. downSizeFilterSurroundingKeyPoses.setInputCloud(surroundingKeyPoses);
  672. downSizeFilterSurroundingKeyPoses.filter(*surroundingKeyPosesDS);
  673. // also extract some latest key frames in case the robot rotates in one position
  674. // 提取一些最新的关键帧以防机器人原地旋转,即和当前帧时间近的帧
  675. int numPoses = cloudKeyPoses3D->size();
  676. // 把10s内的关键帧也加到surroundingKeyPosesDS中,注意是“也”,原先已经装了下采样的位姿(位置)
  677. for (int i = numPoses-1; i >= 0; --i)
  678. {
  679. if (timeLaserCloudInfoLast - cloudKeyPoses6D->points[i].time < 10.0)
  680. surroundingKeyPosesDS->push_back(cloudKeyPoses3D->points[i]);
  681. else
  682. break;
  683. }
  684. //对降采样后的点云提取出角点和平面点,加入局部map中,作为scan-to-map的局部点云地图
  685. extractCloud(surroundingKeyPosesDS);
  686. }
  687. //gc: extract the nearby Map points
  688. /*
  689. * 将相邻关键帧集合对应的角点、平面点,加入到局部map中,作为scan-to-map匹配的局部点云地图
  690. */
  691. void extractCloud(pcl::PointCloud<PointType>::Ptr cloudToExtract)
  692. {
  693. std::vector<pcl::PointCloud<PointType>> laserCloudCornerSurroundingVec; //新加变量,降采样后附近角点
  694. std::vector<pcl::PointCloud<PointType>> laserCloudSurfSurroundingVec;
  695. laserCloudCornerSurroundingVec.resize(cloudToExtract->size());
  696. laserCloudSurfSurroundingVec.resize(cloudToExtract->size());
  697. // extract surrounding map
  698. #pragma omp parallel for num_threads(numberOfCores) //4个线程共同执行循环
  699. for (int i = 0; i < (int)cloudToExtract->size(); ++i)
  700. {
  701. // 距离超过50m,丢弃
  702. if (pointDistance(cloudToExtract->points[i], cloudKeyPoses3D->back()) > surroundingKeyframeSearchRadius)
  703. continue;
  704. //相邻关键帧索引
  705. int thisKeyInd = (int)cloudToExtract->points[i].intensity;//gc: the index of this key frame
  706. //gc: tranform the corner points and surfpoints of the nearby keyFrames into the world frame
  707. //相邻关键帧对应的角点、平面点云,通过6D位姿变换到世界坐标系下
  708. //transformPointCloud输入的两个形参,分别为点云和变换,返回变换位姿后的点
  709. laserCloudCornerSurroundingVec[i] = *transformPointCloud(cornerCloudKeyFrames[thisKeyInd], &cloudKeyPoses6D->points[thisKeyInd]);
  710. laserCloudSurfSurroundingVec[i] = *transformPointCloud(surfCloudKeyFrames[thisKeyInd], &cloudKeyPoses6D->points[thisKeyInd]);
  711. }
  712. // fuse the map,角点面点清空
  713. laserCloudCornerFromMap->clear();
  714. laserCloudSurfFromMap->clear();
  715. // 遍历当前帧(实际是取最近的一个关键帧来找它相邻的关键帧集合)时空维度上相邻的关键帧集合
  716. for (int i = 0; i < (int)cloudToExtract->size(); ++i)
  717. {
  718. // 加入局部map
  719. *laserCloudCornerFromMap += laserCloudCornerSurroundingVec[i];
  720. *laserCloudSurfFromMap += laserCloudSurfSurroundingVec[i];
  721. }
  722. // Downsample the surrounding corner key frames (or map)
  723. // 降采样局部角点map
  724. downSizeFilterCorner.setInputCloud(laserCloudCornerFromMap);
  725. downSizeFilterCorner.filter(*laserCloudCornerFromMapDS);
  726. laserCloudCornerFromMapDSNum = laserCloudCornerFromMapDS->size();
  727. // Downsample the surrounding surf key frames (or map)
  728. // 降采样局部平面点map
  729. downSizeFilterSurf.setInputCloud(laserCloudSurfFromMap);
  730. downSizeFilterSurf.filter(*laserCloudSurfFromMapDS);
  731. laserCloudSurfFromMapDSNum = laserCloudSurfFromMapDS->size();
  732. // 太大了,清空一下内存
  733. // if (laserCloudMapContainer.size() > 1000)
  734. // laserCloudMapContainer.clear();
  735. }
  736. /*
  737. * 提取局部角点、平面点云集合,加入局部map
  738. * 1、对最近的一帧关键帧,搜索时空维度上相邻的关键帧集合,降采样一下
  739. * 2、对关键帧集合中的每一帧,提取对应的角点、平面点,加入局部map中
  740. */
  741. void extractSurroundingKeyFrames()
  742. {
  743. if (cloudKeyPoses3D->points.empty() == true)
  744. return;
  745. if (loopClosureEnableFlag == true)//gc:TODO: a little weired: Loop closure should search the whole map while
  746. {
  747. extractForLoopClosure(); //gc: the name is misleading
  748. } else {
  749. extractNearby();
  750. }
  751. }
  752. void downsampleCurrentScan()
  753. {
  754. // Downsample cloud from current scan
  755. //对当前帧点云降采样 刚刚完成了周围关键帧的降采样
  756. //大量的降采样工作无非是为了使点云稀疏化 加快匹配以及实时性要求
  757. laserCloudCornerLastDS->clear();
  758. downSizeFilterCorner.setInputCloud(laserCloudCornerLast);
  759. downSizeFilterCorner.filter(*laserCloudCornerLastDS);
  760. // 降采样角点数量
  761. laserCloudCornerLastDSNum = laserCloudCornerLastDS->size();
  762. // 降采样面点数量
  763. laserCloudSurfLastDS->clear();
  764. downSizeFilterSurf.setInputCloud(laserCloudSurfLast);
  765. downSizeFilterSurf.filter(*laserCloudSurfLastDS);
  766. laserCloudSurfLastDSNum = laserCloudSurfLastDS->size();
  767. }
  768. /*
  769. * 当前激光帧角点寻找局部map匹配点
  770. * 1、更新当前帧位姿,将当前帧角点坐标变换到map系下,在局部map中查找5个最近点,距离小于1m,且5个点构成直线(用距离中心点的协方差矩阵,特征值进行判断),则认为匹配上了
  771. * 2、计算当前帧角点到直线的距离、垂线的单位向量,存储为角点参数
  772. */
  773. void cornerOptimization()
  774. {
  775. //实现transformTobeMapped的矩阵形式转换
  776. // 把结果存入transPointAssociateToMap中
  777. updatePointAssociateToMap();
  778. #pragma omp parallel for num_threads(numberOfCores)
  779. //gc: for every corner point
  780. for (int i = 0; i < laserCloudCornerLastDSNum; i++)
  781. {
  782. PointType pointOri, pointSel, coeff;
  783. std::vector<int> pointSearchInd;
  784. std::vector<float> pointSearchSqDis;
  785. pointOri = laserCloudCornerLastDS->points[i];
  786. //gc: calculate its location in the map using the prediction pose
  787. //第i帧的点转换到第一帧坐标系下
  788. //这里就调用了第一步中updatePointAssociateToMap中实现的transPointAssociateToMap,
  789. //然后利用这个函数,把pointOri的点转换到pointSel下,pointSel作为输出
  790. pointAssociateToMap(&pointOri, &pointSel);
  791. // kd-tree最近搜索
  792. kdtreeCornerFromMap->nearestKSearch(pointSel, 5, pointSearchInd, pointSearchSqDis);
  793. cv::Mat matA1(3, 3, CV_32F, cv::Scalar::all(0)); //mat(int rows行数, int cols列数, int type类型);
  794. cv::Mat matD1(1, 3, CV_32F, cv::Scalar::all(0));
  795. cv::Mat matV1(3, 3, CV_32F, cv::Scalar::all(0));
  796. if (pointSearchSqDis[4] < 1.0) {
  797. float cx = 0, cy = 0, cz = 0;
  798. // 5个样本的均值
  799. for (int j = 0; j < 5; j++) {
  800. cx += laserCloudCornerFromMapDS->points[pointSearchInd[j]].x;
  801. cy += laserCloudCornerFromMapDS->points[pointSearchInd[j]].y;
  802. cz += laserCloudCornerFromMapDS->points[pointSearchInd[j]].z;
  803. }
  804. //gc: the average coordinate of the most nearest points
  805. cx /= 5; cy /= 5; cz /= 5;
  806. // 下面求矩阵matA1=[ax,ay,az]^t*[ax,ay,az]
  807. // 更准确地说应该是在求协方差matA1
  808. float a11 = 0, a12 = 0, a13 = 0, a22 = 0, a23 = 0, a33 = 0;
  809. for (int j = 0; j < 5; j++) {
  810. float ax = laserCloudCornerFromMapDS->points[pointSearchInd[j]].x - cx;
  811. float ay = laserCloudCornerFromMapDS->points[pointSearchInd[j]].y - cy;
  812. float az = laserCloudCornerFromMapDS->points[pointSearchInd[j]].z - cz;
  813. a11 += ax * ax; a12 += ax * ay; a13 += ax * az;
  814. a22 += ay * ay; a23 += ay * az;
  815. a33 += az * az;
  816. }
  817. a11 /= 5; a12 /= 5; a13 /= 5; a22 /= 5; a23 /= 5; a33 /= 5;
  818. matA1.at<float>(0, 0) = a11; matA1.at<float>(0, 1) = a12; matA1.at<float>(0, 2) = a13;
  819. matA1.at<float>(1, 0) = a12; matA1.at<float>(1, 1) = a22; matA1.at<float>(1, 2) = a23;
  820. matA1.at<float>(2, 0) = a13; matA1.at<float>(2, 1) = a23; matA1.at<float>(2, 2) = a33;
  821. // 求正交阵的特征值和特征向量
  822. // 特征值:matD1,特征向量:matV1中 对应于LOAM论文里雷达建图 特征值与特征向量那块
  823. cv::eigen(matA1, matD1, matV1);
  824. // 边缘:与较大特征值相对应的特征向量代表边缘线的方向(一大两小,大方向)
  825. // 以下这一大块是在计算点到边缘的距离,最后通过系数s来判断是否距离很近
  826. // 如果距离很近就认为这个点在边缘上,需要放到laserCloudOri中
  827. // 如果最大的特征值相比次大特征值,大很多,认为构成了线,角点是合格的
  828. if (matD1.at<float>(0, 0) > 3 * matD1.at<float>(0, 1)) {
  829. // 当前帧角点坐标(map系下)
  830. float x0 = pointSel.x;
  831. float y0 = pointSel.y;
  832. float z0 = pointSel.z;
  833. // 局部map对应中心角点,沿着特征向量(直线方向)方向,前后各取一个点
  834. float x1 = cx + 0.1 * matV1.at<float>(0, 0);
  835. float y1 = cy + 0.1 * matV1.at<float>(0, 1);
  836. float z1 = cz + 0.1 * matV1.at<float>(0, 2);
  837. float x2 = cx - 0.1 * matV1.at<float>(0, 0);
  838. float y2 = cy - 0.1 * matV1.at<float>(0, 1);
  839. float z2 = cz - 0.1 * matV1.at<float>(0, 2);
  840. // 这边是在求[(x0-x1),(y0-y1),(z0-z1)]与[(x0-x2),(y0-y2),(z0-z2)]叉乘得到的向量的模长
  841. // 这个模长是由0.2*V1[0]和点[x0,y0,z0]构成的平行四边形的面积
  842. // 因为[(x0-x1),(y0-y1),(z0-z1)]x[(x0-x2),(y0-y2),(z0-z2)]=[XXX,YYY,ZZZ],
  843. // [XXX,YYY,ZZZ]=[(y0-y1)(z0-z2)-(y0-y2)(z0-z1),-(x0-x1)(z0-z2)+(x0-x2)(z0-z1),(x0-x1)(y0-y2)-(x0-x2)(y0-y1)]
  844. // area_012,也就是三个点组成的三角形面积*2,叉积的模|axb|=a*b*sin(theta)
  845. float a012 = sqrt(((x0 - x1)*(y0 - y2) - (x0 - x2)*(y0 - y1)) * ((x0 - x1)*(y0 - y2) - (x0 - x2)*(y0 - y1))
  846. + ((x0 - x1)*(z0 - z2) - (x0 - x2)*(z0 - z1)) * ((x0 - x1)*(z0 - z2) - (x0 - x2)*(z0 - z1))
  847. + ((y0 - y1)*(z0 - z2) - (y0 - y2)*(z0 - z1)) * ((y0 - y1)*(z0 - z2) - (y0 - y2)*(z0 - z1)));
  848. // line_12,底边边长
  849. float l12 = sqrt((x1 - x2)*(x1 - x2) + (y1 - y2)*(y1 - y2) + (z1 - z2)*(z1 - z2));
  850. // 两次叉积,得到点到直线的垂线段单位向量,x分量,下面同理
  851. // 求叉乘结果[la',lb',lc']=[(x1-x2),(y1-y2),(z1-z2)]x[XXX,YYY,ZZZ]
  852. // [la,lb,lc]=[la',lb',lc']/a012/l12
  853. // 得到底边上的高的方向向量[la,lb,lc]
  854. // LLL=[la,lb,lc]是V1[0]这条高上的单位法向量。||LLL||=1;
  855. //如不理解则看图:
  856. // A
  857. // B C
  858. // 这里ABxAC,代表垂直于ABC面的法向量,其模长为平行四边形面积
  859. //因此BCx(ABxAC),代表了BC和(ABC平面的法向量)的叉乘,那么其实这个向量就是A到BC的垂线的方向向量
  860. //那么(ABxAC)/|ABxAC|,代表着ABC平面的单位法向量
  861. //BCxABC平面单位法向量,即为一个长度为|BC|的(A到BC垂线的方向向量),因此再除以|BC|,得到A到BC垂线的单位方向向量
  862. float la = ((y1 - y2)*((x0 - x1)*(y0 - y2) - (x0 - x2)*(y0 - y1))
  863. + (z1 - z2)*((x0 - x1)*(z0 - z2) - (x0 - x2)*(z0 - z1))) / a012 / l12;
  864. float lb = -((x1 - x2)*((x0 - x1)*(y0 - y2) - (x0 - x2)*(y0 - y1))
  865. - (z1 - z2)*((y0 - y1)*(z0 - z2) - (y0 - y2)*(z0 - z1))) / a012 / l12;
  866. float lc = -((x1 - x2)*((x0 - x1)*(z0 - z2) - (x0 - x2)*(z0 - z1))
  867. + (y1 - y2)*((y0 - y1)*(z0 - z2) - (y0 - y2)*(z0 - z1))) / a012 / l12;
  868. // 三角形的高,也就是点到直线距离
  869. // 计算点pointSel到直线的距离
  870. // 这里需要特别说明的是ld2代表的是点pointSel到过点[cx,cy,cz]的方向向量直线的距离
  871. float ld2 = a012 / l12;
  872. // 下面涉及到一个鲁棒核函数,作者简单地设计了这个核函数。
  873. // 距离越大,s越小,是个距离惩罚因子(权重)
  874. float s = 1 - 0.9 * fabs(ld2);
  875. // coeff代表系数的意思
  876. // coff用于保存距离的方向向量
  877. coeff.x = s * la;
  878. coeff.y = s * lb;
  879. coeff.z = s * lc;
  880. // intensity本质上构成了一个核函数,ld2越接近于1,增长越慢
  881. // intensity=(1-0.9*ld2)*ld2=ld2-0.9*ld2*ld2
  882. coeff.intensity = s * ld2;
  883. // 程序末尾根据s的值来判断是否将点云点放入点云集合laserCloudOri以及coeffSel中。
  884. // 所以就应该认为这个点是边缘点
  885. // s>0.1 也就是要求点到直线的距离ld2要小于1m
  886. // s越大说明ld2越小(离边缘线越近),这样就说明点pointOri在直线上
  887. if (s > 0.1) {
  888. laserCloudOriCornerVec[i] = pointOri;
  889. coeffSelCornerVec[i] = coeff;
  890. laserCloudOriCornerFlag[i] = true;
  891. }
  892. }
  893. }
  894. }
  895. }
  896. /*
  897. * 当前激光帧平面点寻找局部map匹配点
  898. * 1、更新当前帧位姿,将当前帧平面点坐标变换到map系下,在局部map中查找5个最近点,距离小于1m,且5个点构成平面(最小二乘拟合平面),则认为匹配上了
  899. * 2、计算当前帧平面点到平面的距离、垂线的单位向量,存储为平面点参数
  900. */
  901. void surfOptimization()
  902. {
  903. updatePointAssociateToMap();
  904. // 遍历当前帧平面点集合
  905. #pragma omp parallel for num_threads(numberOfCores)
  906. for (int i = 0; i < laserCloudSurfLastDSNum; i++)
  907. {
  908. PointType pointOri, pointSel, coeff;
  909. std::vector<int> pointSearchInd;
  910. std::vector<float> pointSearchSqDis;
  911. // 寻找5个紧邻点, 计算其特征值和特征向量
  912. // 平面点(坐标还是lidar系)
  913. pointOri = laserCloudSurfLastDS->points[i];
  914. // 根据当前帧位姿,变换到世界坐标系(map系)下
  915. pointAssociateToMap(&pointOri, &pointSel);
  916. // 在局部平面点map中查找当前平面点相邻的5个平面点
  917. kdtreeSurfFromMap->nearestKSearch(pointSel, 5, pointSearchInd, pointSearchSqDis);
  918. Eigen::Matrix<float, 5, 3> matA0;
  919. Eigen::Matrix<float, 5, 1> matB0;
  920. Eigen::Vector3f matX0;
  921. matA0.setZero(); // 5*3 存储5个紧邻点
  922. matB0.fill(-1);
  923. matX0.setZero();
  924. // 只考虑附近1.0m内
  925. if (pointSearchSqDis[4] < 1.0) {
  926. for (int j = 0; j < 5; j++) {
  927. matA0(j, 0) = laserCloudSurfFromMapDS->points[pointSearchInd[j]].x;
  928. matA0(j, 1) = laserCloudSurfFromMapDS->points[pointSearchInd[j]].y;
  929. matA0(j, 2) = laserCloudSurfFromMapDS->points[pointSearchInd[j]].z;
  930. }
  931. // 求maxA0中点构成的平面法向量
  932. //matB0是-1,这个函数用来求解AX=B的X,
  933. //也就是AX+BY+CZ+1=0
  934. matX0 = matA0.colPivHouseholderQr().solve(matB0);
  935. // 假设平面方程为ax+by+cz+1=0,这里就是求方程的系数abc,d=1
  936. float pa = matX0(0, 0);
  937. float pb = matX0(1, 0);
  938. float pc = matX0(2, 0);
  939. float pd = 1;
  940. // 单位法向量
  941. // 对[pa,pb,pc,pd]进行单位化
  942. float ps = sqrt(pa * pa + pb * pb + pc * pc);
  943. pa /= ps; pb /= ps; pc /= ps; pd /= ps;
  944. // 检查平面是否合格,如果5个点中有点到平面的距离超过0.2m,那么认为这些点太分散了,不构成平面
  945. bool planeValid = true;
  946. for (int j = 0; j < 5; j++) {
  947. if (fabs(pa * laserCloudSurfFromMapDS->points[pointSearchInd[j]].x +
  948. pb * laserCloudSurfFromMapDS->points[pointSearchInd[j]].y +
  949. pc * laserCloudSurfFromMapDS->points[pointSearchInd[j]].z + pd) > 0.2) {
  950. planeValid = false;
  951. break;
  952. }
  953. }
  954. if (planeValid) {
  955. // 当前激光帧点到平面距离
  956. //点(x0,y0,z0)到了平面Ax+By+Cz+D=0的距离为:d=|Ax0+By0+Cz0+D|/√(A^2+B^2+C^2)
  957. //但是会发现下面的分母开了两次方,不知道为什么,分母多开一次方会更小,这因此求出的距离会更大
  958. float pd2 = pa * pointSel.x + pb * pointSel.y + pc * pointSel.z + pd;
  959. // 距离越大,s越小,是个距离惩罚因子(权重)
  960. // 后面部分相除求的是[pa,pb,pc,pd]与pointSel的夹角余弦值(两个sqrt,其实并不是余弦值)
  961. // 这个夹角余弦值越小越好,越小证明所求的[pa,pb,pc,pd]与平面越垂直
  962. float s = 1 - 0.9 * fabs(pd2) / sqrt(sqrt(pointSel.x * pointSel.x
  963. + pointSel.y * pointSel.y + pointSel.z * pointSel.z));
  964. // 点到平面垂线单位法向量(其实等价于平面法向量)
  965. coeff.x = s * pa;
  966. coeff.y = s * pb;
  967. coeff.z = s * pc;
  968. coeff.intensity = s * pd2;
  969. if (s > 0.1) {
  970. // 当前激光帧平面点,加入匹配集合中.
  971. //如果s>0.1,代表fabs(pd2) / sqrt(sqrt(pointSel.x * pointSel.x+ pointSel.y * pointSel.y + pointSel.z * pointSel.z))这一项<1,即"伪距离"<1
  972. laserCloudOriSurfVec[i] = pointOri;
  973. coeffSelSurfVec[i] = coeff;
  974. laserCloudOriSurfFlag[i] = true;
  975. }
  976. }
  977. }
  978. }
  979. }
  980. /*
  981. * 提取当前帧中与局部map匹配上了的角点、平面点,加入同一集合
  982. */
  983. void combineOptimizationCoeffs()
  984. {
  985. // combine corner coeffs
  986. // 遍历当前帧角点集合,提取出与局部map匹配上了的角点
  987. for (int i = 0; i < laserCloudCornerLastDSNum; ++i){
  988. if (laserCloudOriCornerFlag[i] == true){
  989. laserCloudOri->push_back(laserCloudOriCornerVec[i]);
  990. coeffSel->push_back(coeffSelCornerVec[i]);
  991. }
  992. }
  993. // combine surf coeffs
  994. // 遍历当前帧平面点集合,提取出与局部map匹配上了的平面点
  995. for (int i = 0; i < laserCloudSurfLastDSNum; ++i){
  996. if (laserCloudOriSurfFlag[i] == true){
  997. laserCloudOri->push_back(laserCloudOriSurfVec[i]);
  998. coeffSel->push_back(coeffSelSurfVec[i]);
  999. }
  1000. }
  1001. // reset flag for next iteration
  1002. // 清空标记
  1003. std::fill(laserCloudOriCornerFlag.begin(), laserCloudOriCornerFlag.end(), false);
  1004. std::fill(laserCloudOriSurfFlag.begin(), laserCloudOriSurfFlag.end(), false);
  1005. }
  1006. void scan2MapOptimization()
  1007. {
  1008. //根据现有地图与最新点云数据进行配准从而更新机器人精确位姿与融合建图,
  1009. //它分为角点优化、平面点优化、配准与更新等部分。
  1010. //优化的过程与里程计的计算类似,是通过计算点到直线或平面的距离,构建优化公式再用LM法求解。
  1011. if (cloudKeyPoses3D->points.empty())
  1012. return;
  1013. //降采样后角点和平面点数量如果大于最小合理角点数量10和面点数量10
  1014. if (laserCloudCornerLastDSNum > edgeFeatureMinValidNum && laserCloudSurfLastDSNum > surfFeatureMinValidNum)
  1015. {
  1016. // 构建kd-tree
  1017. kdtreeCornerFromMap->setInputCloud(laserCloudCornerFromMapDS);
  1018. kdtreeSurfFromMap->setInputCloud(laserCloudSurfFromMapDS);
  1019. // 迭代30次
  1020. for (int iterCount = 0; iterCount < 30; iterCount++)
  1021. {
  1022. laserCloudOri->clear();
  1023. coeffSel->clear();
  1024. //gc: calculate some coeff and judge whether tho point is valid corner point
  1025. // 当前激光帧与局部地图之间的:角点-角点 特征匹配
  1026. cornerOptimization();
  1027. //gc: calculate some coeff and judge whether tho point is valid surface point
  1028. // 当前激光帧与局部地图之间的:面点-面点 特征匹配
  1029. surfOptimization();
  1030. // 提取当前帧中与局部map匹配上了的角点、平面点,加入同一集合
  1031. combineOptimizationCoeffs();
  1032. //gc: the true iteration steps, calculate the transform
  1033. // scan-to-map优化
  1034. if (LMOptimization(iterCount) == true)
  1035. break;
  1036. }
  1037. //gc: interpolate the roll and pitch angle using the IMU measurement and Lidar calculation
  1038. //使用了9轴imu的orientation与做transformTobeMapped插值,并且roll和pitch收到常量阈值约束(权重)
  1039. // 更新当前位姿
  1040. transformUpdate();
  1041. } else {
  1042. ROS_WARN("Not enough features! Only %d edge and %d planar features available.", laserCloudCornerLastDSNum, laserCloudSurfLastDSNum);
  1043. }
  1044. }
  1045. /*
  1046. * 用imu原始RPY数据与scan-to-map优化后的位姿进行加权融合,更新当前帧位姿的roll、pitch,约束z坐标
  1047. */
  1048. //gc: interpolate the roll and pitch angle using the IMU measurement and Lidar calculation
  1049. void transformUpdate()
  1050. {
  1051. if (cloudInfo.imuAvailable == true)
  1052. {
  1053. // 俯仰角小于1.4
  1054. if (std::abs(cloudInfo.imuPitchInit) < 1.4)
  1055. {
  1056. double imuWeight = 0.01;
  1057. tf::Quaternion imuQuaternion;
  1058. tf::Quaternion transformQuaternion;
  1059. double rollMid, pitchMid, yawMid;
  1060. // slerp roll
  1061. transformQuaternion.setRPY(transformTobeMapped[0], 0, 0);
  1062. imuQuaternion.setRPY(cloudInfo.imuRollInit, 0, 0);
  1063. //gc: interpolate between Imu roll measurement and angle from lidar calculation
  1064. tf::Matrix3x3(transformQuaternion.slerp(imuQuaternion, imuWeight)).getRPY(rollMid, pitchMid, yawMid);
  1065. transformTobeMapped[0] = rollMid;
  1066. // slerp pitch
  1067. transformQuaternion.setRPY(0, transformTobeMapped[1], 0);
  1068. imuQuaternion.setRPY(0, cloudInfo.imuPitchInit, 0);
  1069. //gc: interpolate between Imu roll measurement and angle from lidar calculation
  1070. tf::Matrix3x3(transformQuaternion.slerp(imuQuaternion, imuWeight)).getRPY(rollMid, pitchMid, yawMid);
  1071. transformTobeMapped[1] = pitchMid;
  1072. }
  1073. }
  1074. // 更新当前帧位姿的roll, pitch, z坐标;因为是小车,roll、pitch是相对稳定的,
  1075. // 不会有很大变动,一定程度上可以信赖imu的数据,z是进行高度约束
  1076. transformTobeMapped[0] = constraintTransformation(transformTobeMapped[0], rotation_tollerance);
  1077. transformTobeMapped[1] = constraintTransformation(transformTobeMapped[1], rotation_tollerance);
  1078. transformTobeMapped[5] = constraintTransformation(transformTobeMapped[5], z_tollerance);
  1079. }
  1080. //相当于clib函数
  1081. float constraintTransformation(float value, float limit)
  1082. {
  1083. if (value < -limit)
  1084. value = -limit;
  1085. if (value > limit)
  1086. value = limit;
  1087. return value;
  1088. }
  1089. /*
  1090. * 计算当前帧与前一帧位姿变换,如果变化太小,不设为关键帧,反之设为关键帧
  1091. */
  1092. bool saveFrame()
  1093. {
  1094. if (cloudKeyPoses3D->points.empty())
  1095. return true;
  1096. // 前一帧位姿
  1097. //注:最开始没有的时候,在函数extractCloud里面有
  1098. Eigen::Affine3f transStart = pclPointToAffine3f(cloudKeyPoses6D->back());
  1099. // 当前帧位姿
  1100. Eigen::Affine3f transFinal = pcl::getTransformation(transformTobeMapped[3], transformTobeMapped[4], transformTobeMapped[5],
  1101. transformTobeMapped[0], transformTobeMapped[1], transformTobeMapped[2]);
  1102. // 位姿变换增量
  1103. Eigen::Affine3f transBetween = transStart.inverse() * transFinal;
  1104. float x, y, z, roll, pitch, yaw;
  1105. pcl::getTranslationAndEulerAngles(transBetween, x, y, z, roll, pitch, yaw);
  1106. //gc: judge whther should generate key pose
  1107. // 旋转和平移量都较小,当前帧不设为关键帧
  1108. if (abs(roll) < surroundingkeyframeAddingAngleThreshold &&
  1109. abs(pitch) < surroundingkeyframeAddingAngleThreshold &&
  1110. abs(yaw) < surroundingkeyframeAddingAngleThreshold &&
  1111. sqrt(x*x + y*y + z*z) < surroundingkeyframeAddingDistThreshold)
  1112. return false;
  1113. return true;
  1114. }
  1115. void addOdomFactor()
  1116. {
  1117. //gc: the first key pose
  1118. if (cloudKeyPoses3D->points.empty())
  1119. {
  1120. noiseModel::Diagonal::shared_ptr priorNoise = noiseModel::Diagonal::Variances((Vector(6) << 1e-2, 1e-2, M_PI*M_PI, 1e8, 1e8, 1e8).finished()); // rad*rad, meter*meter
  1121. gtSAMgraph.add(PriorFactor<Pose3>(0, trans2gtsamPose(transformTobeMapped), priorNoise));
  1122. initialEstimate.insert(0, trans2gtsamPose(transformTobeMapped));
  1123. }else{
  1124. noiseModel::Diagonal::shared_ptr odometryNoise = noiseModel::Diagonal::Variances((Vector(6) << 1e-6, 1e-6, 1e-6, 1e-4, 1e-4, 1e-4).finished());
  1125. gtsam::Pose3 poseFrom = pclPointTogtsamPose3(cloudKeyPoses6D->points.back());
  1126. gtsam::Pose3 poseTo = trans2gtsamPose(transformTobeMapped);
  1127. //gc: add constraint between current pose and previous pose
  1128. gtSAMgraph.add(BetweenFactor<Pose3>(cloudKeyPoses3D->size()-1, cloudKeyPoses3D->size(), poseFrom.between(poseTo), odometryNoise));
  1129. initialEstimate.insert(cloudKeyPoses3D->size(), poseTo);
  1130. }
  1131. }
  1132. void addGPSFactor()
  1133. {
  1134. if (gpsQueue.empty())
  1135. return;
  1136. // wait for system initialized and settles down
  1137. if (cloudKeyPoses3D->points.empty())
  1138. return;
  1139. else
  1140. {
  1141. //gc: the translation between the first and last pose is very small
  1142. if (pointDistance(cloudKeyPoses3D->front(), cloudKeyPoses3D->back()) < 5.0)
  1143. return;
  1144. }
  1145. // pose covariance small, no need to correct
  1146. //gc: the odom covariance is small TODO: although small maybe some interpolation
  1147. if (poseCovariance(3,3) < poseCovThreshold && poseCovariance(4,4) < poseCovThreshold)
  1148. return;
  1149. // last gps position
  1150. static PointType lastGPSPoint;
  1151. while (!gpsQueue.empty())
  1152. {
  1153. if (gpsQueue.front().header.stamp.toSec() < timeLaserCloudInfoLast - 0.2)
  1154. {
  1155. // message too old
  1156. gpsQueue.pop_front();
  1157. }
  1158. else if (gpsQueue.front().header.stamp.toSec() > timeLaserCloudInfoLast + 0.2)
  1159. {
  1160. // message too new
  1161. break;
  1162. }
  1163. else
  1164. {
  1165. nav_msgs::Odometry thisGPS = gpsQueue.front();
  1166. gpsQueue.pop_front();
  1167. // GPS too noisy, skip
  1168. float noise_x = thisGPS.pose.covariance[0];
  1169. float noise_y = thisGPS.pose.covariance[7];
  1170. float noise_z = thisGPS.pose.covariance[14];
  1171. if (noise_x > gpsCovThreshold || noise_y > gpsCovThreshold)
  1172. continue;
  1173. float gps_x = thisGPS.pose.pose.position.x;
  1174. float gps_y = thisGPS.pose.pose.position.y;
  1175. float gps_z = thisGPS.pose.pose.position.z;
  1176. if (!useGpsElevation)
  1177. {
  1178. gps_z = transformTobeMapped[5];
  1179. noise_z = 0.01;
  1180. }
  1181. // GPS not properly initialized (0,0,0)
  1182. if (abs(gps_x) < 1e-6 && abs(gps_y) < 1e-6)
  1183. continue;
  1184. // Add GPS every a few meters
  1185. PointType curGPSPoint;
  1186. curGPSPoint.x = gps_x;
  1187. curGPSPoint.y = gps_y;
  1188. curGPSPoint.z = gps_z;
  1189. if (pointDistance(curGPSPoint, lastGPSPoint) < 5.0)
  1190. continue;
  1191. else
  1192. lastGPSPoint = curGPSPoint;
  1193. gtsam::Vector Vector3(3);
  1194. Vector3 << max(noise_x, 1.0f), max(noise_y, 1.0f), max(noise_z, 1.0f);
  1195. noiseModel::Diagonal::shared_ptr gps_noise = noiseModel::Diagonal::Variances(Vector3);
  1196. gtsam::GPSFactor gps_factor(cloudKeyPoses3D->size(), gtsam::Point3(gps_x, gps_y, gps_z), gps_noise);
  1197. gtSAMgraph.add(gps_factor);
  1198. aLoopIsClosed = true;
  1199. break;
  1200. }
  1201. }
  1202. }
  1203. /*
  1204. * 设置当前帧为关键帧并执行因子图优化
  1205. * 1、计算当前帧与前一帧位姿变换,如果变化太小,不设为关键帧,反之设为关键帧
  1206. * 2、添加激光里程计因子、GPS因子、闭环因子
  1207. * 3、执行因子图优化
  1208. * 4、得到当前帧优化后位姿,位姿协方差
  1209. * 5、添加cloudKeyPoses3D,cloudKeyPoses6D,更新transformTobeMapped,添加当前关键帧的角点、平面点集合
  1210. */
  1211. void saveKeyFramesAndFactor()
  1212. {
  1213. //gc: judge whther should generate key pose
  1214. // 计算当前帧与前一帧位姿变换,如果变化太小,不设为关键帧,反之设为关键帧
  1215. if (saveFrame() == false)
  1216. return;
  1217. // 往GTSAM里添加因子都是添加的是序号和相对间的位姿变换和noise(一般设置为固定值),
  1218. // odom factor
  1219. //gc: add odom factor in the graph
  1220. addOdomFactor();
  1221. // gps factor
  1222. addGPSFactor();
  1223. // cout << "****************************************************" << endl;
  1224. // gtSAMgraph.print("GTSAM Graph:\n");
  1225. // update iSAM
  1226. // 执行优化
  1227. isam->update(gtSAMgraph, initialEstimate);
  1228. isam->update();
  1229. // update之后要清空一下保存的因子图,注:历史数据不会清掉,ISAM保存起来了
  1230. gtSAMgraph.resize(0);
  1231. initialEstimate.clear();
  1232. //save key poses
  1233. PointType thisPose3D;
  1234. PointTypePose thisPose6D;
  1235. Pose3 latestEstimate;
  1236. // 优化结果
  1237. isamCurrentEstimate = isam->calculateEstimate();
  1238. // 当前位姿结果
  1239. latestEstimate = isamCurrentEstimate.at<Pose3>(isamCurrentEstimate.size()-1);
  1240. // cout << "****************************************************" << endl;
  1241. // isamCurrentEstimate.print("Current estimate: ");
  1242. //gc:cloudKeyPoses3D can be used to calculate the nearest key frames
  1243. // cloudKeyPoses3D加入当前帧位姿
  1244. thisPose3D.x = latestEstimate.translation().x();
  1245. thisPose3D.y = latestEstimate.translation().y();
  1246. thisPose3D.z = latestEstimate.translation().z();
  1247. // 索引
  1248. thisPose3D.intensity = cloudKeyPoses3D->size(); // this can be used as index
  1249. cloudKeyPoses3D->push_back(thisPose3D);
  1250. // cloudKeyPoses6D加入当前帧位姿
  1251. thisPose6D.x = thisPose3D.x;
  1252. thisPose6D.y = thisPose3D.y;
  1253. thisPose6D.z = thisPose3D.z;
  1254. thisPose6D.intensity = thisPose3D.intensity ; // this can be used as index
  1255. thisPose6D.roll = latestEstimate.rotation().roll();
  1256. thisPose6D.pitch = latestEstimate.rotation().pitch();
  1257. thisPose6D.yaw = latestEstimate.rotation().yaw();
  1258. thisPose6D.time = timeLaserCloudInfoLast;
  1259. cloudKeyPoses6D->push_back(thisPose6D);
  1260. // cout << "****************************************************" << endl;
  1261. // cout << "Pose covariance:" << endl;
  1262. // cout << isam->marginalCovariance(isamCurrentEstimate.size()-1) << endl << endl;
  1263. // 位姿协方差
  1264. poseCovariance = isam->marginalCovariance(isamCurrentEstimate.size()-1);
  1265. // save updated transform
  1266. // transformTobeMapped更新当前帧位姿
  1267. transformTobeMapped[0] = latestEstimate.rotation().roll();
  1268. transformTobeMapped[1] = latestEstimate.rotation().pitch();
  1269. transformTobeMapped[2] = latestEstimate.rotation().yaw();
  1270. transformTobeMapped[3] = latestEstimate.translation().x();
  1271. transformTobeMapped[4] = latestEstimate.translation().y();
  1272. transformTobeMapped[5] = latestEstimate.translation().z();
  1273. // save all the received edge and surf points
  1274. // 当前帧激光角点、面点,降采样集合
  1275. // 把当前帧点云放进全局地图里
  1276. pcl::PointCloud<PointType>::Ptr thisCornerKeyFrame(new pcl::PointCloud<PointType>());
  1277. pcl::PointCloud<PointType>::Ptr thisSurfKeyFrame(new pcl::PointCloud<PointType>());
  1278. //gc:
  1279. pcl::copyPointCloud(*laserCloudCornerLastDS, *thisCornerKeyFrame);
  1280. pcl::copyPointCloud(*laserCloudSurfLastDS, *thisSurfKeyFrame);
  1281. // save key frame cloud
  1282. // 保存特征点降采样集合
  1283. cornerCloudKeyFrames.push_back(thisCornerKeyFrame);
  1284. surfCloudKeyFrames.push_back(thisSurfKeyFrame);
  1285. // save path for visualization
  1286. // 更新里程计轨迹
  1287. updatePath(thisPose6D);
  1288. }
  1289. void updatePointAssociateToMap()
  1290. {
  1291. transPointAssociateToMap = trans2Affine3f(transformTobeMapped);
  1292. }
  1293. // 进行完一次图优化之后,改变的不单单是当前帧的位姿,其它节点的位姿也可能变化,所以都更新一下
  1294. // 更新因子图所有变量节点的位姿,也就是所有历史关键帧的位姿,更新里程计轨迹
  1295. void correctPoses()
  1296. {
  1297. if (cloudKeyPoses3D->points.empty())
  1298. return;
  1299. if (aLoopIsClosed == true)
  1300. {
  1301. // clear path
  1302. // 清空里程计轨迹
  1303. globalPath.poses.clear();
  1304. // update key poses
  1305. //更新因子图所有变量节点的位姿,也就是所有历史关键帧的位姿,
  1306. int numPoses = isamCurrentEstimate.size();
  1307. for (int i = 0; i < numPoses; ++i)
  1308. {
  1309. cloudKeyPoses3D->points[i].x = isamCurrentEstimate.at<Pose3>(i).translation().x();
  1310. cloudKeyPoses3D->points[i].y = isamCurrentEstimate.at<Pose3>(i).translation().y();
  1311. cloudKeyPoses3D->points[i].z = isamCurrentEstimate.at<Pose3>(i).translation().z();
  1312. cloudKeyPoses6D->points[i].x = cloudKeyPoses3D->points[i].x;
  1313. cloudKeyPoses6D->points[i].y = cloudKeyPoses3D->points[i].y;
  1314. cloudKeyPoses6D->points[i].z = cloudKeyPoses3D->points[i].z;
  1315. cloudKeyPoses6D->points[i].roll = isamCurrentEstimate.at<Pose3>(i).rotation().roll();
  1316. cloudKeyPoses6D->points[i].pitch = isamCurrentEstimate.at<Pose3>(i).rotation().pitch();
  1317. cloudKeyPoses6D->points[i].yaw = isamCurrentEstimate.at<Pose3>(i).rotation().yaw();
  1318. updatePath(cloudKeyPoses6D->points[i]);
  1319. }
  1320. aLoopIsClosed = false;
  1321. // ID for reseting IMU pre-integration
  1322. ++imuPreintegrationResetId;
  1323. }
  1324. }
  1325. // 更新里程计轨迹。输入:thisPose6D
  1326. void updatePath(const PointTypePose& pose_in)
  1327. {
  1328. geometry_msgs::PoseStamped pose_stamped;
  1329. pose_stamped.header.stamp = ros::Time().fromSec(pose_in.time);
  1330. pose_stamped.header.frame_id = "odom";
  1331. pose_stamped.pose.position.x = pose_in.x;
  1332. pose_stamped.pose.position.y = pose_in.y;
  1333. pose_stamped.pose.position.z = pose_in.z;
  1334. tf::Quaternion q = tf::createQuaternionFromRPY(pose_in.roll, pose_in.pitch, pose_in.yaw);
  1335. pose_stamped.pose.orientation.x = q.x();
  1336. pose_stamped.pose.orientation.y = q.y();
  1337. pose_stamped.pose.orientation.z = q.z();
  1338. pose_stamped.pose.orientation.w = q.w();
  1339. globalPath.poses.push_back(pose_stamped);
  1340. }
  1341. // 较原码修改、删除较多
  1342. void publishOdometry()
  1343. {
  1344. // Publish odometry for ROS
  1345. nav_msgs::Odometry laserOdometryROS;
  1346. laserOdometryROS.header.stamp = timeLaserInfoStamp;
  1347. laserOdometryROS.header.frame_id = "odom";
  1348. laserOdometryROS.child_frame_id = "odom_mapping";
  1349. laserOdometryROS.pose.pose.position.x = transformTobeMapped[3];
  1350. laserOdometryROS.pose.pose.position.y = transformTobeMapped[4];
  1351. laserOdometryROS.pose.pose.position.z = transformTobeMapped[5];
  1352. laserOdometryROS.pose.pose.orientation = tf::createQuaternionMsgFromRollPitchYaw(transformTobeMapped[0], transformTobeMapped[1], transformTobeMapped[2]);
  1353. laserOdometryROS.pose.covariance[0] = double(imuPreintegrationResetId);
  1354. // 没有经过IMU数据融合,单纯由mapOptimization经过GTSAM优化后的位姿是由pubOdomAftMappedROS发布的
  1355. // 它的话题是"lio_sam/mapping/odometry",这个是由transformFusion订阅的,用于与IMU里程计进行融合
  1356. pubOdomAftMappedROS.publish(laserOdometryROS);
  1357. // 在publishOdometry()里,删除了pubLaserOdometryIncremental,它的话题是"lio_sam/mapping/odometry_incremental"
  1358. // 它发布的laserOdomIncremental位姿是经过IMU数据加权融合的,这个是由IMUPreintegration订阅的
  1359. // 用于制作高频的IMU里程计;
  1360. // 代码段如下:
  1361. // Publish odometry for ROS (incremental)
  1362. // static bool lastIncreOdomPubFlag = false;
  1363. // static nav_msgs::Odometry laserOdomIncremental; // incremental odometry msg
  1364. // static Eigen::Affine3f increOdomAffine; // incremental odometry in affine
  1365. // //第一次数据直接用全局里程计初始化
  1366. // if (lastIncreOdomPubFlag == false)
  1367. // {
  1368. // lastIncreOdomPubFlag = true;
  1369. // laserOdomIncremental = laserOdometryROS;
  1370. // increOdomAffine = trans2Affine3f(transformTobeMapped);
  1371. // } else {
  1372. // // 当前帧与前一帧之间的位姿变换
  1373. // Eigen::Affine3f affineIncre = incrementalOdometryAffineFront.inverse() * incrementalOdometryAffineBack;
  1374. // increOdomAffine = increOdomAffine * affineIncre;
  1375. // float x, y, z, roll, pitch, yaw;
  1376. // pcl::getTranslationAndEulerAngles (increOdomAffine, x, y, z, roll, pitch, yaw);
  1377. // if (cloudInfo.imuAvailable == true)
  1378. // {
  1379. // if (std::abs(cloudInfo.imuPitchInit) < 1.4)
  1380. // {
  1381. // double imuWeight = 0.1;
  1382. // tf::Quaternion imuQuaternion;
  1383. // tf::Quaternion transformQuaternion;
  1384. // double rollMid, pitchMid, yawMid;
  1385. // // slerp roll
  1386. // // roll姿态角加权平均
  1387. // transformQuaternion.setRPY(roll, 0, 0);
  1388. // imuQuaternion.setRPY(cloudInfo.imuRollInit, 0, 0);
  1389. // tf::Matrix3x3(transformQuaternion.slerp(imuQuaternion, imuWeight)).getRPY(rollMid, pitchMid, yawMid);
  1390. // roll = rollMid;
  1391. // // slerp pitch
  1392. // // pitch姿态角加权平均
  1393. // transformQuaternion.setRPY(0, pitch, 0);
  1394. // imuQuaternion.setRPY(0, cloudInfo.imuPitchInit, 0);
  1395. // tf::Matrix3x3(transformQuaternion.slerp(imuQuaternion, imuWeight)).getRPY(rollMid, pitchMid, yawMid);
  1396. // pitch = pitchMid;
  1397. // }
  1398. // }
  1399. // laserOdomIncremental.header.stamp = timeLaserInfoStamp;
  1400. // laserOdomIncremental.header.frame_id = odometryFrame;
  1401. // laserOdomIncremental.child_frame_id = "odom_mapping";
  1402. // laserOdomIncremental.pose.pose.position.x = x;
  1403. // laserOdomIncremental.pose.pose.position.y = y;
  1404. // laserOdomIncremental.pose.pose.position.z = z;
  1405. // laserOdomIncremental.pose.pose.orientation = tf::createQuaternionMsgFromRollPitchYaw(roll, pitch, yaw);
  1406. // if (isDegenerate)
  1407. // laserOdomIncremental.pose.covariance[0] = 1;
  1408. // else
  1409. // laserOdomIncremental.pose.covariance[0] = 0;
  1410. // }
  1411. // pubLaserOdometryIncremental.publish(laserOdomIncremental);
  1412. // }
  1413. // 较原码还删除了isDegenerate
  1414. // 这个判断是在scan2map的LMOptimization那里计算匹配的J矩阵时获得的
  1415. // 如果发生退化,那么标志位covariance[0] = 1,在IMUPreintegration那里的图优化会给当前位姿的计算加一个大的方差。
  1416. // if (isDegenerate)
  1417. // laserOdomIncremental.pose.covariance[0] = 1;
  1418. // else
  1419. // laserOdomIncremental.pose.covariance[0] = 0;
  1420. // kitti位姿输出到txt文档
  1421. std::ofstream pose1("/home/clf/trajectory/lio-sam-tum/test.txt", std::ios::app);
  1422. std::ofstream pose2("/home/clf/trajectory/lio-sam-tum/test_time.txt", std::ios::app);
  1423. pose1.setf(std::ios::scientific, std::ios::floatfield);
  1424. pose2.setf(std::ios::scientific, std::ios::floatfield);
  1425. // pose1.precision(15);
  1426. //save final trajectory in the left camera coordinate system.
  1427. Eigen::Matrix3d rotation_matrix;
  1428. // rotation_matrix = q_w_curr.toRotationMatrix();
  1429. rotation_matrix = Eigen::AngleAxisd(transformTobeMapped[2], Eigen::Vector3d::UnitZ()) *
  1430. Eigen::AngleAxisd(transformTobeMapped[1], Eigen::Vector3d::UnitY()) *
  1431. Eigen::AngleAxisd(transformTobeMapped[0], Eigen::Vector3d::UnitX());
  1432. Eigen::Matrix<double, 4, 4> myaloam_pose;
  1433. myaloam_pose.topLeftCorner(3,3) = rotation_matrix;
  1434. myaloam_pose(0,3) = laserOdometryROS.pose.pose.position.x;
  1435. myaloam_pose(1,3) = laserOdometryROS.pose.pose.position.y;
  1436. myaloam_pose(2,3) = laserOdometryROS.pose.pose.position.z;
  1437. Eigen::Matrix<double, 4, 4> cali_paremeter;
  1438. // cali_paremeter << 4.276802385584e-04, -9.999672484946e-01, -8.084491683471e-03, -1.198459927713e-02, //00-02
  1439. // -7.210626507497e-03, 8.081198471645e-03, -9.999413164504e-01, -5.403984729748e-02,
  1440. // 9.999738645903e-01, 4.859485810390e-04, -7.206933692422e-03, -2.921968648686e-01,
  1441. // 0, 0, 0, 1;
  1442. cali_paremeter << -1.857739385241e-03,-9.999659513510e-01, -8.039975204516e-03, -4.784029760483e-03, //04-10
  1443. -6.481465826011e-03, 8.051860151134e-03, -9.999466081774e-01, -7.337429464231e-02,
  1444. 9.999773098287e-01, -1.805528627661e-03, -6.496203536139e-03, -3.339968064433e-01,
  1445. 0, 0, 0, 1;
  1446. /*cali_paremeter << 2.347736981471e-04, -9.999441545438e-01, -1.056347781105e-02, -2.796816941295e-03, // 03
  1447. 1.044940741659e-02, 1.056535364138e-02, -9.998895741176e-01, -7.510879138296e-02,
  1448. 9.999453885620e-01, 1.243653783865e-04, 1.045130299567e-02, -2.721327964059e-01,
  1449. 0, 0, 0, 1;*/
  1450. Eigen::Matrix<double, 4, 4> myloam_pose_f;
  1451. myloam_pose_f = cali_paremeter * myaloam_pose * cali_paremeter.inverse();
  1452. pose1 << myloam_pose_f(0,0) << " " << myloam_pose_f(0,1) << " " << myloam_pose_f(0,2) << " " << myloam_pose_f(0,3) << " "
  1453. << myloam_pose_f(1,0) << " " << myloam_pose_f(1,1) << " " << myloam_pose_f(1,2) << " " << myloam_pose_f(1,3) << " "
  1454. << myloam_pose_f(2,0) << " " << myloam_pose_f(2,1) << " " << myloam_pose_f(2,2) << " " << myloam_pose_f(2,3) << std::endl;
  1455. // 获取当前更新的时间 这样与ground turth对比才更准确
  1456. // 相当于是第一帧的时间
  1457. static auto T1 = timeLaserInfoStamp;
  1458. pose2 << laserOdometryROS.header.stamp - T1 << std::endl;
  1459. pose1.close();
  1460. pose2.close();
  1461. }
  1462. void publishFrames()
  1463. {
  1464. if (cloudKeyPoses3D->points.empty())
  1465. return;
  1466. // publish key poses
  1467. publishCloud(&pubKeyPoses, cloudKeyPoses3D, timeLaserInfoStamp, "odom");
  1468. // Publish surrounding key frames
  1469. publishCloud(&pubRecentKeyFrames, laserCloudSurfFromMapDS, timeLaserInfoStamp, "odom");
  1470. // publish registered key frame
  1471. //gc: feature points
  1472. if (pubRecentKeyFrame.getNumSubscribers() != 0)
  1473. {
  1474. pcl::PointCloud<PointType>::Ptr cloudOut(new pcl::PointCloud<PointType>());
  1475. PointTypePose thisPose6D = trans2PointTypePose(transformTobeMapped);
  1476. *cloudOut += *transformPointCloud(laserCloudCornerLastDS, &thisPose6D);
  1477. *cloudOut += *transformPointCloud(laserCloudSurfLastDS, &thisPose6D);
  1478. publishCloud(&pubRecentKeyFrame, cloudOut, timeLaserInfoStamp, "odom");
  1479. }
  1480. // publish registered high-res raw cloud
  1481. //gc: whole point_cloud of the scan
  1482. if (pubCloudRegisteredRaw.getNumSubscribers() != 0)
  1483. {
  1484. pcl::PointCloud<PointType>::Ptr cloudOut(new pcl::PointCloud<PointType>());
  1485. pcl::fromROSMsg(cloudInfo.cloud_deskewed, *cloudOut);
  1486. PointTypePose thisPose6D = trans2PointTypePose(transformTobeMapped);
  1487. *cloudOut = *transformPointCloud(cloudOut, &thisPose6D);
  1488. publishCloud(&pubCloudRegisteredRaw, cloudOut, timeLaserInfoStamp, "odom");
  1489. }
  1490. // publish path
  1491. if (pubPath.getNumSubscribers() != 0)
  1492. {
  1493. globalPath.header.stamp = timeLaserInfoStamp;
  1494. globalPath.header.frame_id = "odom";
  1495. pubPath.publish(globalPath);
  1496. }
  1497. }
  1498. };
  1499. int main(int argc, char** argv)
  1500. {
  1501. ros::init(argc, argv, "lio_sam");
  1502. mapOptimization MO;
  1503. ROS_INFO("\033[1;32m----> Map Optimization Started.\033[0m");
  1504. std::thread loopthread(&mapOptimization::loopClosureThread, &MO);
  1505. std::thread visualizeMapThread(&mapOptimization::visualizeGlobalMapThread, &MO);
  1506. ros::spin();
  1507. loopthread.join();
  1508. visualizeMapThread.join();
  1509. return 0;
  1510. }

之后会抽空详细写一下kitti数据集的制作、轨迹代码的选择和修改、evo工具的使用等等内容。

最后放几张笔者的测试结果图:

 

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

闽ICP备14008679号