PCL分割——法向量差分分割

Difference of Normals Based Segmentation

该算法基于一个点在不同大小邻域所得到的两个法向量的插值为依据对点进行分割。该方法计算效率高,并且对于大规模的无组织点云数据具有很好的效果。

算法流程

1️⃣ 使用较大的支持半径 (r_l),为点云中的每一个点估计法向量
2️⃣ 使用较小的支持半径 (r_s),再次为点云中的每一个点估计法向量
3️⃣ 对每一个点计算法向量的归一化差值
4️⃣ 对得到的向量场进行滤波,从而提取出属于目标尺度 / 感兴趣区域的点


  1. 算法要求要求必须使用半径搜索去计算点的法向量
  2. 滤波算法(可以使用条件滤波)需要基于我们得到的DON来进行选择,可以从以下数据中进行滤波条件的设定。
  3. 基于DON进行滤波后,还需要对滤波后的点云数据进行分割,才算完整整个流程。
过滤量(Quantity) PointNormal 字段 数值范围 含义说明 典型使用场景
DoN 向量 \(\Delta \hat{n}(p, r_s, r_l)\) normal[3] ([-1, 1]) 小尺度与大尺度法向量的差向量 DoN 方向 / 角度 过滤(方向敏感任务)
DoN 模长(L2 范数) (\(|\Delta \hat{n}|\)) curvature ((0, 1)) DoN 响应强度,衡量该点在给定尺度下的几何变化程度 尺度分割首选(与方向无关)
DoN x 分量 (\(\Delta \hat{n}_x\)) normal[0] ((-1, 1)) DoN 在 x 方向的分量 定向尺度过滤(如建筑立面)
DoN y 分量 (\(\Delta \hat{n}_y\)) normal[1] ((-1, 1)) DoN 在 y 方向的分量 定向尺度过滤
DoN z 分量 (\(\Delta \hat{n}_z\)) normal[2] ((-1, 1)) DoN 在 z 方向的分量 地面 / 屋顶 / 垂直结构区分
使用示例
int main()
{
  // Compute normals using both small and large scales at each point
  pcl::NormalEstimationOMP<PointXYZRGB, PointNormal> ne;
  ne.setInputCloud (cloud);
  ne.setSearchMethod (tree);

  /**
   * NOTE: setting viewpoint is very important, so that we can ensure
   * normals are all pointed in the same direction!
   */
  ne.setViewPoint (std::numeric_limits<float>::max (), std::numeric_limits<float>::max (), std::numeric_limits<float>::max ());

  // calculate normals with the small scale
  std::cout << "Calculating normals for scale..." << scale1 << std::endl;
  pcl::PointCloud<PointNormal>::Ptr normals_small_scale (new pcl::PointCloud<PointNormal>);

  ne.setRadiusSearch (scale1);
  ne.compute (*normals_small_scale);

  // calculate normals with the large scale
  std::cout << "Calculating normals for scale..." << scale2 << std::endl;
  pcl::PointCloud<PointNormal>::Ptr normals_large_scale (new pcl::PointCloud<PointNormal>);

  ne.setRadiusSearch (scale2);
  ne.compute (*normals_large_scale);

  // Create output cloud for DoN results
  PointCloud<PointNormal>::Ptr doncloud (new pcl::PointCloud<PointNormal>);
  copyPointCloud (*cloud, *doncloud);

  std::cout << "Calculating DoN... " << std::endl;
  // Create DoN operator
  pcl::DifferenceOfNormalsEstimation<PointXYZRGB, PointNormal, PointNormal> don;
  don.setInputCloud (cloud);
  don.setNormalScaleLarge (normals_large_scale);
  don.setNormalScaleSmall (normals_small_scale);

  if (!don.initCompute ())
  {
    std::cerr << "Error: Could not initialize DoN feature operator" << std::endl;
    exit (EXIT_FAILURE);
  }

  // Compute DoN
  don.computeFeature (*doncloud);

  // Save DoN features
  pcl::PCDWriter writer;
  writer.write<pcl::PointNormal> ("don.pcd", *doncloud, false); 

  // Filter by magnitude
  std::cout << "Filtering out DoN mag <= " << threshold << "..." << std::endl;

  // Build the condition for filtering
  pcl::ConditionOr<PointNormal>::Ptr range_cond (
    new pcl::ConditionOr<PointNormal> ()
    );
  range_cond->addComparison (pcl::FieldComparison<PointNormal>::ConstPtr (
                               new pcl::FieldComparison<PointNormal> ("curvature", pcl::ComparisonOps::GT, threshold))
                             );
  // Build the filter
  pcl::ConditionalRemoval<PointNormal> condrem;
  condrem.setCondition (range_cond);
  condrem.setInputCloud (doncloud);

  pcl::PointCloud<PointNormal>::Ptr doncloud_filtered (new pcl::PointCloud<PointNormal>);

  // Apply filter
  condrem.filter (*doncloud_filtered);

  doncloud = doncloud_filtered;

  // Save filtered output
  std::cout << "Filtered Pointcloud: " << doncloud->size () << " data points." << std::endl;

  writer.write<pcl::PointNormal> ("don_filtered.pcd", *doncloud, false); 

  // Filter by magnitude
  std::cout << "Clustering using EuclideanClusterExtraction with tolerance <= " << segradius << "..." << std::endl;

  pcl::search::KdTree<PointNormal>::Ptr segtree (new pcl::search::KdTree<PointNormal>);
  segtree->setInputCloud (doncloud);

  std::vector<pcl::PointIndices> cluster_indices;
  pcl::EuclideanClusterExtraction<PointNormal> ec;

  ec.setClusterTolerance (segradius);
  ec.setMinClusterSize (50);
  ec.setMaxClusterSize (100000);
  ec.setSearchMethod (segtree);
  ec.setInputCloud (doncloud);
  ec.extract (cluster_indices);

}

该算法在PCL没有直接封装,需要按照上面的流程进行处理。
PCL中的copyPointCloud是按照字段的名称实现拷贝,只会拷贝两个类型中的交集字段的数据。
此外,由于我们需要计算两组法向量并计算差分。因此在估计法向量是千万要记住设置视点的方向,让法向量朝向我们的视点方向。,保证前后两次计算的一致性。具体设置在哪里应该是随意的,但一定要选一个视点

posted @ 2025-12-15 16:29  Ytytyty  阅读(53)  评论(0)    收藏  举报