|
@@ -0,0 +1,332 @@
|
|
|
+#include "laser_odom.hpp"
|
|
|
+#include "hdl_graph_slam/registrations.hpp"
|
|
|
+
|
|
|
+#include <memory>
|
|
|
+#include <iostream>
|
|
|
+
|
|
|
+#include <ros/ros.h>
|
|
|
+#include <ros/time.h>
|
|
|
+#include <ros/duration.h>
|
|
|
+#include <pcl_ros/point_cloud.h>
|
|
|
+#include <tf_conversions/tf_eigen.h>
|
|
|
+#include <tf/transform_listener.h>
|
|
|
+#include <tf/transform_broadcaster.h>
|
|
|
+
|
|
|
+#include <std_msgs/Time.h>
|
|
|
+#include <nav_msgs/Odometry.h>
|
|
|
+#include <sensor_msgs/PointCloud2.h>
|
|
|
+#include <geometry_msgs/TransformStamped.h>
|
|
|
+#include <geometry_msgs/PoseWithCovarianceStamped.h>
|
|
|
+
|
|
|
+#include <nodelet/nodelet.h>
|
|
|
+#include <pluginlib/class_list_macros.h>
|
|
|
+
|
|
|
+#include <pcl/filters/voxel_grid.h>
|
|
|
+#include <pcl/filters/passthrough.h>
|
|
|
+#include <pcl/filters/approximate_voxel_grid.h>
|
|
|
+
|
|
|
+#include <hdl_graph_slam/ros_utils.hpp>
|
|
|
+#include <hdl_graph_slam/registrations.hpp>
|
|
|
+#include <hdl_graph_slam/ScanMatchingStatus.h>
|
|
|
+
|
|
|
+#include <laser_geometry/laser_geometry.h>
|
|
|
+#include <nav_msgs/Path.h>
|
|
|
+
|
|
|
+#include <tf2/LinearMath/Quaternion.h>
|
|
|
+
|
|
|
+int main(int argc, char* argv[]) {
|
|
|
+
|
|
|
+ ros::init(argc, argv, "laser_odom");
|
|
|
+
|
|
|
+ Laser_Odom laser_odom;
|
|
|
+ laser_odom.init();
|
|
|
+
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+Laser_Odom::Laser_Odom() {
|
|
|
+ laser_odom = this;
|
|
|
+ cloud.reset(new pcl::PointCloud<PointT>());
|
|
|
+ trans_cloud.reset(new pcl::PointCloud<PointT>());
|
|
|
+ registration.reset(new fast_gicp::FastGICP<PointT, PointT>());
|
|
|
+}
|
|
|
+
|
|
|
+Laser_Odom::~Laser_Odom() {}
|
|
|
+
|
|
|
+void Laser_Odom::init() {
|
|
|
+ auto voxelgrid = new pcl::VoxelGrid<PointT>();
|
|
|
+ voxelgrid->setLeafSize(downsample_resolution, downsample_resolution, downsample_resolution);
|
|
|
+ downsample_filter.reset(voxelgrid);
|
|
|
+
|
|
|
+ fast_gicp::FastGICP<PointT, PointT>::Ptr gicp(new fast_gicp::FastGICP<PointT, PointT>());
|
|
|
+ gicp->setNumThreads(0);
|
|
|
+ gicp->setTransformationEpsilon(1e-5);
|
|
|
+ gicp->setMaximumIterations(128);
|
|
|
+ gicp->setMaxCorrespondenceDistance(2.0);
|
|
|
+ gicp->setCorrespondenceRandomness(20);
|
|
|
+
|
|
|
+ registration = gicp;
|
|
|
+
|
|
|
+
|
|
|
+ odom_pub = nh.advertise<nav_msgs::Odometry>("/laser_odom", 32);
|
|
|
+
|
|
|
+
|
|
|
+ source_points_pub = nh.advertise<sensor_msgs::PointCloud2>("/aligned_points", 32);
|
|
|
+ map_points_pub = nh.advertise<sensor_msgs::PointCloud2>("/frame_points", 1, true);
|
|
|
+
|
|
|
+ ros::Subscriber sub = nh.subscribe<sensor_msgs::LaserScan>("/scan", 10, Laser_Odom::cloud_callback);
|
|
|
+ ros::spin();
|
|
|
+}
|
|
|
+
|
|
|
+Laser_Odom* Laser_Odom::laser_odom = nullptr;
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+void Laser_Odom::cloud_callback(const sensor_msgs::LaserScan::ConstPtr& laser_scan_msg) {
|
|
|
+ if(!ros::ok()) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ laser_odom->projector.projectLaser(*laser_scan_msg, laser_odom->cloud2);
|
|
|
+ pcl::fromROSMsg(laser_odom->cloud2, *laser_odom->cloud);
|
|
|
+
|
|
|
+
|
|
|
+ Eigen::Matrix4f pose = laser_odom->matching(laser_scan_msg->header.stamp, laser_odom->cloud);
|
|
|
+
|
|
|
+
|
|
|
+ laser_odom->publish_odometry(laser_scan_msg->header.stamp, laser_scan_msg->header.frame_id, pose);
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+ * @brief estimate the relative pose between an input cloud and a keyframe cloud
|
|
|
+ * @param stamp the timestamp of the input cloud
|
|
|
+ * @param cloud the input cloud
|
|
|
+ * @return the relative pose between the input cloud and the keyframe cloud
|
|
|
+ */
|
|
|
+
|
|
|
+bool first = false;
|
|
|
+Eigen::Matrix4f last_odom;
|
|
|
+Eigen::Matrix4f Laser_Odom::matching(const ros::Time& stamp, const pcl::PointCloud<PointT>::ConstPtr& cloud) {
|
|
|
+
|
|
|
+ if(!first) {
|
|
|
+ prev_time = ros::Time();
|
|
|
+ prev_trans.setIdentity();
|
|
|
+ keyframe_pose.setIdentity();
|
|
|
+ keyframe_stamp = stamp;
|
|
|
+ keyframe = downsample(cloud);
|
|
|
+ registration->setInputTarget(keyframe);
|
|
|
+ first = true;
|
|
|
+ last_odom.setIdentity();
|
|
|
+
|
|
|
+ sensor_msgs::PointCloud2Ptr cloud_msg(new sensor_msgs::PointCloud2());
|
|
|
+ pcl::toROSMsg(*keyframe, *cloud_msg);
|
|
|
+ cloud_msg->header.frame_id = "odom";
|
|
|
+
|
|
|
+ map_points_pub.publish(*cloud_msg);
|
|
|
+
|
|
|
+ return Eigen::Matrix4f::Identity();
|
|
|
+
|
|
|
+
|
|
|
+ }
|
|
|
+ auto filtered = downsample(cloud);
|
|
|
+ registration->setInputSource(filtered);
|
|
|
+
|
|
|
+ std::string msf_source;
|
|
|
+ Eigen::Isometry3f msf_delta = Eigen::Isometry3f::Identity();
|
|
|
+
|
|
|
+
|
|
|
+ pcl::PointCloud<PointT>::Ptr aligned(new pcl::PointCloud<PointT>());
|
|
|
+
|
|
|
+ registration->align(*aligned, Eigen::Isometry3f::Identity().matrix());
|
|
|
+
|
|
|
+ LOG(INFO) << "===== getFitnessScore = " << registration->getFitnessScore() ;
|
|
|
+
|
|
|
+
|
|
|
+ if(!registration->hasConverged()) {
|
|
|
+ ROS_ERROR("scan matching has not converged!!");
|
|
|
+ return keyframe_pose * prev_trans;
|
|
|
+ }
|
|
|
+
|
|
|
+ Eigen::Matrix4f trans = registration->getFinalTransformation();
|
|
|
+ odom = keyframe_pose * trans;
|
|
|
+
|
|
|
+ std::cout << std::fixed << std::setprecision(5);
|
|
|
+ std::cout << "keyframe_pose\n" << keyframe_pose << std::endl;
|
|
|
+ LOG(INFO) << "keyframe_pose = " << atan2(keyframe_pose(1, 0), keyframe_pose(0, 0)) * (180.0 / M_PI);
|
|
|
+
|
|
|
+ std::cout << "trans\n" << trans << std::endl;
|
|
|
+ LOG(INFO) << "trans = " << atan2(trans(1, 0), trans(0, 0)) * (180.0 / M_PI);
|
|
|
+
|
|
|
+ std::cout << "odom\n" << odom << std::endl;
|
|
|
+ LOG(INFO) << "odom = " << atan2(odom(1, 0), odom(0, 0)) * (180.0 / M_PI);
|
|
|
+
|
|
|
+
|
|
|
+ prev_time = stamp;
|
|
|
+
|
|
|
+
|
|
|
+ auto keyframe_trans = matrix2transform(stamp, keyframe_pose, "odom", "keyframe");
|
|
|
+ keyframe_broadcaster.sendTransform(keyframe_trans);
|
|
|
+
|
|
|
+ double delta_trans = trans.block<3, 1>(0, 3).norm();
|
|
|
+ double delta_angle = std::acos(Eigen::Quaternionf(trans.block<3, 3>(0, 0)).w());
|
|
|
+ double delta_time = (stamp - keyframe_stamp).toSec();
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ pcl::PointCloud<PointT>::Ptr filter_trans(new pcl::PointCloud<PointT>());
|
|
|
+ pcl::transformPointCloud(*filtered, *filter_trans, odom);
|
|
|
+
|
|
|
+ pcl::PointCloud<PointT>::Ptr aligned_trans(new pcl::PointCloud<PointT>());
|
|
|
+ pcl::transformPointCloud(*registration->getInputSource(), *aligned_trans, last_odom);
|
|
|
+ *trans_cloud+=*aligned_trans;
|
|
|
+
|
|
|
+
|
|
|
+ sensor_msgs::PointCloud2Ptr cloud2_msg(new sensor_msgs::PointCloud2());
|
|
|
+ pcl::toROSMsg(*trans_cloud, *cloud2_msg);
|
|
|
+ cloud2_msg->header.frame_id = "map";
|
|
|
+ source_points_pub.publish(*cloud2_msg);
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ sensor_msgs::PointCloud2Ptr cloud_msg(new sensor_msgs::PointCloud2());
|
|
|
+ pcl::toROSMsg(*registration->getInputTarget(), *cloud_msg);
|
|
|
+
|
|
|
+ cloud_msg->header.frame_id = "map";
|
|
|
+
|
|
|
+ map_points_pub.publish(*cloud_msg);
|
|
|
+
|
|
|
+
|
|
|
+ registration->setInputTarget(filtered);
|
|
|
+ last_odom = last_odom * trans;
|
|
|
+ std::cout << "The matrix last_odom is: \n" << last_odom << std::endl;
|
|
|
+ return last_odom;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+ * @brief downsample a point cloud
|
|
|
+ * @param cloud input cloud
|
|
|
+ * @return downsampled point cloud
|
|
|
+ */
|
|
|
+pcl::PointCloud<PointT>::ConstPtr Laser_Odom::downsample(const pcl::PointCloud<PointT>::ConstPtr& cloud) {
|
|
|
+ if(!downsample_filter) {
|
|
|
+ return cloud;
|
|
|
+ }
|
|
|
+ pcl::PointCloud<PointT>::Ptr filtered(new pcl::PointCloud<PointT>());
|
|
|
+ downsample_filter->setInputCloud(cloud);
|
|
|
+ downsample_filter->filter(*filtered);
|
|
|
+ return filtered;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+ * @brief convert Eigen::Matrix to geometry_msgs::TransformStamped
|
|
|
+ * @param stamp timestamp
|
|
|
+ * @param pose Eigen::Matrix to be converted
|
|
|
+ * @param frame_id tf frame_id
|
|
|
+ * @param child_frame_id tf child frame_id
|
|
|
+ * @return converted TransformStamped
|
|
|
+ */
|
|
|
+geometry_msgs::TransformStamped Laser_Odom::matrix2transform(const ros::Time& stamp, const Eigen::Matrix4f& pose, const std::string& frame_id, const std::string& child_frame_id) {
|
|
|
+ Eigen::Quaternionf quat(pose.block<3, 3>(0, 0));
|
|
|
+ quat.normalize();
|
|
|
+ geometry_msgs::Quaternion odom_quat;
|
|
|
+ odom_quat.w = quat.w();
|
|
|
+ odom_quat.x = quat.x();
|
|
|
+ odom_quat.y = quat.y();
|
|
|
+ odom_quat.z = quat.z();
|
|
|
+
|
|
|
+ geometry_msgs::TransformStamped odom_trans;
|
|
|
+ odom_trans.header.stamp = stamp;
|
|
|
+ odom_trans.header.frame_id = frame_id;
|
|
|
+ odom_trans.child_frame_id = child_frame_id;
|
|
|
+
|
|
|
+ odom_trans.transform.translation.x = pose(0, 3);
|
|
|
+ odom_trans.transform.translation.y = pose(1, 3);
|
|
|
+ odom_trans.transform.translation.z = pose(2, 3);
|
|
|
+ odom_trans.transform.rotation = odom_quat;
|
|
|
+
|
|
|
+ return odom_trans;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+ * @brief publish odometry
|
|
|
+ * @param stamp timestamp
|
|
|
+ * @param pose odometry pose to be published
|
|
|
+ */
|
|
|
+void Laser_Odom::publish_odometry(const ros::Time& stamp, const std::string& base_frame_id, const Eigen::Matrix4f& pose) {
|
|
|
+
|
|
|
+
|
|
|
+ geometry_msgs::TransformStamped odom_trans = matrix2transform(stamp, pose, "odom", base_frame_id);
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ nav_msgs::Odometry odom;
|
|
|
+ odom.header.stamp = stamp;
|
|
|
+ odom.header.frame_id = "odom";
|
|
|
+ odom.child_frame_id = "base_link";
|
|
|
+
|
|
|
+ odom.pose.pose.position.x = pose(0, 3);
|
|
|
+ odom.pose.pose.position.y = pose(1, 3);
|
|
|
+ odom.pose.pose.position.z = pose(2, 3);
|
|
|
+ odom.pose.pose.orientation = odom_trans.transform.rotation;
|
|
|
+
|
|
|
+ odom.child_frame_id = base_frame_id;
|
|
|
+ odom.twist.twist.linear.x = 0.0;
|
|
|
+ odom.twist.twist.linear.y = 0.0;
|
|
|
+ odom.twist.twist.angular.z = 0.0;
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ odom_pub.publish(odom);
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|