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
    评论
在卫星时间序列数据中检测变点、趋势和季节性是一项重要的任务。卫星时间序列数据是通过卫星观测到的地球表面上的连续观测数据。这些数据可以用于监测和分析地球表面的变化,如气象、土地利用和植被覆盖等。 首先,检测变点是指在时间序列中找到突变或结构转变的点。变点可能代表了不同的影响因素引起的突变,例如自然事件、人为活动或仪器故障等。通过分析时间序列数据的变化趋势,可以使用一些统计方法来检测这些变点。 其次,趋势是指时间序列数据中长期的变化方式。有时,卫星时间序列数据中的变化可能会逐渐增长或减少,这可以被称为趋势。通过对时间序列数据进行回归分析或移动平均处理,我们可以检测和评估这种趋势。 季节性是指在一年内周期性的重复出现的模式。例如,地表温度通常会因季节变化而发生变化,夏季温度高,冬季温度低。对于卫星时间序列数据,我们可以通过分析数据的周期性变化来检测季节性。一种常用的方法是使用季节分解技术,如STL分解,将时间序列分解成长期趋势、季节变化和随机噪声部分。 通过检测卫星时间序列数据中的变点、趋势和季节性,我们可以更好地理解地球表面的变化,并为环境监测和资源管理提供更准确的信息。这些分析结果可以用于研究气候变化、土地利用变化、植被变化等,以及评估其对环境和人类社会的影响。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值