autoware.universe源码略读(3.7)--perception:elevation_map_loader/euclidean_cluster

autoware.universe源码略读3.7--perception:elevation_map_loader/euclidean_cluster

  • elevation_map_loader
  • euclidean_cluster
    • euclidean_cluster
    • voxel_grid_based_euclidean_cluster
    • 节点类
    • launch文件

elevation_map_loader

在这里插入图片描述

在上一篇文章有提到compare_map_segmentation这个滤波的操作,这个模块就是生成海拔图用的。这个也是很标准的一个节点类(在网上又看了一点ROS2的知识,似乎叫组件更合理一些?) 这里我们可以看到,使用到了grid_map_pcl_loader_这样一个变量

  grid_map_pcl_loader_ = pcl::make_shared<grid_map::GridMapPclLoader>(grid_map_logger);grid_map_pcl_loader_->loadParameters(param_file_path);

然后在话题的订阅与发布中,分别订阅了三个话题:map_hash,pointcloud_map以及vector_map

  sub_map_hash_ = create_subscription<tier4_external_api_msgs::msg::MapHash>("/api/autoware/get/map/info/hash", durable_qos,std::bind(&ElevationMapLoaderNode::onMapHash, this, _1));sub_pointcloud_map_ = this->create_subscription<sensor_msgs::msg::PointCloud2>("input/pointcloud_map", durable_qos,std::bind(&ElevationMapLoaderNode::onPointcloudMap, this, _1));sub_vector_map_ = this->create_subscription<autoware_auto_mapping_msgs::msg::HADMapBin>("input/vector_map", durable_qos, std::bind(&ElevationMapLoaderNode::onVectorMap, this, _1));

在三个订阅话题的回调函数里都是根据消息进行了一些准备工作,最重要的变量就是data_manager_,三个回调函数分别设置了这个变量的一些对象

  // onMapHashdata_manager_.elevation_map_path_ = std::make_unique<std::filesystem::path>(std::filesystem::path(elevation_map_directory_) / elevation_map_hash);// onPointcloudMapdata_manager_.map_pcl_ptr_ = pcl::make_shared<pcl::PointCloud<pcl::PointXYZ>>(map_pcl);// onVectorMapdata_manager_.lanelet_map_ptr_ = std::make_shared<lanelet::LaneletMap>();lanelet::utils::conversion::fromBinMsg(*vector_map, data_manager_.lanelet_map_ptr_);const lanelet::ConstLanelets all_lanelets =lanelet::utils::query::laneletLayer(data_manager_.lanelet_map_ptr_);lane_filter_.road_lanelets_ = lanelet::utils::query::roadLanelets(all_lanelets);

data_manager_.isInitialized()通过的时候,会调用到publish函数,这里并不是一定要有已经存在的海拔图的,可以看到在函数最前面先判断海拔地图是否存在

  1. 如果不存在的话,就来生成一个
  if (stat(data_manager_.elevation_map_path_->c_str(), &info) != 0) {RCLCPP_INFO(this->get_logger(), "Create elevation map from pointcloud map ");createElevationMap();

生成海拔地图是分成了是否使用车道线滤波两种方式,如果不使用就直接为grid_map_pcl_loader_设置输入点云了,使用的话就先对点云进行一下滤波的操作,然后把滤波过的点云作为输入点云

    const auto convex_hull = getConvexHull(data_manager_.map_pcl_ptr_);lanelet::ConstLanelets intersected_lanelets =getIntersectedLanelets(convex_hull, lane_filter_.road_lanelets_);pcl::PointCloud<pcl::PointXYZ>::Ptr lane_filtered_map_pcl_ptr =getLaneFilteredPointCloud(intersected_lanelets, data_manager_.map_pcl_ptr_);grid_map_pcl_loader_->setInputCloud(lane_filtered_map_pcl_ptr);

具体生成的部分createElevationMapFromPointcloud,应该是调用grid_map库来实现的

void ElevationMapLoaderNode::createElevationMapFromPointcloud()
{const auto start = std::chrono::high_resolution_clock::now();grid_map_pcl_loader_->preProcessInputCloud();grid_map_pcl_loader_->initializeGridMapGeometryFromInputCloud();grid_map_pcl_loader_->addLayerFromInputCloud(layer_name_);grid_map::grid_map_pcl::printTimeElapsedToRosInfoStream(start, "Finish creating elevation map. Total time: ", this->get_logger());
}

接下来还有一步就是inpaintElevationMap,事实上经过之前的操作,我们已经得到了elevation_map_,但现在的这个海拔图可能有很多孔雀,也并不连贯,所以这个函数的作用就是让海拔图更连续一些,具体是直接用cv::inpaint(original_image, mask, filled_image, radius_in_pixels, cv::INPAINT_NS);来对整个海拔图进行修补,然后把图像再转回elevation_map_

  1. 如果海拔图存在的话,会尝试加载海拔图,加载成功就直接用已经有的海拔图了,加载失败的话就调用createElevationMap()函数去生成,这里也是用到了grid_map
  } else if (info.st_mode & S_IFDIR) {RCLCPP_INFO(this->get_logger(), "Load elevation map from: %s",data_manager_.elevation_map_path_->c_str());// Check if bag can be loadedbool is_bag_loaded = false;try {is_bag_loaded = grid_map::GridMapRosConverter::loadFromBag(*data_manager_.elevation_map_path_, "elevation_map", elevation_map_);} catch (rosbag2_storage_plugins::SqliteException & e) {is_bag_loaded = false;}if (!is_bag_loaded) {// Delete directory including elevation map if bag is brokenRCLCPP_ERROR(this->get_logger(), "Try to loading bag, but bag is broken. Remove %s",data_manager_.elevation_map_path_->c_str());std::filesystem::remove_all(data_manager_.elevation_map_path_->c_str());// Create elevation map from pointcloud map if bag is brokenRCLCPP_INFO(this->get_logger(), "Create elevation map from pointcloud map ");createElevationMap();}}

最后就是对海拔地图,以及海拔地图对应的点云进行发布了

  pub_elevation_map_->publish(std::move(msg));  // 这里用到move是因为后边就不用msg了,所以能避免不必要的copyif (use_elevation_map_cloud_publisher_) {pcl::PointCloud<pcl::PointXYZ>::Ptr elevation_map_cloud_ptr =createPointcloudFromElevationMap();sensor_msgs::msg::PointCloud2 elevation_map_cloud_msg;pcl::toROSMsg(*elevation_map_cloud_ptr, elevation_map_cloud_msg);pub_elevation_map_cloud_->publish(elevation_map_cloud_msg);}

当然,这个包也是最新的版本会比galatic更复杂严谨一些的,不过这里没看最新版本的是什么样的

euclidean_cluster

欧式聚类,这个在上边刚刚用到,简单来说就是把点云聚类成更小的部分以进行物体的分类的步骤。这里是分了两种方法,一种是欧式聚类,也就是euclidean_cluster,另一种是voxel_grid_based_euclidean_cluster,看起来是根据体素网格进行分类的一种办法。

euclidean_cluster

这里的构造函数输入了几个参数,分别是是否使用高度信息,聚类最少点数,最多点数以及点之间的最大距离阈值,然后这里整个类EuclideanCluster是继承自EuclideanClusterInterface的,所以有几个成员变量也是继承过来的。然后主要的聚类操作的函数就在clustrer,这里我们可以看到,其实也是用到了KD树的

  // create treepcl::search::KdTree<pcl::PointXYZ>::Ptr tree(new pcl::search::KdTree<pcl::PointXYZ>);tree->setInputCloud(pointcloud_ptr);

然后直接用PCL库中的聚类函数实现

  // clusteringstd::vector<pcl::PointIndices> cluster_indices;pcl::EuclideanClusterExtraction<pcl::PointXYZ> pcl_euclidean_cluster;pcl_euclidean_cluster.setClusterTolerance(tolerance_);pcl_euclidean_cluster.setMinClusterSize(min_cluster_size_);pcl_euclidean_cluster.setMaxClusterSize(max_cluster_size_);pcl_euclidean_cluster.setSearchMethod(tree);pcl_euclidean_cluster.setInputCloud(pointcloud_ptr);pcl_euclidean_cluster.extract(cluster_indices);

然后就是输出了

voxel_grid_based_euclidean_cluster

VoxelGridBasedEuclideanCluster这个类也是继承自EuclideanClusterInterface的,所以这里的构造函数和EuclideanCluster一样,也是对接口类的几个成员变量进行了设置,这里还有两个成员变量,一个是 voxel_leaf_size_(voxel_leaf_size),这个代表的应该就是体素网格的大小;另一个是min_points_number_per_voxel_,这里对应的应该是一个体素网格里最少的点数。然后这个方法的核心操作也是在clustre函数中,这里一上来也是设置了一些参数

  pcl::PointCloud<pcl::PointXYZ>::Ptr voxel_map_ptr(new pcl::PointCloud<pcl::PointXYZ>);voxel_grid_.setLeafSize(voxel_leaf_size_, voxel_leaf_size_, 100000.0);voxel_grid_.setMinimumPointsNumberPerVoxel(min_points_number_per_voxel_);voxel_grid_.setInputCloud(pointcloud);voxel_grid_.setSaveLeafLayout(true);voxel_grid_.filter(*voxel_map_ptr);

我们可以看到划分网格的时候,z方向其实是不划分的,所以这样网格就可以看成是一个二维的网格了。然后这里的filter我的理解应该就是把点云按照体素网格划分开来了。之后也是用到了KD树,值得注意的是,因为这里其实是不考虑Z轴信息的,所以点云也是被转成了2D点云

  // voxel is pressed 2dpcl::PointCloud<pcl::PointXYZ>::Ptr pointcloud_2d_ptr(new pcl::PointCloud<pcl::PointXYZ>);for (const auto & point : voxel_map_ptr->points) {pcl::PointXYZ point2d;point2d.x = point.x;point2d.y = point.y;point2d.z = 0.0;pointcloud_2d_ptr->push_back(point2d);}// create treepcl::search::KdTree<pcl::PointXYZ>::Ptr tree(new pcl::search::KdTree<pcl::PointXYZ>);tree->setInputCloud(pointcloud_2d_ptr);

然后聚类也是一样,直接用PCL的pcl::EuclideanClusterExtraction

  // clusteringstd::vector<pcl::PointIndices> cluster_indices;pcl::EuclideanClusterExtraction<pcl::PointXYZ> pcl_euclidean_cluster;pcl_euclidean_cluster.setClusterTolerance(tolerance_);pcl_euclidean_cluster.setMinClusterSize(1);pcl_euclidean_cluster.setMaxClusterSize(max_cluster_size_);pcl_euclidean_cluster.setSearchMethod(tree);pcl_euclidean_cluster.setInputCloud(pointcloud_2d_ptr);pcl_euclidean_cluster.extract(cluster_indices);

将点云中的点根据其在体素网格中的位置进行聚类,结果存储在temporary_clusters中。这样,就可以通过体素网格索引快速查找点所在的聚类。

  // create map to search cluster index from voxel grid indexstd::unordered_map</* voxel grid index */ int, /* cluster index */ int> map;for (size_t cluster_idx = 0; cluster_idx < cluster_indices.size(); ++cluster_idx) {const auto & cluster = cluster_indices.at(cluster_idx);for (const auto & point_idx : cluster.indices) {map[point_idx] = cluster_idx;}}// create vector of point cloud cluster. vector index is voxel grid index.std::vector<pcl::PointCloud<pcl::PointXYZ>> temporary_clusters;  // no check about cluster sizetemporary_clusters.resize(cluster_indices.size());for (const auto & point : pointcloud->points) {const int index =voxel_grid_.getCentroidIndexAt(voxel_grid_.getGridCoordinates(point.x, point.y, point.z));if (map.find(index) != map.end()) {temporary_clusters.at(map[index]).points.push_back(point);}}

最后也是把这些作为结果输出就好了

节点类

这两个的节点类是差不多的,构造函数加载参数,实例化聚类的类,然后订阅话题是点云

  using std::placeholders::_1;pointcloud_sub_ = this->create_subscription<sensor_msgs::msg::PointCloud2>("input", rclcpp::SensorDataQoS().keep_last(1),std::bind(&VoxelGridBasedEuclideanClusterNode::onPointCloud, this, _1));

然后在回调函数中,执行了聚类操作以及消息类型的各种转换

  // convert ros to pclpcl::PointCloud<pcl::PointXYZ>::Ptr raw_pointcloud_ptr(new pcl::PointCloud<pcl::PointXYZ>);pcl::fromROSMsg(*input_msg, *raw_pointcloud_ptr);// clusteringstd::vector<pcl::PointCloud<pcl::PointXYZ>> clusters;cluster_->cluster(raw_pointcloud_ptr, clusters);// build output msgtier4_perception_msgs::msg::DetectedObjectsWithFeature output;convertPointCloudClusters2Msg(input_msg->header, clusters, output);cluster_pub_->publish(output);

launch文件

这里的launch文件是使用了launch+py文件的这种形式,在launch文件里主要是加载了程序要使用到的各种参数,然后在python文件中是具体的启动了节点,在python文件中,可以看到还加载了几个组件

    # set voxel grid filter as a componentvoxel_grid_filter_component = ComposableNode(package="pointcloud_preprocessor",plugin="pointcloud_preprocessor::VoxelGridDownsampleFilterComponent",name=AnonName("voxel_grid_filter"),remappings=[("input", LaunchConfiguration("input_pointcloud")),("output", "voxel_grid_filtered/pointcloud"),],parameters=[load_composable_node_param("voxel_grid_param_path")],)# set compare map filter as a componentcompare_map_filter_component = ComposableNode(package="compare_map_segmentation",plugin="compare_map_segmentation::VoxelBasedCompareMapFilterComponent",name=AnonName("compare_map_filter"),remappings=[("input", "voxel_grid_filtered/pointcloud"),("map", LaunchConfiguration("input_map")),("output", "compare_map_filtered/pointcloud"),],)

然后再具体一点,我们可以看到话题的名字在这里是有remapping操作的,所以代码里都是input和output

    use_map_euclidean_cluster_component = ComposableNode(package=pkg,plugin="euclidean_cluster::EuclideanClusterNode",name=AnonName("euclidean_cluster"),remappings=[("input", "compare_map_filtered/pointcloud"),("output", LaunchConfiguration("output_clusters")),],parameters=[load_composable_node_param("euclidean_param_path")],)

然后其实具体的启动函数是在generate_launch_description中的,里面会调用launch_setup这个函数

def generate_launch_description():def add_launch_arg(name: str, default_value=None):return DeclareLaunchArgument(name, default_value=default_value)return launch.LaunchDescription([add_launch_arg("input_pointcloud", "/perception/obstacle_segmentation/pointcloud"),add_launch_arg("input_map", "/map/pointcloud_map"),add_launch_arg("output_clusters", "clusters"),add_launch_arg("use_pointcloud_map", "false"),add_launch_arg("voxel_grid_param_path",[FindPackageShare("euclidean_cluster"), "/config/voxel_grid.param.yaml"],),add_launch_arg("euclidean_param_path",[FindPackageShare("euclidean_cluster"), "/config/euclidean_cluster.param.yaml"],),OpaqueFunction(function=launch_setup),])

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/39056.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于java+springboot+vue实现的家政服务平台(文末源码+Lw)299

摘 要 现代经济快节奏发展以及不断完善升级的信息化技术&#xff0c;让传统数据信息的管理升级为软件存储&#xff0c;归纳&#xff0c;集中处理数据信息的管理方式。本家政服务平台就是在这样的大环境下诞生&#xff0c;其可以帮助管理者在短时间内处理完毕庞大的数据信息&a…

Redis中hash类型的操作命令(命令的语法、返回值、时间复杂度、注意事项、操作演示)

文章目录 字符串和哈希类型相比hset 命令hget 命令hexistshdelhkeyshvalshgetallhmgethlenhsetnxhincrbyhincrbyfloat 字符串和哈希类型相比 假设有以下一种场景&#xff1a;现在要在 Redis 中存储一个用户的基本信息(id1、namezhangsan、age17)&#xff0c;下图表示使用字符串…

2024护网整体工作预案示例

目录 第1章 HW整体工作工作部署 1.1 工作组织架构 1.2 各部门工作职责 1.3 演练期间工作机制 1.3.1 工作汇报机制 1.3.2 应急响应机制 第2章 系统资产梳理整改 2.1 敏感信息梳理整改 2.2 互联网资产发现 2.3 第三方供应商梳理 2.4 业务连接单位梳理 第3…

win10显示毫秒-上午-下午及星期几,24小时制

关于毫秒 winr regedit 计算机\HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced 新建ShowSecondsInSystemClock&#xff0c;编辑1显示&#xff0c;不显示就删了它 然后重启 资源管理器可能有多个全部重启&#xff0c;就可以啦 根据自己喜好…

党建科普3D数字化展馆支持实时更新迭代

3D虚拟策展逐渐成为新时代下的主流方式&#xff0c;深圳华锐视点作为专业的web3d开发公司&#xff0c;具有专业化的3D数字化空间还原能力&#xff0c;能根据企业/个人不同需求和预算&#xff0c;为您打造纯线上虚拟3D艺术展&#xff0c;让您彻底摆脱实体美术馆的限制&#xff0…

面试官:Java文件是如何被加载到内存中的?

面试连环call Java文件是如何被加载到内存中的&#xff1f;Java类的声明周期都有哪些阶段&#xff1f;JVM加载的class文件都有哪些来源&#xff1f;JVM在加载class文件时&#xff0c;何时判断class文件的格式是否符合要求&#xff1f; 类生命周期 一个类从被加载到虚拟机内存…

【计算机体系结构】缓存的false sharing

在介绍缓存的false sharing之前&#xff0c;本文先介绍一下多核系统中缓存一致性是如何维护的。 目前主流的多核系统中的缓存一致性协议是MESI协议及其衍生协议。 MESI协议 MESI协议的4种状态 MESI协议有4种状态。MESI是4种状态的首字母缩写&#xff0c;缓存行的4种状态分别…

【Linux】—Xshell、Xftp安装

文章目录 前言一、下载Xshell、Xftp二、安装Xshell三、使用XShell连接Linux服务器四、修改windows的主机映射文件&#xff08;hosts文件&#xff09;五、远程连接hadoop102/hadoop103/hadoop104服务器六、安装Xftp 前言 XShell远程管理工具&#xff0c;可以在Windows界面下来访…

[数据集][目标检测]螺丝螺母检测数据集VOC+YOLO格式2400张2类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;2400 标注数量(xml文件个数)&#xff1a;2400 标注数量(txt文件个数)&#xff1a;2400 标注…

SpringBoot 整合 Minio 实现文件切片极速上传技术

Centos7安装Minio 创建目标文件夹 mkdir minio使用docker查看目标镜像状况 大家需要注意&#xff0c;此处我们首先需要安装docker&#xff0c;对于相关安装教程&#xff0c;大家可以查看我之前的文章&#xff0c;按部就班就可以&#xff0c;此处不再赘述&#xff01;&#x…

uniapp入门

一、新建项目 进入到主界面&#xff0c;左上角点击新建——1.项目 输入项目名称&#xff0c;Vue版本选择3 二、创建页面 选中左侧文件目录里的pages文件夹&#xff0c;右键&#xff0c;选择新建页面 1输入名称 2选中“创建同名目录” 3选择模板&…

将json对象转为xml进行操作属性

将json对象转为xml进行操作属性 文章目录 将json对象转为xml进行操作属性前端发送json数据格式写入数据库格式-content字段存储&#xff08;varchar(2000)&#xff09;Question实体类-接口映射对象QuestionContent 接收参数对象DAO持久层Mapper层Service层Controller控制层接收…

普元EOS学习笔记-低开实现图书的增删改查

前言 在前一篇《普元EOS学习笔记-创建精简应用》中&#xff0c;我已经创建了EOS精简应用。 我之前说过&#xff0c;EOS精简应用就是自己创建的EOS精简版&#xff0c;该项目中&#xff0c;开发者可以进行低代码开发&#xff0c;也可以进行高代码开发。 本文我就记录一下自己在…

2024年6月 | deepin 深度应用商店-应用更新记录

新增应用 序号应用名称depein 系统版本应用分类应用类型1bkViewer 照片浏览器deepin 20.9 deepin V23网络应用wine291助手deepin 20.9 deepin V23编程开发wine3风云CAD转换器deepin 20.9 deepin V23编程开发wine4Disk Savvydeepin 20.9 deepin V23系统工具wine5飞猫盘…

miniconda3 安装jupyter notebook并配置网络访问

由于服务器安装的miniconda3&#xff0c;无jupyter notebook&#xff0c;所以手工安装jupyter notebook 1 先conda 安装相关包 在base 环境下 conda install ipython conda install jupyter notebook 2 生成配置文件 jupyter notebook --generate-config Writing defaul…

Nginx 常用配置与应用

Nginx 常用配置与应用 官网地址&#xff1a;https://nginx.org/en/docs/ 目录 Nginx 常用配置与应用 Nginx总架构 正向代理 反向代理 Nginx 基本配置反向代理案例 负载均衡 Nginx总架构 进程模型 正向代理 反向代理 Nginx 基本配置反向代理案例 负载均衡 Nginx 基本配置…

新人程序员接手丑陋的老代码怎么办?改还是不改......

许多小伙伴在初入职场的时候&#xff0c;都会遇到要接手老代码的情况&#xff0c;那么问题来了&#xff0c;如果老代码十分丑陋&#xff0c;你是改还是不改&#xff1f; 不改吧&#xff0c;心里难受&#xff1b;改吧&#xff0c;指不定会遇到什么情况&#xff0c;比如…… 1.…

【嫦娥四号】月球着陆器中子和剂量测量(LND)实验

一、引言 嫦娥四号任务是中国月球探测计划的重要里程碑&#xff0c;实现了人类首次在月球背面软着陆&#xff0c;并展开了月面巡视和中继通信。本文所描述的嫦娥四号着陆器上的中子与剂量测定实验&#xff08;Lunar Lander Neutrons and Dosimetry Experiment, LND&#xff09…

【雷丰阳-谷粒商城 】【分布式高级篇-微服务架构篇】【17】认证服务01

持续学习&持续更新中… 守破离 【雷丰阳-谷粒商城 】【分布式高级篇-微服务架构篇】【17】认证服务01 环境搭建验证码倒计时短信服务邮件服务验证码短信形式&#xff1a;邮件形式&#xff1a; 异常机制MD5参考 环境搭建 C:\Windows\System32\drivers\etc\hosts 192.168.…

JAVA每日作业day7.1-7.3小总结

ok了家人们前几天学了一些知识&#xff0c;接下来一起看看吧 一.API Java 的 API &#xff08; API: Application( 应用 ) Programming( 程序 ) Interface(接口 ) &#xff09; Java API 就是 JDK 中提供给我们使用的类&#xff0c;这些类将底层 的代码实现封装了起来&#x…