Python-PCL点云边缘检测源码造轮子 --- AngleCriterion (附Python代码)

2 篇文章 0 订阅

前言与参考文章

最近在使用python-pcl库来做一个边界检测的相关项目,但python-pcl这个库吧……确实实现了很多功能,但找来找去就是没有boundary-estimation。
不仅没有边界检测,还没有空间直线的交点求解……

  • 所以啊,写一个项目,都在疯狂造python的轮子,很吐血……

好吧,没有边界检测,那我就自己写吧……
在此特别感谢McQueen_LT的文章:

  1. PCL点云边缘检测源码解析(过程记录)—AngleCriterion (附C++代码)
  2. PCL点云边界特征检测 (附完整代码 C++)

这两篇文章思路清晰,介绍算法和代码详解也很不错
【但最终1文章代码的72行有一个小错误(🤫)size应该是size-1】

代码详解

Dependency

pip3 install numpy

Code

Talk is cheap,show me the code.

#!/usr/bin/env python
# -*- coding: utf-8 -*-

'''
Author  : Huang Chenrui
Email   : hcr2077@outlook.com
Version : 1.0.0
Date    : Aug 02, 2021
Reference: https://blog.csdn.net/McQueen_LT/article/details/118298329
'''

import pcl 
import pcl.pcl_visualization
import numpy as np
FLT_EPSILON = 1e-1 # 这个数我瞎设的,他说ismuchsmaller我也不知道具体应该多少

'''
Inputs:
    points_array: all points array
    point_array: single point
    neighbor_idx: kdtree nearest indice
    u,v: getCoordinateSystemOnPlane
    angle_threshold: rad
Outputs:
    True or False
Reference: https://pointclouds.org/documentation/boundary_8hpp_source.html
'''
def isBoundaryPoint(points_array, point_array, neighbor_idx, u, v, angle_threshold):
    if neighbor_idx.size <= 3: return False
    max_dif = 0
    cp = 0
    angle_array = np.zeros(neighbor_idx.size)
    for i in range(neighbor_idx.size):
        delta = points_array[neighbor_idx[0][i]] - point_array # 1 * 3 XYZ
        if all(delta == np.zeros(3)): continue
        angle = np.arctan2(np.dot(v, delta), np.dot(u, delta)) # (rad)the angles are fine between -PI and PI too
        angle_array[cp] = angle
        cp = cp + 1
    if cp == 0: return False
    angle_array = np.resize(angle_array, cp)
    angle_array = np.sort(angle_array) # 升序
    # Compute the maximal angle difference between two consecutive angles
    for i in range(angle_array.size - 1):
        dif = angle_array[i+1] - angle_array[i]
        if max_dif < dif: max_dif = dif
    # Get the angle difference between the last and the first
    dif = 2 * np.pi - angle_array[angle_array.size - 1] + angle_array[0]
    if max_dif < dif: max_dif = dif

    return (max_dif > angle_threshold)

'''
Inputs: tuple
Outputs: array( np.array([[]]) )
i.e: np.array([[1,2,3]])
# L109
# https://pointclouds.org/documentation/cuda_2common_2include_2pcl_2cuda_2common_2eigen_8h_source.html
'''
def unitOrthogonal(tuple):
    def isMuchSmallerThan(x, y):
        global FLT_EPSILON
        prec_sqr = FLT_EPSILON * FLT_EPSILON
        if x*x <= prec_sqr*y*y:
            return True
        else:
            return False
    if (not isMuchSmallerThan(tuple[0], tuple[1])) or (not isMuchSmallerThan(tuple[1], tuple[2])):
        invum = 1.0 / np.sqrt(tuple[0]**2 + tuple[1]**2)
        perp_x = -tuple[1] * invum
        perp_y = tuple[0] * invum
        perp_z = 0.0
    else:
        invum = 1.0 / np.sqrt(tuple[2]**2 + tuple[1]**2)
        perp_x = 0.0
        perp_y = -tuple[2] * invum
        perp_z = tuple[1] * invum
    perp_array = np.array([[perp_x, perp_y, perp_z]])
    return perp_array

'''
Inputs: point_normal(pcl.PointCloud_Normal)注意这里的normal已经是单位向量了而且是个tuple
pcl::Normal::Normal	(	
float 	n_x,
float 	n_y,
float 	n_z,
float 	_curvature
)		
注意哦:他们四个数的平方和是1,在之前算法线的时候已经算完了
Outputs: u and v
'''
def getCoordinateSystemOnPlane(point_normal):
    normal_tuple = point_normal
    # v = p_coeff_v.unitOrthogonal();
	# u = p_coeff_v.cross3(v);
    # v是normal的正交向量
    v = unitOrthogonal(normal_tuple)
    u = np.cross(point_normal[:3], v)
    return u, v

'''
Inputs: pointCloud(pcl.pointCloud)
Outputs: normals(pcl.PointCloud_Normal)
Reference by demo NormalEstimation.py
'''
def compute_normals(pointCloud):
    ne = pointCloud.make_NormalEstimation()
    tree = pointCloud.make_kdtree()
    ne.set_SearchMethod(tree)
    ne.set_KSearch(30)
    normals = ne.compute()
    return normals

'''
Inputs: 3D-pointcloud(points array) (n*3)XYZ
Outputs: 3D-pointcloud(points array) (n*3)XYZ
'''
def boundary_detection(points):
    # Create a PointCloud
    points[np.isnan(points)] = 0 # Delete NAN
    xyz_points = points[:, :3] # Change n*5 to n*3
    pc = pcl.PointCloud(xyz_points.astype(np.float32))
    # Calc Normals 计算法线
    pc_normals = compute_normals(pc)
    # Build a kdtree
    pc_kdtree = pc.make_kdtree_flann()

    boundary_array = np.zeros((1,3))
    for i in range(len(xyz_points)):
        # Find nearestKSearch points
        K = 40
        searchPoint = pcl.PointCloud()
        searchPoint.from_array(np.array([xyz_points[i]]))
        [neighbor_idx, neighbor_dist] = pc_kdtree.nearest_k_search_for_cloud(searchPoint, K)
        # pc[neighbor_idx[0][i]]
        # getCoordinateSystemOnPlane, Obtain a coordinate system on the least-squares plane
        u, v = getCoordinateSystemOnPlane(pc_normals[i])
        # isBoundaryPoint
        if isBoundaryPoint(xyz_points, xyz_points[i], neighbor_idx, u, v, angle_threshold=1.5):
            boundary_array = np.vstack([boundary_array, xyz_points[i]])
    boundary_array = np.delete(boundary_array, 0, axis=0)

    return boundary_array



'''
Test Code use bunny.pcd
调整那个threshold哈
就是isBoundaryPoints()的那个angle_threshold
kdtree的K我也是瞎设的,可以都调调看
'''
pc = pcl.load("bunny.pcd")
pc_array = pc.to_array()
boundary_array = boundary_detection(pc_array)

boundary_pc = pcl.PointCloud()
boundary_pc.from_array(boundary_array.astype(np.float32))

visual = pcl.pcl_visualization.CloudViewing()
visual.ShowMonochromeCloud(boundary_pc)
flag = True
while flag:
    if visual.WasStopped() == True:
        flag = False
        break

Function

整个流程就是遍历每个点,对每个点找近邻,并调用getCoordinateSystemOnPlane()对每个点计算 u 和 v 这两个向量,然后调用isBoundaryPoint()函数,判断每个点是否是边缘点

boundary_detection()

调用的主函数,主要包括了算法线、KDTree、找近邻、算uv、判定边缘点这五大块。

compute_normals()

getCoordinateSystemOnPlane()

  • getCoordinateSystemOnPlane()函数的意思是在当前点建立坐标系,分别法线n、法线与z轴(或x轴)叉乘得到的向量v,以及法线n与u的叉乘得到的向量u建立局部正交坐标系(n, u, v均已经过单位化处理)。
  • 关于unitOthogonal()这个函数的具体实现见这里#L109
  • cross()就是两个3维向量的叉乘,用numpy操作即可。

isBoundaryPoints()

  • 参考:PCL具体实现#L62
  • 注意这里的for (std::size_t i = 0; i < angles.size () - 1; ++i)

测试

python3 boundary_estimation.py

效果:

  • 原图
    在这里插入图片描述
  • 边缘检测后
    在这里插入图片描述
    其实效果还是可以的,如果再加上体素滤波等操作,整体的点会更加有特征一些,读者可以自己试着做一下。

致谢

本文主要参考了McQueen_LT的文章,相当于把PCL的C++库用python-pcl重新造了一下轮子。
有时候适当的造轮子还是必要的,写完代码也让我对于AngleCriterion这个算法的理解更深了一层。
在此附上论文地址:Detecting Holes in Point Set Surfaces

请各位读者不吝批评指正。

  • 21
    点赞
  • 50
    收藏
    觉得还不错? 一键收藏
  • 16
    评论
### 回答1: Python-PCL是一个Python绑定库,它提供了对PointCloud库的访问。点云网格化算法是将点云数据进行处理后生成一张网格表面的算法。网格化可以使点云数据更容易可视化、分析和处理。Python-PCL点云网格化算法可以将点云数据分割成均匀分布的三角形网格,并构建一个平滑的网格表面。 Python-PCL点云网格化算法的主要步骤包括: 1.加载点云数据并将其转换为PointNormal格式,以便于法线估计和网格化操作。 2.对点云数据进行法线估计。该过程使用一种求点云曲率的技术,将每个点的法线计算出来,并保存在其对应的点中。 3.将法线信息与点云数据结合,构建一个SurfaceReconstruction3D对象来执行网格化操作。在这个过程中,算法使用一个梯度下降优化算法,将点云数据映射到一个均匀分布的三角形网格上。优化约束条件限制每个三角形的边长,使网格表面更加平滑。 4.最后根据三角形间的相交情况对网格进行修复,并输出网格数据。在这个过程中,算法检查每个三角形是否与其他三角形相交,并尝试通过联通、压缩网格等修复操作,使网格曲面更加平滑。 Python-PCL点云网格化算法是一种实现点云数据可视化、处理和分析的有效工具。在3D打印、机器人视觉和虚拟现实等领域都有广泛的应用。 ### 回答2: Python-PCL是一个PythonPCL(点云库)封装库,允许用户在Python中使用PCL中的众多算法。点云网格化算法就是Python-PCL之中的一个非常重要的算法。 点云网格化算法是将点云数据转化为网格表示的过程,在这个过程中,将点云数据转换为一个等间距的二元矩阵表示。由于点云数据是一个由众多点组成的体,因此,这个矩阵是被空缺的网格填充而成的。在这个过程中,点云数据通过多次计算,不断地从自由形状变为一种规则形状,从而提高点云数据的处理效率。 点云网格化算法是通过点云曲面重构的算法实现的。曲面重构算法能够将点云数据转化为一个规则的几何模型,从而为相关应用提供了非常大的便捷,因此,点云网格化算法的应用非常广泛,例如:机器视觉,三维打印等方面。在Python-PCL的应用中,点云网格化算法的实现基于VTK(Visualization ToolKit)工具包,通过VTK包中的vtkPolyDataMapper类实现点云网格化。 点云网格化算法的实现需要通过多次迭代计算,使点云数据沿着均匀分布的空间方向发生变化,从而最终能够得到一种规则的几何形状。在实际应用中,点云数据可能会受到很多干扰,例如:噪音、数据不完整等因素,这对点云网格化算法的计算效率提出了非常高的要求,因此,Python-PCL提供了一系列优化的算法来满足实际应用的需求。 ### 回答3: Python-PCL是Point Cloud Library的Python封装库,提供了一组Python模块,可以在Python中使用PCL的功能。PCL是一个C++库,是开发点云处理算法和应用的一流工具,具有许多点云处理功能,如配准、滤波、分割、聚类、特征提取和识别等。 点云网格化是一种将点云转化为网格模型的技术。在点云数据中,所有的点都是离散的,而在网格模型中,点是有序的,因此点云网格化是将离散的点云数据转换为有序的网格数据的过程。 Python-PCL库提供了函数pcl.surface包来执行点云到三角网格转换,使用点云同步方法定义的半径搜索方式构建三角网格。 下面是一个简单的例程,它将点云转换为三角网格: ```python import pcl cloud = pcl.PointCloud.PointXYZRGB() mesh = cloud.make_mesh_cuboid() print('PointCloud before meshing') print(cloud) print('PointCloud after meshing') print(mesh.cloud) print(mesh.polygons) ``` 该例程首先定义了一个PointCloud对象,然后使用make_mesh_cuboid()函数将其转换为三角网格。最后,程序将输出PointCloud以及转换后的mesh。由于make_mesh_cuboid()函数是在一个立方体形状上生成网格对象,因此输出结果可能会有所不同。 总体而言,Python-PCL是一个非常强大的工具,它为Python程序员提供了使用点云数据处理的功能。尤其是对于点云网格化算法,Python-PCL提供了一个方便的方式来将点云数据转换为有序的网格数据。这个库是在点云领域做开发非常有帮助的,它为点云的开发和应用提供了大量的可靠和高效的处理工具。
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值