点云凹凸性检验(2/2)

凹凸性

当我们持有表面点云数据时。可能从某个方向看向数据,此区域是凹陷的,但是当我们从背向看过来时其又是凸起的。这使得我们产生一个误会:在判断点云的凹凸性之前,是否需要先规定一下点云的表侧和里侧?

事实上在大多数情况下,我们仅仅需要。通过凹凸性检验的方法,将大面积的点云进行语义分割而已,如果这一半数据不是我想要的凹面或凸面,只需要抽取另一半的数据就好了。

凹凸性检验

逻辑

单个点云凹凸性逻辑如下图:

当P1点的曲率大小达到一定的阈值时,我们希望知道此处点云的凹凸性。

考虑到此逻辑计算的复杂程度,在处理之前先缩小一下待处理点的范围,仅曲率较大的点做凹凸性的判断。

曲率的公式如下:

一般认为是协方差的最小特征值/其他特征值之和

流程

1.通过最小生成树估计的我们可以获得P1点的法向量V1,逻辑图中可以看出这个曲面是凹陷的。

2.P1点通过open3d的k临近查找,可以获得最近三个点的坐标索引。使用此三点构建空间平面。

3.使用P1点对该平面进行投影,而投影点和原点P1可构成另一检测向量V2。

4.计算V1,V2夹角,一般认为夹角小于90度的就可以视为凹陷面了,反之为凸面。

完整代码

注意,以下代码的执行步骤是先执行“1.抽取曲率范围”“2.法向量估计”。将这两步输出数据的地址分别填入“3.检测向量与计算”的data_cur和data_vec就可以执行“3.检测向量与计算”

1.抽取曲率范围

import open3d as o3d
import numpy as np
import os


def pca_compute(data, sort=True):
    """
    点云的特征值与特征向量
    :return: 特征值与特征向量
    """
    average_data = np.mean(data, axis=0)
    decentration_matrix = data - average_data

    H = np.dot(decentration_matrix.T, decentration_matrix) 
    eigenvectors, eigenvalues, eigenvectors_T = np.linalg.svd(H) 
    if sort:
        sort = eigenvalues.argsort()[::-1] 
        eigenvalues = eigenvalues[sort] 
    return eigenvalues

def caculate_surface_curvature(radius,pcd):
    """
    计算点云的表面曲率
    :return: 点云中每个点的表面曲率
    """
    cloud = pcd
    points = np.asarray(cloud.points)
    kdtree = o3d.geometry.KDTreeFlann(cloud)
    num_points = len(cloud.points)

    curvature = []  # 储存表面曲率
    for i in range(num_points):
        k, idx, _ = kdtree.search_radius_vector_3d(cloud.points[i], radius)

        neighbors = points[idx, :]
        w = pca_compute(neighbors)  
        delt = np.divide(w[2], np.sum(w), out=np.zeros_like(w[2]), where=np.sum(w) != 0)
        curvature.append(delt)
    curvature = np.array(curvature, dtype=np.float64)

    return curvature

def curvature_normal():
    '''
    传入的曲率curvature归一化,传出诡异数据到绘图模块
    :return:
    '''
    data_normal = caculate_surface_curvature(radius,pcd)
    ave = np.mean(data_normal)
    data_max = max(data_normal)
    data_min = min(data_normal)
    cur_normal = [(float(i) - data_min) / (data_max - data_min) for i in data_normal]

    return cur_normal

def draw(cur_max,cur_min,pcd):
    '''
    绘图法向量绘图,曲率可视化绘图
    :return:
    '''
    cur_normal = curvature_normal()
    downpcd_normals = pcd
    print(pcd)
    print(cur_normal)

    pcd.paint_uniform_color([0.5,0.5,0.5])
    for i in range(len(cur_normal)):
        if 0 < cur_normal[i] <= cur_min:
            np.asarray(pcd.colors)[i] = [1, 0, 0]
        elif cur_min < cur_normal[i] <= cur_max:
            np.asarray(pcd.colors)[i] = [0, 1, 0]
        elif cur_max < cur_normal[i] <= 1:
            np.asarray(pcd.colors)[i] = [0, 0, 1]

    # 曲率分割基准
    o3d.visualization.draw_geometries([downpcd_normals],window_name="可视化原始点云",
                                      width=800, height=800, left=50, top=50,
                                      mesh_show_back_face=False)
    return None

def save_txt1(cur_min,filename):
    '''
    存1列txt

    :return:
    '''
    un1 = []
    cur_normal = curvature_normal()
    for i in range(len(cur_normal)):
        if cur_normal[i] > cur_min:
            un1.append(i)


    savefilename = "%s"%(filename) + ".txt"
    savefilename = "D:/db/txt/" + savefilename
    if not os.path.exists(savefilename):
        f = open(savefilename, 'w')
        f.close()
    with open(savefilename, 'w') as file_to_write:
        for i in range(len(un1)):
            file_to_write.writelines(str(un1[i]) + "\n")
    return None


if __name__ == '__main__':
    cur_max = 0.7
    cur_min = 0.4
    radius = 0.07
    pcd = o3d.io.read_point_cloud("D:/db/bunny_pcd.pcd") 
    caculate_surface_curvature(radius,pcd)
    print(caculate_surface_curvature(radius,pcd))
    curvature_normal()
    draw(cur_max,cur_min,pcd)


    filename = "data_cur"
    # 使用此函数记得更改文件名!!!!!!!!
    save_txt1(cur_min,filename)

 

图中蓝绿色点部分为曲率大于0.4的部分区域,而蓝色部分为曲率大于0.7的区域 。现抽取的点为大于0.4的点。

2.法向量估计

import open3d as o3d
import numpy as np
import os
pcd = o3d.io.read_point_cloud("D:/db/bunny_pcd.pcd")


pcd.paint_uniform_color([1.0, 0.0, 0.0])
pcd.estimate_normals(search_param=o3d.geometry.KDTreeSearchParamHybrid(radius=0.01, max_nn=30)) #
pcd.orient_normals_consistent_tangent_plane(8)  
print(np.asarray(pcd.normals)[:10, :])

def save_txt3(xlist,ylist,zlist,filename):
    '''
    存3列txt
    :return:
    '''
    savefilename = "%s"%(filename) + ".txt"
    savefilename = "D:/db/txt/" + savefilename
    if not os.path.exists(savefilename):
        f = open(savefilename, 'w')
        f.close()
    with open(savefilename, 'w') as file_to_write:
        for i in range(len(xlist)):
            file_to_write.writelines(str(xlist[i]) + " " + str(ylist[i]) + " " + str(zlist[i]) + "\n")
    return None

xlist = []
ylist = []
zlist = []
for i in range(len(pcd.points)):
    xlist.append(np.asarray(pcd.normals)[i][0])
    ylist.append(np.asarray(pcd.normals)[i][1])
    zlist.append(np.asarray(pcd.normals)[i][2])

filename = "data_vec"
# 使用此函数记得更改文件名!!!!!!!!
save_txt3(xlist,ylist,zlist,filename)

o3d.visualization.draw_geometries([pcd], point_show_normal=True, window_name="朝向相机位置",
                                  width=1024, height=768,
                                  left=50, top=50,
                                  mesh_show_back_face=False)  # 可视化点云和法线




3.检测向量与计算

import open3d as o3d
import numpy as np
import matplotlib.pyplot as plt

def project(data_cur,pcd):
    '''
    计算检测向量
    :return:
    '''
    vector_list = []

    pcd_tree = o3d.geometry.KDTreeFlann(pcd)
    for i in data_cur:
        # 由原点查找临近的三点
        k, idx, _ = pcd_tree.search_knn_vector_3d(pcd.points[int(i)], 3)



        # 原点
        x, y, z = pcd.points[idx[0]]
        # 平面点
        x1, y1, z1 = pcd.points[idx[0]]
        x2, y2, z2 = pcd.points[idx[1]]
        x3, y3, z3 = pcd.points[idx[2]]

        # 平面方程
        A = y2 * z3 - y2 * z1 - y1 * z3 - y3 * z2 + y1 * z2 + y3 * z1
        B = x3 * z2 - x1 * z2 - x3 * z1 - x2 * z3 + x2 * z1 + x1 * z3
        C = x2 * y3 - x2 * y1 - x1 * y3 - x3 * y2 + x3 * y1 + x1 * y2
        D = x1 * y3 * z2 + x2 * y1 * z3 + x3 * y2 * z1 - x1 * y2 * z3 - x3 * y1 * z2 - x2 * y3 * z1

        A, B, C, D = -A, -B, -C, -D


        # 投影点
        xp = ((B ** 2 + C ** 2) * x - A * (B * y + C * z + D)) / (A ** 2 + B ** 2 + C ** 2)
        yp = ((A ** 2 + C ** 2) * y - B * (A * x + C * z + D)) / (A ** 2 + B ** 2 + C ** 2)
        zp = ((A ** 2 + B ** 2) * z - C * (A * x + B * y + D)) / (A ** 2 + B ** 2 + C ** 2)

        # 垂直向量
        n_ver = [xp - x, yp - y, zp - z]
        vector_list.append(n_ver)

    return vector_list

def vectorial_angle(data_vec,vector_list,data_cur):
    '''
    计算检测向量和法向量夹角
    :return:
    '''
    angle_recolist = []

    for i in range(len(vector_list)):
        a = np.array(vector_list[i])
        b = np.array(data_vec[int(data_cur[i])])


        a_norm = np.sqrt(np.sum(a * a))
        b_norm = np.sqrt(np.sum(b * b))
        cos_value = np.dot(a, b) / (a_norm * b_norm)
        arc_value = np.arccos(cos_value)
        angle_value = arc_value * 180 / np.pi
        angle_recolist.append(angle_value)


    return angle_recolist

def draw(pcd,data_cur,angle_recolist):
    '''
    描述凹凸点
    :return:
    '''
    pcd.paint_uniform_color([0.5, 0.5, 0.5])
    for i in range(len(data_cur)):
        # 凸起
        if angle_recolist[i] > 90:
            np.asarray(pcd.colors)[int(data_cur[i])] = [1, 0, 0]
        # 凹陷
        else:
            np.asarray(pcd.colors)[int(data_cur[i])] = [0, 0, 1]

    o3d.visualization.draw_geometries([pcd], point_show_normal=True, window_name="朝向相机位置",
                                      width=1024, height=768,
                                      left=50, top=50,
                                      mesh_show_back_face=False)
    return None

def draw_plt(pcd,data_cur,angle_recolist):


    fig1 = plt.figure()
    ax1 = fig1.add_subplot(111, projection='3d')
    ax1.set_xlabel("x")
    ax1.set_ylabel("y")
    ax1.set_zlabel("z")
    x_global = []
    y_global = []
    z_global = []
    for i in range(len(pcd.points)):
        [x,y,z] = np.asarray(pcd.points[i])
        x_global.append(x)
        y_global.append(y)
        z_global.append(z)


    x_u = []
    y_u = []
    z_u = []
    x_n = []
    y_n = []
    z_n = []
    for i in range(len(data_cur)):
        # 凸起
        if angle_recolist[i] > 90:
            x_n.append(np.asarray(pcd.points)[int(data_cur[i])][0])
            y_n.append(np.asarray(pcd.points)[int(data_cur[i])][1])
            z_n.append(np.asarray(pcd.points)[int(data_cur[i])][2])

        # 凹陷
        else:
            x_u.append(np.asarray(pcd.points)[int(data_cur[i])][0])
            y_u.append(np.asarray(pcd.points)[int(data_cur[i])][1])
            z_u.append(np.asarray(pcd.points)[int(data_cur[i])][2])

    ax1.scatter(x_global, y_global, z_global, c='y', marker='.')
    ax1.scatter(x_u, y_u, z_u, c='b', marker='*')
    ax1.scatter(x_n, y_n, z_n, c='r', marker='*')

    plt.show()

    return None


def read_txt1(file):   
    '''
    读一列
    :return:
    '''
    un1 = []

    for line in file.readlines():
        line = line.strip('\n')
        data = [float(i) for i in line.split()]
        un1.append(data[0])

    return un1

def read_txt3(file):     
    data_vec = []
    for line in file.readlines():
        line = line.strip('\n')
        data_vec.append([float(i) for i in line.split()])


    return data_vec



if __name__ == '__main__':
    pcd = o3d.io.read_point_cloud("D:/db/bunny_pcd.pcd")
    data_cur = read_txt1(open("D:/db/txt/data_cur.txt", "r"))
    data_vec = read_txt3(open("D:/db/txt/data_vec.txt", "r"))
    vector_list = project(data_cur, pcd)
    angle_recolist = vectorial_angle(data_vec, vector_list,data_cur)
    draw(pcd, data_cur, angle_recolist)

 图中可以看到,兔子脖子和眼睛部分的凹陷已经基本表现,一些大范围的凹陷区域可能存在由于数据不均匀出现误判。用同样的方法对另一个密集的点云作处理,我们发现虽然凹陷与凸起的区域任然有少量的交错,但是还是在可以去噪处理的范围内。

参考文献

【1】点云侠.Open3D 计算点云的表面曲率.

  • 5
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

三尺流流

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

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

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

打赏作者

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

抵扣说明:

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

余额充值