PCLの法線推定を使うときに、レーザーからの距離に応じてRadiusSearchを変える実装をご紹介します。
【PCL, ROS】NormalEstimationで距離に応じてRadiusSearchを変えたい
3D-LiDARなどの距離センサでは、センサから遠いエリアほど点群の密度が粗(スパース)になる特徴があります。
これを踏まえると、点群の法線推定をするときは、近傍点探索の半径(RadiusSearch)を、センサからの距離に応じて変化させたくなります。具体的には、センサからの距離が遠いところでは、より大きな探索半径を設定します。
既存のクラスだと痒い所に手が届かない
PCLの既存のクラスだと痒い所に手が届かなく、RadiusSearchが一定で変えることができません。
↓既存のクラスでの実装例はコチラ↓
具体的にはこんな感じです。
void NormalEstimationPCL::NormalEstimation(void) { std::cout << "NORMAL ESTIMATION" << std::endl; pcl::NormalEstimation<pcl::PointNormal, pcl::PointNormal> ne; ne.setInputCloud(normals); pcl::search::KdTree<pcl::PointNormal>::Ptr tree (new pcl::search::KdTree<pcl::PointNormal> ()); ne.setSearchMethod(tree); ne.setRadiusSearch(0.5); // <-ココ ne.compute(*normals); }
この例では0.5mで一定です。
解決策の概要
- 受け取った3次元点群の法線を推定
- センサから遠いほど近傍点探索の半径を大きくする
- sub:点群(point-cloud,{x, y, z})
- pub:法線群(normal-cloud{x, y, z, n})
- 可視化:pcl viewer
ソースコード
#include <ros/ros.h> #include <sensor_msgs/PointCloud2.h> #include <pcl_conversions/pcl_conversions.h> #include <pcl/point_cloud.h> #include <pcl/point_types.h> #include <pcl/kdtree/kdtree_flann.h> #include <pcl/features/normal_3d.h> #include <pcl/visualization/cloud_viewer.h> // #include <omp.h> class NormalEstimation{ private: /*node handle*/ ros::NodeHandle nh; ros::NodeHandle nhPrivate; /*subscribe*/ ros::Subscriber sub_pc; /*publish*/ ros::Publisher pub_pc; /*pcl*/ pcl::visualization::PCLVisualizer viewer {"Normal Estimation Multi Thread"}; pcl::KdTreeFLANN<pcl::PointXYZ> kdtree; pcl::PointCloud<pcl::PointXYZ>::Ptr cloud {new pcl::PointCloud<pcl::PointXYZ>}; pcl::PointCloud<pcl::PointNormal>::Ptr normals {new pcl::PointCloud<pcl::PointNormal>}; /*parameters*/ int skip; double search_radius_ratio; public: NormalEstimation(); void CallbackPC(const sensor_msgs::PointCloud2ConstPtr &msg); void Computation(void); double Getdepth(pcl::PointXYZ point); std::vector<int> KdtreeSearch(pcl::PointXYZ searchpoint, double search_radius); void Visualization(void); void Publication(void); }; NormalEstimation::NormalEstimation() :nhPrivate("~") { sub_pc = nh.subscribe("/velodyne_points", 1, &NormalEstimation::CallbackPC, this); pub_pc = nh.advertise<sensor_msgs::PointCloud2>("/normals", 1); viewer.setBackgroundColor(1, 1, 1); viewer.addCoordinateSystem(0.8, "axis"); viewer.setCameraPosition(-30.0, 0.0, 10.0, 0.0, 0.0, 1.0); nhPrivate.param("skip", skip, 3); nhPrivate.param("search_radius_ratio", search_radius_ratio, 0.09); std::cout << "skip = " << skip << std::endl; std::cout << "search_radius_ratio = " << search_radius_ratio << std::endl; } void NormalEstimation::CallbackPC(const sensor_msgs::PointCloud2ConstPtr &msg) { /* std::cout << "CALLBACK PC" << std::endl; */ pcl::fromROSMsg(*msg, *cloud); std::cout << "cloud->points.size() = " << cloud->points.size() << std::endl; normals->points.clear(); normals->points.resize((cloud->points.size()-1)/skip + 1); kdtree.setInputCloud(cloud); Computation(); Publication(); Visualization(); } void NormalEstimation::Computation(void) { std::cout << "omp_get_max_threads() = " << omp_get_max_threads() << std::endl; double time_start = ros::Time::now().toSec(); // #ifdef _OPENMP // #pragma omp parallel for // #endif for(size_t i=0;i<cloud->points.size();i+=skip){ /*search neighbor points*/ double laser_distance = Getdepth(cloud->points[i]); double search_radius = search_radius_ratio*laser_distance; std::vector<int> indices = KdtreeSearch(cloud->points[i], search_radius); /*compute normal*/ float curvature; Eigen::Vector4f plane_parameters; pcl::computePointNormal(*cloud, indices, plane_parameters, curvature); /*input*/ size_t normal_index = i/skip; normals->points[normal_index].x = cloud->points[i].x; normals->points[normal_index].y = cloud->points[i].y; normals->points[normal_index].z = cloud->points[i].z; normals->points[normal_index].normal_x = plane_parameters[0]; normals->points[normal_index].normal_y = plane_parameters[1]; normals->points[normal_index].normal_z = plane_parameters[2]; normals->points[normal_index].curvature = curvature; flipNormalTowardsViewpoint(cloud->points[i], 0.0, 0.0, 0.0, normals->points[normal_index].normal_x, normals->points[normal_index].normal_y, normals->points[normal_index].normal_z); } for(size_t i=0;i<normals->points.size();){ if(std::isnan(normals->points[i].normal_x) || std::isnan(normals->points[i].normal_y) || std::isnan(normals->points[i].normal_z)){ std::cout << "deleted NAN normal" << std::endl; normals->points.erase(normals->points.begin() + i); } else i++; } std::cout << "computation time [s] = " << ros::Time::now().toSec() - time_start << std::endl; } double NormalEstimation::Getdepth(pcl::PointXYZ point) { double depth = sqrt( point.x*point.x + point.y*point.y + point.z*point.z ); return depth; } std::vector<int> NormalEstimation::KdtreeSearch(pcl::PointXYZ searchpoint, double search_radius) { std::vector<int> pointIdxRadiusSearch; std::vector<float> pointRadiusSquaredDistance; if(kdtree.radiusSearch(searchpoint, search_radius, pointIdxRadiusSearch, pointRadiusSquaredDistance)<=0) std::cout << "kdtree error" << std::endl; return pointIdxRadiusSearch; } void NormalEstimation::Visualization(void) { viewer.removeAllPointClouds(); viewer.addPointCloud(cloud, "cloud"); viewer.setPointCloudRenderingProperties (pcl::visualization::PCL_VISUALIZER_COLOR, 0.0, 0.0, 0.0, "cloud"); viewer.setPointCloudRenderingProperties (pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 3, "cloud"); viewer.addPointCloudNormals<pcl::PointNormal>(normals, 1, 0.5, "normals"); viewer.setPointCloudRenderingProperties (pcl::visualization::PCL_VISUALIZER_COLOR, 1.0, 0.0, 0.0, "normals"); viewer.setPointCloudRenderingProperties (pcl::visualization::PCL_VISUALIZER_LINE_WIDTH, 1, "normals"); viewer.spinOnce(); } void NormalEstimation::Publication(void) { normals->header.stamp = cloud->header.stamp; normals->header.frame_id = cloud->header.frame_id; sensor_msgs::PointCloud2 pc; pcl::toROSMsg(*normals, pc); pub_pc.publish(pc); } int main(int argc, char** argv) { ros::init(argc, argv, "normal_estimation"); std::cout << "Normal Estimation" << std::endl; NormalEstimation normal_estimation; ros::spin(); }
解説
-
流れ
- 点群をサブスクライブ
- forループ(各点にアクセス)
- センサと注目点との距離を計算
- 距離に比例した探索半径を設定
- 近傍点を探索
- 近傍点群に対して主成分分析を適用
pcl::computePointNormal(*cloud, indices, plane_parameters, curvature);
- オブジェクトに計算結果を格納
- パブリッシュ
- パラメータ skip
何個飛ばしで法線を計算するか設定します。 - パラメータ search_radius_ratio
センサからの距離と探索半径の比率を設定します。 - OpenMPで並列処理できるようにするために、push_backを使わずに書いたので、少しややこしい実装になっています。
- OpenMPを使う場合は、コメントアウトしている部分を解除してください。
さいごに
参考になれば幸いです
以上です。
Ad.
コメント