当前位置: 首页 > news >正文

3d激光slam建图与定位(1)_基于ndt算法定位

一.代码实现流程
二.ndt算法原理

一.该算法定位有三个进程文件
1.map_loader.cpp用于点云地图的读取,从文件中读取点云后对这个点云地图进行旋转平移后发布点云地图到ros

	#include "map_loader.h"MapLoader::MapLoader(ros::NodeHandle &nh){std::string pcd_file_path, map_topic;// 点云地图读取路径nh.param<std::string>("pcd_path", pcd_file_path, "");// 点云地图话题名nh.param<std::string>("map_topic", map_topic, "point_map");//初始位置的相对位置坐标变换 参数读取init_tf_params(nh);//要发布的map话题pc_map_pub_ = nh.advertise<sensor_msgs::PointCloud2>(map_topic, 10, true);// 点云地图路径存储file_list_.push_back(pcd_file_path);//从文件中读取pcdauto pc_msg = CreatePcd();// 根据初始坐标变换进行点云地图的坐标变换auto out_msg = TransformMap(pc_msg);
//发布点云地图话题if (out_msg.width != 0) {out_msg.header.frame_id = "map";pc_map_pub_.publish(out_msg);}}void MapLoader::init_tf_params(ros::NodeHandle &nh){// 从参数中加载坐标系变换值nh.param<float>("x", tf_x_, 0.0);nh.param<float>("y", tf_y_, 0.0);nh.param<float>("z", tf_z_, 0.0);nh.param<float>("roll", tf_roll_, 0.0);nh.param<float>("pitch", tf_pitch_, 0.0);nh.param<float>("yaw", tf_yaw_, 0.0);ROS_INFO_STREAM("x" << tf_x_ <<"y: "<<tf_y_<<"z: "<<tf_z_<<"roll: "<<tf_roll_<<" pitch: "<< tf_pitch_<<"yaw: "<<tf_yaw_);
}sensor_msgs::PointCloud2 MapLoader::TransformMap(sensor_msgs::PointCloud2 & in){pcl::PointCloud<pcl::PointXYZ>::Ptr in_pc(new pcl::PointCloud<pcl::PointXYZ>);// 传感器点云格式转换为pcl点云格式pcl::fromROSMsg(in, *in_pc);pcl::PointCloud<pcl::PointXYZ>::Ptr transformed_pc_ptr(new pcl::PointCloud<pcl::PointXYZ>);// 坐标变换Eigen::Translation3f tl_m2w(tf_x_, tf_y_, tf_z_);                 // tl: translationEigen::AngleAxisf rot_x_m2w(tf_roll_, Eigen::Vector3f::UnitX());  // rot: rotationEigen::AngleAxisf rot_y_m2w(tf_pitch_, Eigen::Vector3f::UnitY());Eigen::AngleAxisf rot_z_m2w(tf_yaw_, Eigen::Vector3f::UnitZ());Eigen::Matrix4f tf_m2w = (tl_m2w * rot_z_m2w * rot_y_m2w * rot_x_m2w).matrix();pcl::transformPointCloud(*in_pc, *transformed_pc_ptr, tf_m2w);// 保存变换后的点云地图// SaveMap(transformed_pc_ptr);sensor_msgs::PointCloud2 output_msg;pcl::toROSMsg(*transformed_pc_ptr, output_msg);return output_msg;
}void MapLoader::SaveMap(const pcl::PointCloud<pcl::PointXYZ>::Ptr map_pc_ptr){pcl::io::savePCDFile("/tmp/transformed_map.pcd", *map_pc_ptr);
}sensor_msgs::PointCloud2 MapLoader::CreatePcd()
{sensor_msgs::PointCloud2 pcd, part;for (const std::string& path : file_list_) {if (pcd.width == 0) {if (pcl::io::loadPCDFile(path.c_str(), pcd) == -1) {std::cerr << "load failed " << path << std::endl;}} else{if (pcl::io::loadPCDFile(path.c_str(), part) == -1) {std::cerr << "load failed " << path << std::endl;}pcd.width += part.width;pcd.row_step += part.row_step;pcd.data.insert(pcd.data.end(), part.data.begin(), part.data.end());}std::cerr << "load " << path << std::endl;if (!ros::ok()) break;}return pcd;
}int main(int argc, char** argv)
{ros::init(argc, argv, "map_loader");ROS_INFO("点云地图读取并发布map话题");ros::NodeHandle nh("~");MapLoader map_loader(nh);ros::spin();return 0;
}

2.points_downsampler.cpp对雷达数据的预处理,当雷达数据回调后进行点云范围的过滤,过滤掉过远的点,通过体素滤波进行下采样,提升匹配效率

#include <ros/ros.h>
#include <sensor_msgs/PointCloud2.h>#include <pcl/point_types.h>
#include <pcl_conversions/pcl_conversions.h>
#include <pcl/filters/voxel_grid.h>// #include "points_downsampler.h"#define MAX_MEASUREMENT_RANGE 120.0ros::Publisher filtered_points_pub;// Leaf size of VoxelGrid filter.
static double voxel_leaf_size = 2.0;static bool _output_log = false;
static std::ofstream ofs;
static std::string filename;static std::string POINTS_TOPIC;
// 移除超出搜索半径的点
static pcl::PointCloud<pcl::PointXYZ> removePointsByRange(pcl::PointCloud<pcl::PointXYZ> scan, double min_range, double max_range)
{pcl::PointCloud<pcl::PointXYZ> narrowed_scan;narrowed_scan.header = scan.header;if( min_range>=max_range ) {ROS_ERROR_ONCE("min_range>=max_range @(%lf, %lf)", min_range, max_range );return scan;}double square_min_range = min_range * min_range;double square_max_range = max_range * max_range;for(pcl::PointCloud<pcl::PointXYZ>::const_iterator iter = scan.begin(); iter != scan.end(); ++iter){const pcl::PointXYZ &p = *iter;// 点云点到原点的位置的欧式距离double square_distance = p.x * p.x + p.y * p.y;if(square_min_range <= square_distance && square_distance <= square_max_range){narrowed_scan.points.push_back(p);}}return narrowed_scan;
}static void scan_callback(const sensor_msgs::PointCloud2::ConstPtr& input)
{pcl::PointCloud<pcl::PointXYZ> scan;// 传感器点云转换为pcl点云pcl::fromROSMsg(*input, scan);// 移除距离原点中心过远的点scan = removePointsByRange(scan, 0, MAX_MEASUREMENT_RANGE);pcl::PointCloud<pcl::PointXYZ>::Ptr scan_ptr(new pcl::PointCloud<pcl::PointXYZ>(scan));pcl::PointCloud<pcl::PointXYZ>::Ptr filtered_scan_ptr(new pcl::PointCloud<pcl::PointXYZ>());sensor_msgs::PointCloud2 filtered_msg;//体素滤波器过滤点云if (voxel_leaf_size >= 0.1){// Downsampling the velodyne scan using VoxelGrid filterpcl::VoxelGrid<pcl::PointXYZ> voxel_grid_filter;voxel_grid_filter.setLeafSize(voxel_leaf_size, voxel_leaf_size, voxel_leaf_size);voxel_grid_filter.setInputCloud(scan_ptr);voxel_grid_filter.filter(*filtered_scan_ptr);pcl::toROSMsg(*filtered_scan_ptr, filtered_msg);}else{pcl::toROSMsg(*scan_ptr, filtered_msg);}filtered_msg.header = input->header;// 发布体素滤波之后的点云filtered_points_pub.publish(filtered_msg);}int main(int argc, char** argv)
{ros::init(argc, argv, "voxel_grid_filter");ros::NodeHandle nh;ros::NodeHandle private_nh("~");
// 点云话题名private_nh.getParam("points_topic", POINTS_TOPIC);// private_nh.getParam("output_log", _output_log);
//体素滤波器格子大小private_nh.param<double>("leaf_size", voxel_leaf_size, 2.0);ROS_INFO_STREAM("Voxel leaf size is: "<<voxel_leaf_size);// 写入log到本地if(_output_log == true){char buffer[80];std::time_t now = std::time(NULL);std::tm *pnow = std::localtime(&now);std::strftime(buffer,80,"%Y%m%d_%H%M%S",pnow);filename = "voxel_grid_filter_" + std::string(buffer) + ".csv";ofs.open(filename.c_str(), std::ios::app);}//发布滤波后的点云话题filtered_points_pub = nh.advertise<sensor_msgs::PointCloud2>("/filtered_points", 10);// 订阅从雷达拿下来的点云话题ros::Subscriber scan_sub = nh.subscribe(POINTS_TOPIC, 10, scan_callback);ros::spin();return 0;
}

3.ndt.cpp对点云地图,雷达点云,发布的雷达点云相对于点云地图的初始位置进行回调处理,通过ndt算法,将雷达点云匹配到点云地图,并计算出它们的位置姿态关系,这个就是定位

#include "ndt.h"NdtLocalizer::NdtLocalizer(ros::NodeHandle &nh, ros::NodeHandle &private_nh):nh_(nh), private_nh_(private_nh), tf2_listener_(tf2_buffer_){
//添加一个名为state的键 并将值设置为Initializingkey_value_stdmap_["state"] = "Initializing";// 初始参数读取init_params();// Publisherssensor_aligned_pose_pub_ = nh_.advertise<sensor_msgs::PointCloud2>("points_aligned", 10);ndt_pose_pub_ = nh_.advertise<geometry_msgs::PoseStamped>("ndt_pose", 10);exe_time_pub_ = nh_.advertise<std_msgs::Float32>("exe_time_ms", 10);transform_probability_pub_ = nh_.advertise<std_msgs::Float32>("transform_probability", 10);iteration_num_pub_ = nh_.advertise<std_msgs::Float32>("iteration_num", 10);diagnostics_pub_ = nh_.advertise<diagnostic_msgs::DiagnosticArray>("diagnostics", 10);// Subscribers// 重定位设置initial_pose_sub_ = nh_.subscribe("initialpose", 100, &NdtLocalizer::callback_init_pose, this);// 点云地图map_points_sub_ = nh_.subscribe("points_map", 1, &NdtLocalizer::callback_pointsmap, this);// 经过下采样之后的点云sensor_points_sub_ = nh_.subscribe("filtered_points", 1, &NdtLocalizer::callback_pointcloud, this);
// // 创建个独立线程去执行timer_diagnostic
//   diagnostic_thread_ = std::thread(&NdtLocalizer::timer_diagnostic, this);
//   diagnostic_thread_.detach();
}NdtLocalizer::~NdtLocalizer() {}void NdtLocalizer::timer_diagnostic()
{// 100hzros::Rate rate(100);while (ros::ok()) {diagnostic_msgs::DiagnosticStatus diag_status_msg;diag_status_msg.name = "ndt_scan_matcher";diag_status_msg.hardware_id = "";for (const auto & key_value : key_value_stdmap_) {diagnostic_msgs::KeyValue key_value_msg;// 键statekey_value_msg.key = key_value.first;//值 Initializingkey_value_msg.value = key_value.second;diag_status_msg.values.push_back(key_value_msg);}diag_status_msg.level = diagnostic_msgs::DiagnosticStatus::OK;diag_status_msg.message = "";if (key_value_stdmap_.count("state") && key_value_stdmap_["state"] == "Initializing") {diag_status_msg.level = diagnostic_msgs::DiagnosticStatus::WARN;diag_status_msg.message += "Initializing State. ";}if (key_value_stdmap_.count("skipping_publish_num") &&std::stoi(key_value_stdmap_["skipping_publish_num"]) > 1) {diag_status_msg.level = diagnostic_msgs::DiagnosticStatus::WARN;diag_status_msg.message += "skipping_publish_num > 1. ";}if (key_value_stdmap_.count("skipping_publish_num") &&std::stoi(key_value_stdmap_["skipping_publish_num"]) >= 5) {diag_status_msg.level = diagnostic_msgs::DiagnosticStatus::ERROR;diag_status_msg.message += "skipping_publish_num exceed limit. ";}diagnostic_msgs::DiagnosticArray diag_msg;diag_msg.header.stamp = ros::Time::now();diag_msg.status.push_back(diag_status_msg);diagnostics_pub_.publish(diag_msg);rate.sleep();}
}void NdtLocalizer::callback_init_pose(const geometry_msgs::PoseWithCovarianceStamped::ConstPtr & initial_pose_msg_ptr)
{// base_link to mapif (initial_pose_msg_ptr->header.frame_id == map_frame_) {initial_pose_cov_msg_ = *initial_pose_msg_ptr;}else{// get TF from pose_frame to map_framegeometry_msgs::TransformStamped::Ptr TF_pose_to_map_ptr(new geometry_msgs::TransformStamped);get_transform(map_frame_, initial_pose_msg_ptr->header.frame_id, TF_pose_to_map_ptr);// transform pose_frame to map_framegeometry_msgs::PoseWithCovarianceStamped::Ptr mapTF_initial_pose_msg_ptr(new geometry_msgs::PoseWithCovarianceStamped);tf2::doTransform(*initial_pose_msg_ptr, *mapTF_initial_pose_msg_ptr, *TF_pose_to_map_ptr);initial_pose_cov_msg_ = *mapTF_initial_pose_msg_ptr;}// if click the initpose again, re init!init_pose = false;
}
// ndt的目标点云部分 通过ndt_new 赋值给ndt 也就是 点云地图
void NdtLocalizer::callback_pointsmap(const sensor_msgs::PointCloud2::ConstPtr & map_points_msg_ptr)
{const auto trans_epsilon = ndt_.getTransformationEpsilon();const auto step_size = ndt_.getStepSize();const auto resolution = ndt_.getResolution();const auto max_iterations = ndt_.getMaximumIterations();pcl::NormalDistributionsTransform<pcl::PointXYZ, pcl::PointXYZ> ndt_new;ndt_new.setTransformationEpsilon(trans_epsilon);ndt_new.setStepSize(step_size);ndt_new.setResolution(resolution);ndt_new.setMaximumIterations(max_iterations);pcl::PointCloud<pcl::PointXYZ>::Ptr map_points_ptr(new pcl::PointCloud<pcl::PointXYZ>);pcl::fromROSMsg(*map_points_msg_ptr, *map_points_ptr);// 设置目标点云ndt_new.setInputTarget(map_points_ptr);// create Thread// detachpcl::PointCloud<pcl::PointXYZ>::Ptr output_cloud(new pcl::PointCloud<pcl::PointXYZ>);// 执行ndt计算ndt_new.align(*output_cloud, Eigen::Matrix4f::Identity());// swapndt_map_mtx_.lock();ndt_ = ndt_new;ndt_map_mtx_.unlock();
}void NdtLocalizer::callback_pointcloud(const sensor_msgs::PointCloud2::ConstPtr & sensor_points_sensorTF_msg_ptr)
{// 时间戳const auto exe_start_time = std::chrono::system_clock::now();// mutex Mapstd::lock_guard<std::mutex> lock(ndt_map_mtx_);
// lidar frameconst std::string sensor_frame = sensor_points_sensorTF_msg_ptr->header.frame_id;//点云进来的时间戳const auto sensor_ros_time = sensor_points_sensorTF_msg_ptr->header.stamp;boost::shared_ptr<pcl::PointCloud<pcl::PointXYZ>> sensor_points_sensorTF_ptr(new pcl::PointCloud<pcl::PointXYZ>);// 传感器点云转换到pcl点云pcl::fromROSMsg(*sensor_points_sensorTF_msg_ptr, *sensor_points_sensorTF_ptr);// get TF base to sensorgeometry_msgs::TransformStamped::Ptr TF_base_to_sensor_ptr(new geometry_msgs::TransformStamped);// 获取map to base_link的坐标变换get_transform(base_frame_, sensor_frame, TF_base_to_sensor_ptr);const Eigen::Affine3d base_to_sensor_affine = tf2::transformToEigen(*TF_base_to_sensor_ptr);const Eigen::Matrix4f base_to_sensor_matrix = base_to_sensor_affine.matrix().cast<float>();boost::shared_ptr<pcl::PointCloud<pcl::PointXYZ>> sensor_points_baselinkTF_ptr(new pcl::PointCloud<pcl::PointXYZ>);// 将点云坐标变换到map坐标系下pcl::transformPointCloud(*sensor_points_sensorTF_ptr, *sensor_points_baselinkTF_ptr, base_to_sensor_matrix);//设置ndt的输入点云ndt_.setInputSource(sensor_points_baselinkTF_ptr);if (ndt_.getInputTarget() == nullptr) {ROS_WARN_STREAM_THROTTLE(1, "No MAP!");return;}// alignEigen::Matrix4f initial_pose_matrix;// 如果进行重定位设置了if (!init_pose){Eigen::Affine3d initial_pose_affine;// 将ros格式的初始位姿发布 转换成 eigen格式tf2::fromMsg(initial_pose_cov_msg_.pose.pose, initial_pose_affine);initial_pose_matrix = initial_pose_affine.matrix().cast<float>();//将重定位的发布赋值给pre_transpre_trans = initial_pose_matrix;// 只执行一次的目的init_pose = true;}else{//ndt初始计算位置初值  当前变换矩阵*(当前与上一次之间的变换矩阵)initial_pose_matrix = pre_trans * delta_trans;}pcl::PointCloud<pcl::PointXYZ>::Ptr output_cloud(new pcl::PointCloud<pcl::PointXYZ>);const auto align_start_time = std::chrono::system_clock::now();// 键的值改变来确定ndt执行状态key_value_stdmap_["state"] = "Aligning";// 执行ndtndt_.align(*output_cloud, initial_pose_matrix);// 键的值改变来确定ndt执行状态key_value_stdmap_["state"] = "Sleeping";// 时间戳const auto align_end_time = std::chrono::system_clock::now();// 单纯的ndt执行时间差值const double align_time = std::chrono::duration_cast<std::chrono::microseconds>(align_end_time - align_start_time).count() /1000.0;
//ndt变换得到的目标点云和输入点云的关系const Eigen::Matrix4f result_pose_matrix = ndt_.getFinalTransformation();Eigen::Affine3d result_pose_affine;result_pose_affine.matrix() = result_pose_matrix.cast<double>();//定位结果const geometry_msgs::Pose result_pose_msg = tf2::toMsg(result_pose_affine);const auto exe_end_time = std::chrono::system_clock::now();// 执行一次ndt的时间差值,包括获取目标点云与实际点云的坐标关系流程const double exe_time = std::chrono::duration_cast<std::chrono::microseconds>(exe_end_time - exe_start_time).count() / 1000.0;
// ndt变换概率const float transform_probability = ndt_.getTransformationProbability();//ndt的最终迭代次数const int iteration_num = ndt_.getFinalNumIteration();bool is_converged = true;static size_t skipping_publish_num = 0;if (iteration_num >= ndt_.getMaximumIterations() + 2 ||transform_probability < converged_param_transform_probability_) {is_converged = false;++skipping_publish_num;std::cout << "Not Converged" << std::endl;} else {skipping_publish_num = 0;}// 目标点云与当前点云的姿态关系delta_trans = pre_trans.inverse() * result_pose_matrix;
// 位置向量Eigen::Vector3f delta_translation = delta_trans.block<3, 1>(0, 3);std::cout<<"delta x: "<<delta_translation(0) << " y: "<<delta_translation(1)<<" z: "<<delta_translation(2)<<std::endl;
// 旋转向量Eigen::Matrix3f delta_rotation_matrix = delta_trans.block<3, 3>(0, 0);Eigen::Vector3f delta_euler = delta_rotation_matrix.eulerAngles(2,1,0);std::cout<<"delta yaw: "<<delta_euler(0) << " pitch: "<<delta_euler(1)<<" roll: "<<delta_euler(2)<<std::endl;pre_trans = result_pose_matrix;// publishgeometry_msgs::PoseStamped result_pose_stamped_msg;result_pose_stamped_msg.header.stamp = sensor_ros_time;result_pose_stamped_msg.header.frame_id = map_frame_;result_pose_stamped_msg.pose = result_pose_msg;if (is_converged) {// 发布定位结果ndt_pose_pub_.publish(result_pose_stamped_msg);}//发布map to base_link的坐标关系publish_tf(map_frame_, base_frame_, result_pose_stamped_msg);
std::cout<<"发布了坐标变换"<<map_frame_<<"dsa"<<base_frame_<<std::endl;// publish aligned point cloudpcl::PointCloud<pcl::PointXYZ>::Ptr sensor_points_mapTF_ptr(new pcl::PointCloud<pcl::PointXYZ>);pcl::transformPointCloud(*sensor_points_baselinkTF_ptr, *sensor_points_mapTF_ptr, result_pose_matrix);sensor_msgs::PointCloud2 sensor_points_mapTF_msg;pcl::toROSMsg(*sensor_points_mapTF_ptr, sensor_points_mapTF_msg);sensor_points_mapTF_msg.header.stamp = sensor_ros_time;sensor_points_mapTF_msg.header.frame_id = map_frame_;sensor_aligned_pose_pub_.publish(sensor_points_mapTF_msg);std_msgs::Float32 exe_time_msg;exe_time_msg.data = exe_time;exe_time_pub_.publish(exe_time_msg);std_msgs::Float32 transform_probability_msg;transform_probability_msg.data = transform_probability;transform_probability_pub_.publish(transform_probability_msg);std_msgs::Float32 iteration_num_msg;iteration_num_msg.data = iteration_num;iteration_num_pub_.publish(iteration_num_msg);key_value_stdmap_["seq"] = std::to_string(sensor_points_sensorTF_msg_ptr->header.seq);key_value_stdmap_["transform_probability"] = std::to_string(transform_probability);key_value_stdmap_["iteration_num"] = std::to_string(iteration_num);key_value_stdmap_["skipping_publish_num"] = std::to_string(skipping_publish_num);std::cout << "------------------------------------------------" << std::endl;std::cout << "align_time: " << align_time << "ms" << std::endl;std::cout << "exe_time: " << exe_time << "ms" << std::endl;std::cout << "trans_prob: " << transform_probability << std::endl;std::cout << "iter_num: " << iteration_num << std::endl;std::cout << "skipping_publish_num: " << skipping_publish_num << std::endl;
}void NdtLocalizer::init_params(){
// 底盘的idprivate_nh_.getParam("base_frame", base_frame_);ROS_INFO("base_frame_id: %s", base_frame_.c_str());//收敛判定阈值 两次迭代之间的矩阵变化量小于该值认为已经收敛 停止迭代double trans_epsilon = ndt_.getTransformationEpsilon();// 移动步长 格子大小double step_size = ndt_.getStepSize();// 点云分辨率double resolution = ndt_.getResolution();// 迭代次数int max_iterations = ndt_.getMaximumIterations();
// 从配置中读取这几个参数private_nh_.getParam("trans_epsilon", trans_epsilon);private_nh_.getParam("step_size", step_size);private_nh_.getParam("resolution", resolution);private_nh_.getParam("max_iterations", max_iterations);map_frame_ = "map";
// 进行ndt参数设置ndt_.setTransformationEpsilon(trans_epsilon);ndt_.setStepSize(step_size);ndt_.setResolution(resolution);ndt_.setMaximumIterations(max_iterations);private_nh_.getParam("converged_param_transform_probability", converged_param_transform_probability_);
}bool NdtLocalizer::get_transform(const std::string & target_frame, const std::string & source_frame,const geometry_msgs::TransformStamped::Ptr & transform_stamped_ptr, const ros::Time & time_stamp)
{if (target_frame == source_frame) {transform_stamped_ptr->header.stamp = time_stamp;transform_stamped_ptr->header.frame_id = target_frame;transform_stamped_ptr->child_frame_id = source_frame;transform_stamped_ptr->transform.translation.x = 0.0;transform_stamped_ptr->transform.translation.y = 0.0;transform_stamped_ptr->transform.translation.z = 0.0;transform_stamped_ptr->transform.rotation.x = 0.0;transform_stamped_ptr->transform.rotation.y = 0.0;transform_stamped_ptr->transform.rotation.z = 0.0;transform_stamped_ptr->transform.rotation.w = 1.0;return true;}try {*transform_stamped_ptr =tf2_buffer_.lookupTransform(target_frame, source_frame, time_stamp);} catch (tf2::TransformException & ex) {ROS_WARN("%s", ex.what());ROS_ERROR("Please publish TF %s to %s", target_frame.c_str(), source_frame.c_str());transform_stamped_ptr->header.stamp = time_stamp;transform_stamped_ptr->header.frame_id = target_frame;transform_stamped_ptr->child_frame_id = source_frame;transform_stamped_ptr->transform.translation.x = 0.0;transform_stamped_ptr->transform.translation.y = 0.0;transform_stamped_ptr->transform.translation.z = 0.0;transform_stamped_ptr->transform.rotation.x = 0.0;transform_stamped_ptr->transform.rotation.y = 0.0;transform_stamped_ptr->transform.rotation.z = 0.0;transform_stamped_ptr->transform.rotation.w = 1.0;return false;}return true;
}
// 返回baseid to map 的坐标变换
bool NdtLocalizer::get_transform(const std::string & target_frame, const std::string & source_frame,const geometry_msgs::TransformStamped::Ptr & transform_stamped_ptr)
{// 如果id相同 则坐标变换为0if (target_frame == source_frame) {transform_stamped_ptr->header.stamp = ros::Time::now();transform_stamped_ptr->header.frame_id = target_frame;transform_stamped_ptr->child_frame_id = source_frame;transform_stamped_ptr->transform.translation.x = 0.0;transform_stamped_ptr->transform.translation.y = 0.0;transform_stamped_ptr->transform.translation.z = 0.0;transform_stamped_ptr->transform.rotation.x = 0.0;transform_stamped_ptr->transform.rotation.y = 0.0;transform_stamped_ptr->transform.rotation.z = 0.0;transform_stamped_ptr->transform.rotation.w = 1.0;return true;}try {*transform_stamped_ptr =tf2_buffer_.lookupTransform(target_frame, source_frame, ros::Time(0), ros::Duration(1.0));} catch (tf2::TransformException & ex) {ROS_WARN("%s", ex.what());ROS_ERROR("Please publish TF %s to %s", target_frame.c_str(), source_frame.c_str());transform_stamped_ptr->header.stamp = ros::Time::now();transform_stamped_ptr->header.frame_id = target_frame;transform_stamped_ptr->child_frame_id = source_frame;transform_stamped_ptr->transform.translation.x = 0.0;transform_stamped_ptr->transform.translation.y = 0.0;transform_stamped_ptr->transform.translation.z = 0.0;transform_stamped_ptr->transform.rotation.x = 0.0;transform_stamped_ptr->transform.rotation.y = 0.0;transform_stamped_ptr->transform.rotation.z = 0.0;transform_stamped_ptr->transform.rotation.w = 1.0;return false;}return true;
}void NdtLocalizer::publish_tf(const std::string & frame_id, const std::string & child_frame_id,const geometry_msgs::PoseStamped & pose_msg)
{geometry_msgs::TransformStamped transform_stamped;transform_stamped.header.frame_id = frame_id;transform_stamped.child_frame_id = child_frame_id;transform_stamped.header.stamp = pose_msg.header.stamp;transform_stamped.transform.translation.x = pose_msg.pose.position.x;transform_stamped.transform.translation.y = pose_msg.pose.position.y;transform_stamped.transform.translation.z = pose_msg.pose.position.z;tf2::Quaternion tf_quaternion;tf2::fromMsg(pose_msg.pose.orientation, tf_quaternion);transform_stamped.transform.rotation.x = tf_quaternion.x();transform_stamped.transform.rotation.y = tf_quaternion.y();transform_stamped.transform.rotation.z = tf_quaternion.z();transform_stamped.transform.rotation.w = tf_quaternion.w();tf2_broadcaster_.sendTransform(transform_stamped);
}int main(int argc, char **argv)
{ros::init(argc, argv, "ndt_localizer");ros::NodeHandle nh;ros::NodeHandle private_nh("~");NdtLocalizer ndt_localizer(nh, private_nh);ros::spin();return 0;
}

二.ndt算法原理
1.通过将点云分成多个体素格子
2.计算格子内的协方差与均值,得到概率模型
在这里插入图片描述
3.变换输入点云,与目标点云配准得到坐标变换关系
在这里插入图片描述
4.根据正态分布参数计算每个转换点落在对应cell中的概率
在这里插入图片描述

5.NDT配准得分(score):计算对应点落在对应网格cell中的概率之和
在这里插入图片描述

6.根据牛顿优化算法对目标函数score进行优化,即寻找变换参数p使得 score的值最大。优化的关键步骤是要计算目标函数的梯度和Hessian矩阵:
在这里插入图片描述
7.跳转到第3步重复执行,直到满足收敛条件结束

运行效果
在这里插入图片描述

参考:https://blog.csdn.net/weixin_41469272/article/details/105622447
代码:git clone https://github.com/AbangLZU/ndt_localizer.git

相关文章:

3d激光slam建图与定位(1)_基于ndt算法定位

一.代码实现流程 二.ndt算法原理 一.该算法定位有三个进程文件 1.map_loader.cpp用于点云地图的读取&#xff0c;从文件中读取点云后对这个点云地图进行旋转平移后发布点云地图到ros #include "map_loader.h"MapLoader::MapLoader(ros::NodeHandle &nh){std::st…...

云安全攻防(二)之 云原生安全

云原生安全 什么是云原生安全&#xff1f;云原生安全包含两层含义&#xff1a;面向云原生环境的安全和具有云原生特征的安全 面向云原生环境的安全 面向云原生环境的安全的目标是防护云原生环境中的基础设施、编排系统和微服务系统的安全。这类安全机制不一定会具有云原生的…...

最后的组合:K8s 1.24 基于 Hekiti 实现 GlusterFS 动态存储管理实践

前言 知识点 定级&#xff1a;入门级GlusterFS 和 Heketi 简介GlusterFS 安装部署Heketi 安装部署Kubernetes 命令行对接 GlusterFS 实战服务器配置(架构 1:1 复刻小规模生产环境&#xff0c;配置略有不同) 主机名IPCPU内存系统盘数据盘用途ks-master-0192.168.9.912450100…...

笙默考试管理系统-MyExamTest(16)

笙默考试管理系统-MyExamTest&#xff08;16&#xff09; 目录 一、 笙默考试管理系统-MyExamTest 二、 笙默考试管理系统-MyExamTest 三、 笙默考试管理系统-MyExamTest 四、 笙默考试管理系统-MyExamTest 五、 笙默考试管理系统-MyExamTest 笙默考试管理系统-MyExa…...

初级算法-树

文章目录 二叉树的最大深度题意&#xff1a;解&#xff1a;代码&#xff1a; 验证二叉搜索树题意&#xff1a;解&#xff1a;代码&#xff1a; 对称二叉树题意&#xff1a;解&#xff1a;代码&#xff1a; 二叉树的层序遍历题意&#xff1a;解&#xff1a;代码&#xff1a; 将有…...

Harbor Failed to start docker.service: Unit docker.service not found.

有可能是修改配置文件导致了问题&#xff0c;最近肯定修改过某个配置文件 本文只针对配置Harbor过程中遇到该问题&#xff0c;很有是deamon.json的 insecure-registries和docker.service的 ExecStart/usr/bin/dockerd --insecure-registry冲突了&#xff0c;删掉一个就好 我使…...

网络安全/信息安全(黑客技术)自学笔记

一、网络安全基础知识 1.计算机基础知识 了解了计算机的硬件、软件、操作系统和网络结构等基础知识&#xff0c;可以帮助您更好地理解网络安全的概念和技术。 2.网络基础知识 了解了网络的结构、协议、服务和安全问题&#xff0c;可以帮助您更好地解决网络安全的原理和技术…...

ADB 命令结合 monkey 的简单使用,超详细

一&#xff1a;ADB简介 1&#xff0c;什么是adb&#xff1a; ADB 全称为 Android Debug Bridge&#xff0c;起到调试桥的作用&#xff0c;是一个客户端-服务器端程序。其中客户端是用来操作的电脑&#xff0c;服务端是 Android 设备。ADB 也是 Android SDK 中的一个工具&…...

级联选择框

文章目录 实现级联选择框效果图实现前端工具版本添加依赖main.js导入依赖级联选择框样式 后端数据库设计 实现级联选择框 效果图 实现 前端 工具版本 node.js v16.6.0vue3 级联选择框使用 Element-Plus 实现 添加依赖 在 package.json 添加依赖&#xff0c;并 npm i 导入…...

如何在3ds max中创建可用于真人场景的巨型机器人:第 5 部分

推荐&#xff1a; NSDT场景编辑器助你快速搭建可二次开发的3D应用场景 1. After Effects 中的项目设置 步骤 1 打开“后效”。 打开后效果 步骤 2 我有真人版 我在After Effects中导入的素材。这是将 用作与机器人动画合成的背景素材。 实景镜头 步骤 3 有背景 选定的素材…...

【MATLAB第61期】基于MATLAB的GMM高斯混合模型回归数据预测

【MATLAB第61期】基于MATLAB的GMM高斯混合模型回归数据预测 高斯混合模型GMM广泛应用于数据挖掘、模式识别、机器学习和统计分析。其中&#xff0c;它们的参数通常由最大似然和EM算法确定。关键思想是使用高斯混合模型对数据&#xff08;包括输入和输出&#xff09;的联合概率…...

Mnist分类与气温预测任务

目录 传统机器学习与深度学习的特征工程特征向量pytorch实现minist代码解析归一化损失函数计算图Mnist分类获取Mnist数据集&#xff0c;预处理&#xff0c;输出一张图像面向工具包编程使用TensorDataset和DataLoader来简化数据预处理计算验证集准确率 气温预测回归构建神经网络…...

Pytorch深度学习-----神经网络的卷积操作

系列文章目录 PyTorch深度学习——Anaconda和PyTorch安装 Pytorch深度学习-----数据模块Dataset类 Pytorch深度学习------TensorBoard的使用 Pytorch深度学习------Torchvision中Transforms的使用&#xff08;ToTensor&#xff0c;Normalize&#xff0c;Resize &#xff0c;Co…...

微信小程序转抖音小程序的坑:The component <xxx> used in pages/xxx/xxx is undefined

微信小程序组件定义在根目录的 app.json 中了&#xff0c;在抖音小程序中出现找不到的情况。 在需要用到组件的 pages 目录中页面文件夹的 json "usingComponents": {} 大括号中添加页面使用的组件&#xff0c;即可使用......

Vue+element Ui的el-select同时获取value和label的方法总结

1.通过ref的形式&#xff08;推荐) <template><div class"root"><el-selectref"optionRef"change"handleChange"v-model"value"placeholder"请选择"style"width: 250px"><el-optionv-for&q…...

乐划锁屏充分发挥强创新能力,打造内容业新生态

乐划锁屏作为新型内容媒体,在这一市场有着众多独特的优势,不仅能够通过多场景的联动给内容创作者带来了更多可能性,还促进了更多优质作品的诞生,为用户带来更加丰富多彩的锁屏使用体验。 作为OPPO系统原生的OS应用,乐划锁屏一直致力于打造为用户提供至美内容的内容平台,吸引了全…...

防御第三天

1.总结当堂NAT与双机热备原理&#xff0c;形成思维导图 2.完成课堂NAT与双机热备实验 fw1: <USG6000V1>sy [USG6000V1]int g0/0/0 [USG6000V1-GigabitEthernet0/0/0]ip add 192.168.18.2 24 [USG6000V1-GigabitEthernet0/0/0]service-manage all permit (地址无所谓&…...

用JavaScript和HTML实现一个精美的计算器

文章目录 一、前言二、技术栈三、功能实现3.1 引入样式3.2 编写显示页面3.2 美化计算器页面3.3 实现计算器逻辑 四、总结 一、前言 计算器是我们日常生活中经常使用的工具之一&#xff0c;可以帮助我们进行简单的数学运算。在本博文中&#xff0c;我将使用JavaScript编写一个漂…...

基于postgresl的gaussDB(DWS)地址省市区解析函数

地址格式为&#xff1a; 省(自治区&#xff0c;直辖市)、市、区。 直辖市的地址格式为&#xff0c; 北京市北京市海淀区xxxxx。 若是北京市海淀区xxx&#xff0c;自己改改就可以了 采用的是笨办法&#xff0c;穷举。 涉及的两个主要内置函数。 1. instr( <start_positio…...

【Golang】Golang进阶系列教程--Go 语言 new 和 make 关键字的区别

文章目录 前言new源码使用 make源码使用 总结 前言 本篇文章来介绍一道非常常见的面试题&#xff0c;到底有多常见呢&#xff1f;可能很多面试的开场白就是由此开始的。那就是 new 和 make 这两个内置函数的区别。 在 Go 语言中&#xff0c;有两个比较雷同的内置函数&#xf…...

Chapter03-Authentication vulnerabilities

文章目录 1. 身份验证简介1.1 What is authentication1.2 difference between authentication and authorization1.3 身份验证机制失效的原因1.4 身份验证机制失效的影响 2. 基于登录功能的漏洞2.1 密码爆破2.2 用户名枚举2.3 有缺陷的暴力破解防护2.3.1 如果用户登录尝试失败次…...

TDengine 快速体验(Docker 镜像方式)

简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能&#xff0c;本节首先介绍如何通过 Docker 快速体验 TDengine&#xff0c;然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker&#xff0c;请使用 安装包的方式快…...

python/java环境配置

环境变量放一起 python&#xff1a; 1.首先下载Python Python下载地址&#xff1a;Download Python | Python.org downloads ---windows -- 64 2.安装Python 下面两个&#xff0c;然后自定义&#xff0c;全选 可以把前4个选上 3.环境配置 1&#xff09;搜高级系统设置 2…...

【SpringBoot】100、SpringBoot中使用自定义注解+AOP实现参数自动解密

在实际项目中,用户注册、登录、修改密码等操作,都涉及到参数传输安全问题。所以我们需要在前端对账户、密码等敏感信息加密传输,在后端接收到数据后能自动解密。 1、引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId...

反射获取方法和属性

Java反射获取方法 在Java中&#xff0c;反射&#xff08;Reflection&#xff09;是一种强大的机制&#xff0c;允许程序在运行时访问和操作类的内部属性和方法。通过反射&#xff0c;可以动态地创建对象、调用方法、改变属性值&#xff0c;这在很多Java框架中如Spring和Hiberna…...

拉力测试cuda pytorch 把 4070显卡拉满

import torch import timedef stress_test_gpu(matrix_size16384, duration300):"""对GPU进行压力测试&#xff0c;通过持续的矩阵乘法来最大化GPU利用率参数:matrix_size: 矩阵维度大小&#xff0c;增大可提高计算复杂度duration: 测试持续时间&#xff08;秒&…...

今日科技热点速览

&#x1f525; 今日科技热点速览 &#x1f3ae; 任天堂Switch 2 正式发售 任天堂新一代游戏主机 Switch 2 今日正式上线发售&#xff0c;主打更强图形性能与沉浸式体验&#xff0c;支持多模态交互&#xff0c;受到全球玩家热捧 。 &#x1f916; 人工智能持续突破 DeepSeek-R1&…...

AI书签管理工具开发全记录(十九):嵌入资源处理

1.前言 &#x1f4dd; 在上一篇文章中&#xff0c;我们完成了书签的导入导出功能。本篇文章我们研究如何处理嵌入资源&#xff0c;方便后续将资源打包到一个可执行文件中。 2.embed介绍 &#x1f3af; Go 1.16 引入了革命性的 embed 包&#xff0c;彻底改变了静态资源管理的…...

均衡后的SNRSINR

本文主要摘自参考文献中的前两篇&#xff0c;相关文献中经常会出现MIMO检测后的SINR不过一直没有找到相关数学推到过程&#xff0c;其中文献[1]中给出了相关原理在此仅做记录。 1. 系统模型 复信道模型 n t n_t nt​ 根发送天线&#xff0c; n r n_r nr​ 根接收天线的 MIMO 系…...

智能AI电话机器人系统的识别能力现状与发展水平

一、引言 随着人工智能技术的飞速发展&#xff0c;AI电话机器人系统已经从简单的自动应答工具演变为具备复杂交互能力的智能助手。这类系统结合了语音识别、自然语言处理、情感计算和机器学习等多项前沿技术&#xff0c;在客户服务、营销推广、信息查询等领域发挥着越来越重要…...