传统点云分割三部曲(二)——LCCP(Locally Convex Connected Patches)

一、前言

传统方法进行点云分割也有很好的效果,这篇博客是本系列的第二篇,介绍的是发表于2014年CVPR上的LCCP算法,LCCP原文 。VCCS是使用LCCP进行分割前的预处理工作,该方法的介绍可以看上一篇博客,LCCP的效果如下,

二、LCCP论文阅读

2.1 摘要部分

本文提出了一种高效的,无需学习的点云分割方法,避免了对数据集的需求。算法首先利用VCCS获得supervoxel的邻接图,然后判断邻接图的每条边是凸边还是凹边。以这种方式,邻接图被分成了一系列的可以用来代表物体的局部凸连接子图。除此以外,本文还提出了depth dependent voxel grid(DDVG)来解决点云密度随距离降低的问题。下面是LCCP算法的流程图:

2.2 方法部分

LCCP的灵感来自于心理物理学(以科学的方法研究刺激与感觉的关系)中的一个观点,人具有将物体分解成不同部分的能力,与对3D凹凸性的判断是密不可分的。

2.2.1 Building the Surface-Patch Adjacency Graph

本文使用了该实验室在2013年CVPR发表的工作VCCS来将点云数据聚类成一系列的patches,这被他们称作supervoxel,VCCS的核心算法是一个考虑了连通性和流动性的k-means,最后的结果是输出一些独立的超体素 p i ⃗ = ( x i ⃗ , n i ⃗ , N i ) \vec{p_i}=(\vec{x_i}, \vec{n_i}, N_i) pi =(xi ,ni ,Ni) x i ⃗ \vec{x_i} xi 是超体素的质心, n i ⃗ \vec{n_i} ni 是法向, N i N_i Ni是一个集合,包含了当前超体素的邻接边信息。

但文中所说,LCCP对VCCS进行了一些优化:1)使用邻接图来判断计算法线所用的近邻点,而不是简单的radius-search;2)使用了一种新的法线计算方法[1]。目的是为了锐利边缘处的法线能更准确

如何准确高效的计算锐利边缘的法线,是一直困扰我的问题,这篇论文里的效果图看起来很好,有时间再学习一下

2.2.2 Locally Convex Connected Patches(LCCP)

这部分是本文的核心,目的是判断两个邻接体素的连接 e = ( p i ⃗ , p j ⃗ ) e=(\vec{p_i},\vec{p_j}) e=(pi ,pj )是凸的还是凹的,凸的认为是有效,而凹的视为无效(再看看,思考下这种想法的合理性),判断基于两个准测——Extended Convexity Criterion(CC)和Sanity criterion(SC)。

2.2.2.1 Extended Convexity Criterion(EC)

CC利用两个超体素的质心连线与法线来判断二者的凹凸关系 ,前提是法线的指向得是正确的,论文中法线要指向相机,如果法线反向或者不连续,这个准则将不再适用。

定义质心连线 d ⃗ = x 1 ⃗ − x 2 ⃗ \vec{d}=\vec{x_1}-\vec{x_2} d =x1 x2 α 1 \alpha_1 α1 α 2 \alpha_2 α2分别是两条法线与质心连线的夹角,可以观察到对于凸连接 α 1 < α 2 \alpha_1<\alpha_2 α1<α2,即 ( n 1 ⃗ − n 2 ⃗ ) ⋅ d > 0 (\vec{n_1}-\vec{n_2})\cdot d>0 (n1 n2 )d>0,凹连接 α 1 > α 2 \alpha_1>\alpha_2 α1>α2。并且不需要担心二者的顺序,计算是可交换的。

在平坦区域,理想情况下 β = ∣ α 1 − α 2 ∣ = 0 \beta=|\alpha_1-\alpha_2|=0 β=α1α2=0,但是因为噪声存法线往往存在误差,导致 β \beta β在零点附近不是很稳定,所以在此处作者引入了 β T h r e s h \beta_{Thresh} βThresh(Concavity tolerance threshold)来提升 β \beta β在零点附近的稳定性。平坦区域理应不做划分,所以对于 β < β t h r e s h \beta<\beta_{thresh} β<βthresh部分很自然归类为有效的凸类,基础版本 C C b CC_b CCb如下:
C C b ( p i ⃗ , p j ⃗ ) = { true if  ( n 1 ⃗ − n 2 ⃗ ) ⋅ d ⃗ > 0  or  ( β < β thresh ) false otherwise CC_b(\vec{p_i}, \vec{p_j}) = \begin{cases} \text{true} & \text{if } (\vec{n_1} - \vec{n_2}) \cdot \vec{d} > 0 \text{ or } (\beta < \beta_{\text{thresh}}) \\ \text{false} & \text{otherwise} \end{cases} CCb(pi ,pj )={truefalseif (n1 n2 )d >0 or (β<βthresh)otherwise
但这样处理会使得小幅度凹面容易被忽略掉,所以作者使用更强的凸性判断标准,找到 p i ⃗ \vec{p_i} pi p j ⃗ \vec{p_j} pj 的共同邻居 p c ⃗ \vec{p_c} pc ,需要 C C b ( p i ⃗ , p c ⃗ ) CC_b(\vec{p_i},\vec{p_c}) CCb(pi ,pc ) C C b ( p j ⃗ , p c ⃗ ) CC_b(\vec{p_j},\vec{p_c}) CCb(pj ,pc )均为true,才会最终确定 e = ( p i ⃗ , p j ⃗ ) e=(\vec{p_i},\vec{p_j}) e=(pi ,pj )为凸连接。完整的凸性判断准则 C C e CC_e CCe如下:
C C e ( p i ⃗ , p j ⃗ ) = C C b ( p i ⃗ , p j ⃗ ) ∧ C C b ( p i ⃗ , p c ⃗ ) ∧ C C b ( p j ⃗ , p c ⃗ ) CC_e(\vec{p_i}, \vec{p_j}) = CC_{b}(\vec{p_i}, \vec{p_j}) \land CC_{b}(\vec{p_i}, \vec{p_c}) \land CC_{b}(\vec{p_j}, \vec{p_c}) CCe(pi ,pj )=CCb(pi ,pj )CCb(pi ,pc )CCb(pj ,pc )

2.2.2.2 Sanity criterion(SC)

对于一些奇异位置,判断他们连接是凹还是凸是没有意义的,这种位置经常会出现,如下箭头所指位置:

在这种情况下,两个patch的相对关系可能会呈现右边左下子图的样子,因此需要分辨这种情况,视为无效连接。

文中定义了 v ( p 1 ⃗ , p 2 ⃗ ) = m i n ( ∠ ( d ⃗ , s ⃗ ) , 180 ° − ∠ ( d ⃗ , s ⃗ ) ) \mathcal{v}(\vec{p_1},\vec{p_2})=min(\angle (\vec{d},\vec{s}),180\degree-\angle(\vec{d},\vec{s})) v(p1 ,p2 )=min((d ,s ),180°(d ,s )),其中 s ⃗ = n 1 ⃗ × n 2 ⃗ \vec{s}=\vec{n_1}\times\vec{n_2} s =n1 ×n2 ,上图中按箭头方向 v \mathcal{v} v逐渐减小。可以看到当 v \mathcal{v} v比较小的时候,连接是无效的,所以引入 v T h r e s h \mathcal{v}_{Thresh} vThresh,当 v < v T h r e s h \mathcal{v}<\mathcal{v_{Thresh}} v<vThresh时,连接无效。 v T h r e s h v_{Thresh} vThresh是一个以 β \beta β(两个法向量夹角)为自变量的sigmoid函数——一个柔和的阶跃函数

这个地方也是为了考虑噪声的影响,如果相差法线相差较小,就放宽限制。(还需要注意对 β = 0 \beta=0 β=0时的特殊处理,但这地方总觉得怪怪的。。。。)

最后一个连接是否有效,要由这两个准则同时判断:
c o n v ( p i ⃗ , p j ⃗ ) = C C e ( p i ⃗ , p j ⃗ ) ∧ S C ( p i ⃗ , p j ⃗ ) conv(\vec{p_i}, \vec{p_j}) = CC_{e}(\vec{p_i}, \vec{p_j}) \land SC(\vec{p_i}, \vec{p_j}) conv(pi ,pj )=CCe(pi ,pj )SC(pi ,pj )
判断完每条边的属性之后,沿凸边使用区域生长法。接着对于区域生长法生成的每个聚类,检查其中包含的超体素个数,如果个数小于 n f i l t e r n_{filter} nfilter,就把他合并到相邻的最大聚类中,文中 n f i l t e r n_{filter} nfilter使用的是3。

2.2.3 Depth Dependent Voxel Grid(DDVG)

这一部分作者提出了一个可以应用于所有RGB-D应用中的小技巧,随着距离的增加,点云密度会不断减小并且误差也会以距离的二次方增加。这个问题也严重影响了VCCS的效果,这在上一篇博客的最后可以明显的看到。

文中对原始点云 ( x , y , z ) (x,y,z) (x,y,z)做如下变换:
x ′ = x / z , y ′ = y / z , z ′ = l o g ( z ) x'=x/z,y'=y/z,z'=log(z) x=x/z,y=y/z,z=log(z)
这步变换会使得变换后的点云在x-y平面上分布是均匀的,并且z轴也被压缩,这样点的密度就不会像变换前那样随深度增加而降低的那么剧烈。

并且这个变换有个很好的性质,即:
∂ x ′ ∂ x = ∂ y ′ ∂ y = ∂ z ′ ∂ z = 1 z \frac{\partial x'}{\partial x}=\frac{\partial y'}{\partial y}=\frac{\partial z'}{\partial z}=\frac{1}{z} xx=yy=zz=z1
也就是说在各个方向被相同的拉伸了,这可以保证原来的体素还会以。。。。。。。。。。这个变换对于VCCS的效果提升是显著的,如下图所示在这里插入图片描述

三、实际效果与调参建议

四、参考文献


[1]Boulch A, Marlet R. Fast and robust normal estimation for point clouds with sharp features[C]//Computer graphics forum. Oxford, UK: Blackwell Publishing Ltd, 2012, 31(5): 1765-1774.

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
【资源说明】 基于lccp与边缘检测的三维点云分割方法c++源码(含项目使用说明+测试数据).zip 主要内容:改善维实例分割生成的点云,并生成分割点云地图。 测试环境 系统:win10 编译器:MSVC2017 64bit、cmake 3.5 第三方库:pcl 1.9.1、boost 1.6.8、opencv 4.2.0、yaml 0.6.0 实例分割网络:YOLACT++,权重:yolact_plus_resnet50_54_800000 测试数据集 TUM数据集 rgbd_dataset_freiburg1_room序列 百度网盘 链接:https://pan.baidu.com/s/1sMxa3skD_Uix4TYjzioLyQ 提取码:1wpe 使用方法: 编译文件 首先编译yaml_cpp cd yaml_cpp mkdir build cd build cmake [-G generator] [-DBUILD_SHARED_LIBS=ON|OFF] .. make 编译本工程 mkdir build cd build cmake [-G generator] [-DBUILD_SHARED_LIBS=ON|OFF] .. make 执行命令 "SegmentMap.exe --Dataset" 数据集目录 -t 数据集目录/KeyFrameTrajectory.txt -c 0.2 -s 0.4 -n 0.6 -C 15 -S 0.01" 【备注】 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载使用,也适合小白学习进阶,当然也可作为毕设项目、课程设计、作业、项目初期立项演示等。 3、如果基础还行,也可在此代码基础上进行修改,以实现其他功能,也可直接用于毕设、课设、作业等。 欢迎下载,沟通交流,互相学习,共同进步!
点云超体聚类+LCCP程序是一种将点云分割成多个超体素,并在每个超体素内进行聚类的方法,并使用LCCP算法进行分割的程序。以下是一份基于Python和open3d库实现的点云超体聚类+LCCP程序的代码示例: ```python import open3d as o3d # 读取点云 pcd = o3d.io.read_point_cloud("point_cloud.pcd") # 超体素聚类 voxel_size = 0.05 # 超体素大小 pcd = pcd.voxel_down_sample(voxel_size) # 下采样 labels = pcd.cluster_dbscan(eps=0.05, min_points=10) # DBSCAN聚类 max_label = labels.max() print(f"point cloud has {max_label + 1} clusters") # LCCP分割 segmentation = pcd.segment_plane(distance_threshold=0.01, ransac_n=3, num_iterations=1000) segment_labels = np.asarray(segmentation[1]) pcd_normals = pcd.compute_point_cloud_normals(knn=knn) pcd_labels = np.array(labels) pcd_segments = o3d.geometry.PointCloud() for segment_id in np.unique(segment_labels): segment_mask = segment_labels == segment_id segment_pcd = pcd.select_by_index(np.where(segment_mask)[0]) segment_normals = pcd_normals.select_by_index(np.where(segment_mask)[0]) segment_labels = np.array(pcd_labels[np.where(segment_mask)[0]]) lccp = segment_pcd.segment_local_maxima( search_radius=0.2, suppress_radius=0.1, normal_radius=0.1, method=o3d.geometry.SegmentLocalMaximaMethod.LCCP) segments = lccp.get_segmented_point_cloud() segments.normals = segment_normals segments.colors = np.array([segment_id / len(np.unique(segment_labels)) for i in range(len(segments.points))]) pcd_segments += segments # 可视化结果 o3d.visualization.draw_geometries([pcd_segments]) ``` 其中,`read_point_cloud()`函数用于读取点云数据,`voxel_down_sample()`函数用于进行超体素分割的下采样操作,`cluster_dbscan()`函数用于进行DBSCAN聚类,`segment_plane()`函数用于平面分割,`compute_point_cloud_normals()`函数用于计算点云法向量,`select_by_index()`函数用于选取指定索引的点,`segment_local_maxima()`函数用于进行LCCP分割,`get_segmented_point_cloud()`函数用于获取分割结果,`draw_geometries()`函数用于可视化分割结果。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

糊烟乱雨

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值