PCL点云库入门(第20讲)——PCL库点云特征之CVFH特征描述Clustered Viewpoint Feature Histogram(CVFH)

发布于:2025-06-25 ⋅ 阅读:(119) ⋅ 点赞:(0)

Clustered Viewpoint Feature Histogram(CVFH)是 一种全局点云描述子,主要用于物体识别与姿态估计。它是 Viewpoint Feature Histogram(VFH) 的扩展,旨在解决物体形状复杂时 VFH 产生的模糊性问题。CVFH 通过对点云进行分簇处理,将复杂物体表面分解为若干个“形状稳定区域”,从而提取更鲁棒、区分度更高的全局特征。
在这里插入图片描述


一、CVFH 算法原理概述

CVFH 的核心思路是将点云按照曲率变化分成多个区域,每个区域计算一个局部 VFH 描述子,最终形成整个物体的多个描述子集合,从而达到更稳定的描述效果。

CVFH 包括以下关键步骤:

  1. 估计法线和曲率
  2. 确定视角方向
  3. 分割物体点云为多个区域(通过曲率分割 + 连通性)
  4. 为每个区域计算一个 VFH 描述子
  5. 对所有区域的描述子进行聚类或编码,形成整体描述

1、数学公式与推导细节

1.1、 法线与曲率估计

使用 PCA 对点邻域 N ( p i ) \mathcal{N}(p_i) N(pi) 求协方差矩阵:

C = 1 ∣ N ∣ ∑ q ∈ N ( q − q ˉ ) ( q − q ˉ ) T \mathbf{C} = \frac{1}{|\mathcal{N}|} \sum_{q \in \mathcal{N}} (q - \bar{q})(q - \bar{q})^T C=N1qN(qqˉ)(qqˉ)T

其中 q ˉ \bar{q} qˉ 是邻域点的均值。对 C \mathbf{C} C 做特征值分解,取最小特征值对应的特征向量为法线 n i \mathbf{n}_i ni

表面曲率 κ \kappa κ 可由特征值估计:

κ i = λ 0 λ 0 + λ 1 + λ 2 \kappa_i = \frac{\lambda_0}{\lambda_0 + \lambda_1 + \lambda_2} κi=λ0+λ1+λ2λ0


1.2、 视点方向确定

视点(camera position)设为 v p \mathbf{v}_p vp,对于每个点 p i p_i pi,定义其 视点方向向量

v i = v p − p i \mathbf{v}_i = \mathbf{v}_p - \mathbf{p}_i vi=vppi

用于计算 VFH 的角度特征。


1.3、分割为多个区域(Clustering)

使用曲率变化与法线夹角来划分点云区域。两点 p i p_i pi p j p_j pj 之间是否属于同一区域的判断依据:

  • 法线夹角小于阈值 θ \theta θ

    arccos ⁡ ( n i ⋅ n j ) < θ \arccos(\mathbf{n}_i \cdot \mathbf{n}_j) < \theta arccos(ninj)<θ

  • 曲率差值小于阈值

并通过 区域增长算法(Region Growing) 合并满足条件的邻近点,形成多个“稳定区域” { R 1 , R 2 , … , R k } \{ R_1, R_2, \dots, R_k \} {R1,R2,,Rk}


1.4、 对每个区域计算 VFH

VFH 是对 每个点对其中心点(质心)和法线之间的相对几何信息进行统计构建直方图。

对于区域 R j R_j Rj,计算参考方向:

  • u = n c \mathbf{u} = \mathbf{n}_c u=nc(质心法线)
  • v = ( p i − c ) × u \mathbf{v} = (\mathbf{p}_i - \mathbf{c}) \times \mathbf{u} v=(pic)×u
  • w = u × v \mathbf{w} = \mathbf{u} \times \mathbf{v} w=u×v

对于区域内每个点 p i p_i pi,其法线为 n i \mathbf{n}_i ni,计算以下角度特征:

  • α = arccos ⁡ ( v ⋅ n i ) \alpha = \arccos(\mathbf{v} \cdot \mathbf{n}_i) α=arccos(vni)
  • ϕ = arccos ⁡ ( u ⋅ p i − c ∥ p i − c ∥ ) \phi = \arccos(\mathbf{u} \cdot \frac{\mathbf{p}_i - \mathbf{c}}{\|\mathbf{p}_i - \mathbf{c}\|}) ϕ=arccos(upicpic)
  • θ = arctan ⁡ 2 ( w ⋅ n i , u ⋅ n i ) \theta = \arctan2(w \cdot \mathbf{n}_i, u \cdot \mathbf{n}_i) θ=arctan2(wni,uni)

每个特征被量化成直方图,再拼接成一个局部描述子。
在这里插入图片描述


1.5、 聚类后的全局特征

将所有区域的 VFH 拼接为一组:

CVFH = { VFH R 1 , VFH R 2 , … , VFH R k } \text{CVFH} = \{ \text{VFH}_{R_1}, \text{VFH}_{R_2}, \dots, \text{VFH}_{R_k} \} CVFH={VFHR1,VFHR2,,VFHRk}

可以使用 聚类中心 + 直方图统计 或在检索时将每个子 VFH 与数据库中所有子 VFH 比较取最小距离,形成鲁棒匹配方式。


2、CVFH 与 VFH 的对比

特性 VFH CVFH
视角敏感性
对形状复杂性适应 好(通过区域分割)
特征数量 单一全局描述子 多个区域子描述子
计算复杂度 较低 较高(需区域分割 + 多次计算)
适用场景 简单对象识别 复杂物体识别、姿态估计

3、CVFH 算法实现流程

1. 估计每个点的法线与曲率(使用 PCL::NormalEstimation)
2. 提取物体的边界轮廓(可使用 PCL::Boundary)
3. 基于法线夹角和曲率变化进行区域分割(区域增长)
4. 对每个区域:
   a. 计算区域质心及法线
   b. 计算角度特征 α、φ、θ
   c. 构建直方图(通常 3 个角度 × 45 bin)
5. 将所有区域描述子组合,构成最终 CVFH

二、成员变量和成员函数

1、成员变量解析(private & protected)

成员变量名 类型 含义
float vpx_, vpy_, vpz_ float 视点位置(Viewpoint),用于描述观察点(假设为针孔相机模型)的位置。
float leaf_size_ float 用于体素滤波的体素大小。注意:应与训练数据一致,否则必须启用 normalize_bins_。
bool normalize_bins_ bool 是否对最终的 CVFH 直方图进行归一化(默认:false)。
float curv_threshold_ float 曲率阈值。用于剔除曲率过大的法向量(表示边界或噪声)。
float cluster_tolerance_ float 欧氏聚类的距离阈值,控制点云中点间的最大聚类距离。
float eps_angle_threshold_ float 法线夹角阈值(弧度)。超过该夹角的法向量不被认为属于同一聚类。
std::size_t min_points_ size_t 聚类中最小点数量,低于此数量的聚类会被舍弃。
float radius_normals_ float 法向量估计时的搜索半径。
std::vector<Eigen::Vector3f> centroids_dominant_orientations_ 向量 每个有效聚类的中心点,用于描述 CVFH 的方向特征。
std::vector<Eigen::Vector3f> dominant_normals_ 向量 每个聚类的法向量中心,用于后续分析。

2、成员函数解析(public)

1. 构造函数
CVFHEstimation()

初始化各参数,包括聚类容差、曲率阈值、法线估计半径等,并设置 feature_name_ = "CVFHEstimation"


2. 法线剔除函数
filterNormalsWithHighCurvature(...)

过滤掉曲率大于设定阈值的法线点(可能是边缘或噪声)。


3. 视点相关函数
setViewPoint(float x, float y, float z)
getViewPoint(float &x, float &y, float &z)

设置和获取视点坐标。视点会影响特征的方向性判断。


4. 参数设置函数
setRadiusNormals(float r)
setClusterTolerance(float d)
setEPSAngleThreshold(float d)
setCurvatureThreshold(float d)
setMinPoints(std::size_t min)
setNormalizeBins(bool normalize)

设置各类算法参数,包括聚类阈值、法线计算半径、法线夹角阈值、是否进行归一化等。


5. 输出聚类信息
getCentroidClusters(std::vector<Eigen::Vector3f> & centroids)
getCentroidNormalClusters(std::vector<Eigen::Vector3f> & centroids)

输出当前计算中每个聚类的几何质心和法向量质心。


6. 特征计算函数(重载)
compute(PointCloudOut &output)

调用基类特征接口,外部调用入口。

computeFeature(PointCloudOut &output) override

核心函数,内部实现 CVFH 特征计算流程,包括体素滤波、聚类、法线角度判断、特征直方图生成等。由 compute() 调用。


7.私有辅助函数(private)
extractEuclideanClustersSmooth(...)

基于欧氏距离 + 法线夹角约束的区域生长聚类方法,结合 KdTree 搜索实现,用于将点云划分为多个平滑的小区域,作为后续 CVFH 特征的输入。


8.继承关系说明
template<typename PointInT, typename PointNT, typename PointOutT = pcl::VFHSignature308>
class CVFHEstimation : public FeatureFromNormals<PointInT, PointNT, PointOutT>

继承自 FeatureFromNormals,意味着该类:

  • 支持基于法向量的特征估计(需要输入 normals)。
  • 遵循 PCL 的 compute() / computeFeature() 调用框架。
  • 默认输出的特征类型为 pcl::VFHSignature308(即 308 维的 CVFH 描述子)。

使用流程小结

  1. 调用 setInputCloudsetInputNormalssetSearchSurface 等设置输入。
  2. 设置参数(视点、聚类阈值、法线半径等)。
  3. 调用 compute(output)
  4. 获得输出的 PointCloud<pcl::VFHSignature308>

三、关键部分代码注释

1、平滑欧几里得聚类(Smooth Euclidean Clustering)实现,用于结合法向量信息进行点云分割


// 定义模板函数,用于从点云中提取带有法向量平滑性的欧几里得聚类
template<typename PointInT, typename PointNT, typename PointOutT>
void pcl::CVFHEstimation<PointInT, PointNT, PointOutT>::extractEuclideanClustersSmooth(
    const pcl::PointCloud<pcl::PointNormal> &cloud,                    // 输入点云,包含点和法向量
    const pcl::PointCloud<pcl::PointNormal> &normals,                  // 法向量点云(通常与 cloud 相同)
    float tolerance,                                                   // 聚类时邻域搜索半径
    const pcl::search::Search<pcl::PointNormal>::Ptr &tree,           // 用于加速搜索的空间索引树(如 kd-tree)
    std::vector<pcl::PointIndices> &clusters,                          // 输出的聚类结果
    double eps_angle,                                                  // 法向量之间的最大夹角(弧度),用于判断平滑性
    unsigned int min_pts_per_cluster,                                  // 每个聚类最小点数
    unsigned int max_pts_per_cluster)                                  // 每个聚类最大点数

输入合法性检查

if (tree->getInputCloud ()->points.size () != cloud.points.size ())
{
  // 搜索树的输入点云与当前输入点云大小不一致,报错返回
  PCL_ERROR ("Tree built for a different point cloud dataset (%lu) than the input cloud (%lu)!\n", 
             tree->getInputCloud ()->points.size (), cloud.points.size ());
  return;
}

if (cloud.points.size () != normals.points.size ())
{
  // 点云与法向量数量不一致,报错返回
  PCL_ERROR ("Number of points in the input point cloud (%lu) different than normals (%lu)!\n", 
             cloud.points.size (), normals.points.size ());
  return;
}

初始化处理标记

// 初始化布尔数组,标记哪些点已被处理,初始为 false(未处理)
std::vector<bool> processed (cloud.points.size (), false);

临时变量声明

std::vector<int> nn_indices;       // 邻域内点的索引
std::vector<float> nn_distances;   // 邻域内点的距离

遍历所有点

for (std::size_t i = 0; i < cloud.points.size (); ++i)
{
  if (processed[i])                // 如果当前点已处理,跳过
    continue;
  processed[i] = true;             // 否则标记为已处理

  pcl::PointIndices r;            // 存储当前聚类点索引
  r.header = cloud.header;
  auto& seed_queue = r.indices;   // 聚类种子队列,初始包含当前点

  seed_queue.push_back (i);

聚类扩展循环(类似 BFS)

  for (std::size_t idx = 0; idx != seed_queue.size (); ++idx)
  {
    // 对当前队列中的点,进行半径搜索找邻居
    if (!tree->radiusSearch (seed_queue[idx], tolerance, nn_indices, nn_distances))
    {
      continue; // 没有邻居,跳过
    }

    // 从第一个邻居开始(索引0是自身,可略过)
    for (std::size_t j = 1; j < nn_indices.size (); ++j)
    {
      if (processed[nn_indices[j]])
        continue; // 已处理的点跳过

      // 计算两个法向量之间的夹角(点积后取 arccos)
      const double dot_p = normals.points[seed_queue[idx]].getNormalVector3fMap().dot(
                           normals.points[nn_indices[j]].getNormalVector3fMap());

      if (std::acos(dot_p) < eps_angle) // 如果夹角小于阈值,认为法向量方向相近(平滑)
      {
        processed[nn_indices[j]] = true;        // 标记为已处理
        seed_queue.emplace_back(nn_indices[j]); // 加入队列继续扩展
      }
    }
  }

判断并保存聚类结果

  // 检查聚类大小是否满足设定的范围要求
  if (seed_queue.size () >= min_pts_per_cluster && seed_queue.size () <= max_pts_per_cluster)
  {
    std::sort (r.indices.begin (), r.indices.end ());
    r.indices.erase (std::unique (r.indices.begin (), r.indices.end ()), r.indices.end ());

    // 将该聚类加入最终聚类列表,使用 std::move 避免复制
    clusters.emplace_back (std::move(r));
  }
}

小结说明

这个函数是 pcl::CVFHEstimation 类中的一个聚类辅助函数,其作用是结合点的位置关系法向量方向的平滑性,进行基于区域增长的聚类(Region Growing)。其核心逻辑是:

  • 利用 radiusSearch 在一定半径内找邻居;
  • 仅当邻居点的法向量与当前点方向相近(小于 eps_angle)时才纳入聚类;
  • 最终将符合大小要求的点集合加入 clusters

2、过滤曲率过高的法向量点

// 模板函数定义,适用于任意类型的输入点、法向量点和输出点
template<typename PointInT, typename PointNT, typename PointOutT>
void pcl::CVFHEstimation<PointInT, PointNT, PointOutT>::filterNormalsWithHighCurvature (
    const pcl::PointCloud<PointNT> & cloud,           // 输入:包含法向量和曲率的点云(如 PointNormal 或 Normal)
    std::vector<int> &indices_to_use,                 // 输入:需要检查的点索引列表
    std::vector<int> &indices_out,                    // 输出:曲率高于阈值的点的索引(被“剔除”的点)
    std::vector<int> &indices_in,                     // 输出:曲率低于或等于阈值的点的索引(保留的点)
    float threshold)                                  // 曲率阈值,超过该值则认为是高曲率点

初始化输出向量大小与计数器

indices_out.resize (cloud.points.size ());
indices_in.resize (cloud.points.size ());

std::size_t in, out;
in = out = 0;
  • 预分配 indices_outindices_in 的容量为最大可能值(全部点),防止频繁 realloc;
  • 使用 inout 两个计数器分别记录当前写入的索引数量。

遍历所有要处理的索引

for (const int &index : indices_to_use)
{
  if (cloud.points[index].curvature > threshold)
  {
    // 当前点的曲率大于阈值,认为是不规则或边缘点,放入剔除集合
    indices_out[out] = index;
    out++;
  }
  else
  {
    // 曲率小于等于阈值,认为是平滑区域,保留
    indices_in[in] = index;
    in++;
  }
}
  • 利用 curvature 属性对每个点判断;
  • 一般高曲率对应于边缘、不规则区域;
  • 平滑区域(低曲率)通常更适合用于特征估计或主方向提取。

缩减输出向量大小

indices_out.resize (out);
indices_in.resize (in);
  • 清除预分配过程中未使用的尾部元素;
  • 保证 indices_outindices_in 中的大小刚好等于有效元素个数。

函数作用

将给定点集根据曲率阈值进行二分类,分为:

  • 高曲率点:通常用于剔除异常/边界点
  • 低曲率点:用于保留用于特征估计或聚类的稳定点

3、计算所点的特征

template<typename PointInT, typename PointNT, typename PointOutT>
void pcl::CVFHEstimation<PointInT, PointNT, PointOutT>::computeFeature(PointCloudOut &output)
{
  // 检查法向量输入是否存在
  if (!normals_)
  {
    PCL_ERROR ("[pcl::%s::computeFeature] No input dataset containing normals was given!\n", getClassName ().c_str ());
    output.width = output.height = 0;
    output.points.clear ();
    return;
  }

  // 检查法向量点数量是否与输入点云数量一致
  if (normals_->points.size () != surface_->points.size ())
  {
    PCL_ERROR ("[pcl::%s::computeFeature] The number of points in the input dataset differs from the number of points in the dataset containing the normals!\n", getClassName ().c_str ());
    output.width = output.height = 0;
    output.points.clear ();
    return;
  }

  // 清空之前计算的主方向质心结果
  centroids_dominant_orientations_.clear ();

  // ---[ 第0步:根据曲率阈值过滤法线(排除高曲率区域)
  std::vector<int> indices_out, indices_in;
  filterNormalsWithHighCurvature(*normals_, *indices_, indices_out, indices_in, curv_threshold_);

  // 构建过滤后(低曲率)的点云
  pcl::PointCloud<pcl::PointNormal>::Ptr normals_filtered_cloud(new pcl::PointCloud<pcl::PointNormal>());
  normals_filtered_cloud->width = static_cast<std::uint32_t>(indices_in.size());
  normals_filtered_cloud->height = 1;
  normals_filtered_cloud->points.resize(normals_filtered_cloud->width);

  for (std::size_t i = 0; i < indices_in.size(); ++i)
  {
    normals_filtered_cloud->points[i].x = surface_->points[indices_in[i]].x;
    normals_filtered_cloud->points[i].y = surface_->points[indices_in[i]].y;
    normals_filtered_cloud->points[i].z = surface_->points[indices_in[i]].z;
  }

  std::vector<pcl::PointIndices> clusters;

  // 如果过滤后的点数大于设定的最小数量,执行基于法线的欧式聚类
  if(normals_filtered_cloud->points.size() >= min_points_)
  {
    // 重新估计法线(对平滑性聚类更稳健)
    KdTreePtr normals_tree_filtered(new pcl::search::KdTree<pcl::PointNormal>(false));
    normals_tree_filtered->setInputCloud(normals_filtered_cloud);

    pcl::NormalEstimation<PointNormal, PointNormal> n3d;
    n3d.setRadiusSearch(radius_normals_);
    n3d.setSearchMethod(normals_tree_filtered);
    n3d.setInputCloud(normals_filtered_cloud);
    n3d.compute(*normals_filtered_cloud);

    // 对估计法线的点云进行平滑聚类(角度 + 距离)
    KdTreePtr normals_tree(new pcl::search::KdTree<pcl::PointNormal>(false));
    normals_tree->setInputCloud(normals_filtered_cloud);

    extractEuclideanClustersSmooth(
      *normals_filtered_cloud,
      *normals_filtered_cloud,
      cluster_tolerance_,
      normals_tree,
      clusters,
      eps_angle_threshold_,
      static_cast<unsigned int>(min_points_));
  }

  // 配置 VFH 特征估计器
  VFHEstimator vfh;
  vfh.setInputCloud(surface_);
  vfh.setInputNormals(normals_);
  vfh.setIndices(indices_);
  vfh.setSearchMethod(this->tree_);
  vfh.setUseGivenNormal(true);
  vfh.setUseGivenCentroid(true);
  vfh.setNormalizeBins(normalize_bins_);
  vfh.setNormalizeDistance(true);
  vfh.setFillSizeComponent(true);
  output.height = 1;

  // ---[ 第1步:判断是否聚类成功
  if (!clusters.empty())
  {
    // ---[ 1.1 对每个主方向聚类,计算平均法线和质心
    for (const auto &cluster : clusters)
    {
      Eigen::Vector4f avg_normal = Eigen::Vector4f::Zero();
      Eigen::Vector4f avg_centroid = Eigen::Vector4f::Zero();

      for (const auto &index : cluster.indices)
      {
        avg_normal += normals_filtered_cloud->points[index].getNormalVector4fMap();
        avg_centroid += normals_filtered_cloud->points[index].getVector4fMap();
      }

      avg_normal /= static_cast<float>(cluster.indices.size());
      avg_centroid /= static_cast<float>(cluster.indices.size());

      Eigen::Vector4f centroid_test;
      pcl::compute3DCentroid(*normals_filtered_cloud, centroid_test);
      avg_normal.normalize();

      Eigen::Vector3f avg_norm(avg_normal[0], avg_normal[1], avg_normal[2]);
      Eigen::Vector3f avg_dominant_centroid(avg_centroid[0], avg_centroid[1], avg_centroid[2]);

      // 保存主方向法线与质心
      dominant_normals_.push_back(avg_norm);
      centroids_dominant_orientations_.push_back(avg_dominant_centroid);
    }

    // ---[ 1.2 针对每个主方向,计算对应的 CVFH 特征
    output.points.resize(dominant_normals_.size());
    output.width = static_cast<std::uint32_t>(dominant_normals_.size());

    for (std::size_t i = 0; i < dominant_normals_.size(); ++i)
    {
      vfh.setNormalToUse(dominant_normals_[i]);
      vfh.setCentroidToUse(centroids_dominant_orientations_[i]);
      pcl::PointCloud<pcl::VFHSignature308> vfh_signature;
      vfh.compute(vfh_signature);
      output.points[i] = vfh_signature.points[0];
    }
  }
  else
  {
    // ---[ 1.3 若未聚类成功,则用整体点云估算 CVFH
    Eigen::Vector4f avg_centroid;
    pcl::compute3DCentroid(*surface_, avg_centroid);
    Eigen::Vector3f cloud_centroid(avg_centroid[0], avg_centroid[1], avg_centroid[2]);
    centroids_dominant_orientations_.push_back(cloud_centroid);

    vfh.setCentroidToUse(cloud_centroid);
    vfh.setUseGivenNormal(false); // 不使用预设法线

    pcl::PointCloud<pcl::VFHSignature308> vfh_signature;
    vfh.compute(vfh_signature);

    output.points.resize(1);
    output.width = 1;
    output.points[0] = vfh_signature.points[0];
  }
}

总结逻辑流程

  1. 检查输入合法性(法线是否存在、数量是否匹配)。

  2. 过滤高曲率点(可提高稳定性)。

  3. 对低曲率区域重新估计法线并聚类

  4. 对每个主方向聚类

    • 计算平均法线与质心。
    • 使用该主方向进行 VFH 特征提取。
  5. 若未聚类成功,对整个点云估算 CVFH 特征。

  6. 将所有 CVFH 特征写入 output

四、使用代码示例

/*****************************************************************//**
 * \file   PCLLearnCVFHFeathermain.cpp
 * \brief
 *
 * \author YZS
 * \date   Jun 2025
 *********************************************************************/

#include <iostream>
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
#include <pcl/features/cvfh.h>
#include <pcl/features/normal_3d.h>
#include <pcl/search/kdtree.h>

int TestCVFH(int argc, char** argv)
{
	// 加载点云
	pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
	std::string file = "E:/db_data/PCLLearnData/cloud_bin_1.pcd";
	if (pcl::io::loadPCDFile<pcl::PointXYZ>(file, *cloud) == -1)
	{
		PCL_ERROR("Couldn't read input file input.pcd\n");
		return -1;
	}

	// 法线估计
	pcl::PointCloud<pcl::Normal>::Ptr normals(new pcl::PointCloud<pcl::Normal>);
	pcl::NormalEstimation<pcl::PointXYZ, pcl::Normal> ne;
	ne.setInputCloud(cloud);
	pcl::search::KdTree<pcl::PointXYZ>::Ptr tree(new pcl::search::KdTree<pcl::PointXYZ>());
	ne.setSearchMethod(tree);
	ne.setRadiusSearch(0.03); // 半径可根据点云密度调整
	ne.compute(*normals);

	// CVFH 估计
	pcl::CVFHEstimation<pcl::PointXYZ, pcl::Normal, pcl::VFHSignature308> cvfh;
	cvfh.setInputCloud(cloud);
	cvfh.setInputNormals(normals);
	cvfh.setSearchMethod(tree);
	cvfh.setRadiusNormals(0.03);               // 法线估计半径
	cvfh.setClusterTolerance(0.05);            // 欧氏聚类容差
	cvfh.setCurvatureThreshold(0.03);          // 曲率阈值
	cvfh.setEPSAngleThreshold(5.0f / 180.0f * static_cast<float>(M_PI)); // 法线夹角阈值
	cvfh.setMinPoints(100);                    // 每个聚类的最小点数

	pcl::PointCloud<pcl::VFHSignature308>::Ptr cvfh_signature(new pcl::PointCloud<pcl::VFHSignature308>);
	cvfh.compute(*cvfh_signature);

	// 输出 CVFH 特征维度和数量
	std::cout << "Computed " << cvfh_signature->size() << " CVFH signature(s) with 308 dimensions each." << std::endl;

	// 输出第一个特征向量
	if (!cvfh_signature->empty()) {
		std::cout << "First CVFH descriptor: " << std::endl;
		for (int i = 0; i < 308; ++i)
			std::cout << cvfh_signature->points[0].histogram[i] << " ";
		std::cout << std::endl;
	}

	return 0;
}


int main(int argc, char** argv)
{
	TestCVFH(argc, argv);
	std::cout << "Hello PCL World!" << std::endl;
	std::system("pause");
	return 0;
}

在这里插入图片描述

至此完成第二十节PCL库点云特征之CVFH特征描述,下一节我们将进入《PCL库中点云特征之RSD(Radius-based Surface Descriptor)特征描述》的学习,欢迎喜欢的朋友订阅。


网站公告

今日签到

点亮在社区的每一天
去签到