首页 > 其他分享 >[Open3d系列]--点云平面提取

[Open3d系列]--点云平面提取

时间:2024-05-07 17:45:08浏览次数:26  
标签:-- plane current Open3d planes 点云 平面 o3d pcd

Open3d: 点云平面拟合

因为项目需要分析点云数据, 此文总结其中拟合平面经验。

加载点云

import open3d as o3d
plypath = "/xxx/xxx.ply"
pcd = o3d.io.read_point_cloud(plypath)
o3d.visualization.draw_geometries([pcd])

效果如下:
原始点云

平面拟合

plane_model, inliers = current_pcd.segment_plane(distance_threshold=0.3, ransac_n=5, num_iterations=1000)

其中三个参数:

  • distance_threshold: 用于定义点到平面的最大距离阈值,单位为米。如果点到平面的距离小于此阈值,则认为该点属于平面。默认值为0.01
  • ransac_n: 用于RANSAC算法的迭代次数。RANSAC是一种随机采样一致性算法,用于估计平面模型参数。默认值为3
  • num_iterations: 在RANSAC算法中,每次迭代时使用的随机样本点的数量。默认值为10000

返回值:

  • inliers: 拟合得到的点云索引
  • plane_model: 平面模型\(Ax + By + Cz + D = 0\),所以该返回值即为(A,B,C,D)元组。 (A,B,C)平面法向量, D为平面上一点到原点的有向距离
    如已知法向量(0,0,2), D=-1, 则其平面如下图所示
    plane model
    可见, 平面离原点的距离为0.5, 因为其法向量非单位向量, 对法向量除以2, D除以2,则得到平面到原点的有向距离。

拟合多个平面

上述点云中, 明显存在5个平面, 分别对应4面墙与地板。 而调用segment_plane仅仅得到一个平面的点云。想要达到每个平面拟合得到, 则需要分为几步:

  1. 拟合一个平面
  2. 将该平面在原始点云中删除, 得到新的点云b
  3. 对b进行上述1、2步骤, 直到无法拟合处平面为止

代码示例:

def plane_detection(pcd, tolerance = 50):

    current_pcd = pcd
    planes = []
    while len(current_pcd.points) > tolerance:
        # 1. 拟合点云
        plane_model, inliers = current_pcd.segment_plane(distance_threshold=0.3, ransac_n=5, num_iterations=1000)
        # 如果不构成平面的必要条件, 则退出
        if len(inliers) < tolerance:
            break

        inlier_indices = np.asarray(inliers)
        plane_pcd = current_pcd.select_by_index(inlier_indices)
        # 2. 删除已拟合的平面,得到新的点云
        current_pcd = current_pcd.select_by_index(inlier_indices, invert=True)

        planes.append(plane_pcd)
    
    return planes,current_pcd

绘制平面法向量

已知平面模型plane_model, 求法向量两点代码示例如下:

# 生成法向量线段的端点坐标
normal_vector = plane_model[:3]
# 求平面上的一点
point_in_plane = -normal_vector * plane_model[3] / np.linalg.norm(normal_vector)**2 

# 沿着法向量方向上的另一点
endpoint = point_in_plane + normal_vector * 2 

# 创建法向量线段
line = o3d.geometry.LineSet()
line.points = o3d.utility.Vector3dVector([point_in_plane, endpoint])
line.lines = o3d.utility.Vector2iVector([[0, 1]])

绘制坐标轴

coordinate = o3d.geometry.TriangleMesh.create_coordinate_frame(size=5.0, origin=[0,0,0])

最终效果

平面拟合

完整实例

import open3d as o3d
import numpy as np
import random


def plane_detection(pcd, tolerance = 50):

    current_pcd = pcd
    planes = []
    while len(current_pcd.points) > tolerance:
        plane_model, inliers = current_pcd.segment_plane(distance_threshold=0.3, ransac_n=5, num_iterations=1000)
        if len(inliers) < tolerance:
            break
        inlier_indices = np.asarray(inliers)
        inlier_cloud = current_pcd.select_by_index(inlier_indices)
        current_pcd = current_pcd.select_by_index(inlier_indices, invert=True)
        
        normal_vector = plane_model[:3]
        point_in_plane = -normal_vector * plane_model[3] / np.linalg.norm(normal_vector)**2 
        endpoint = point_in_plane + normal_vector * 2 

        line = o3d.geometry.LineSet()
        line.points = o3d.utility.Vector3dVector([point_in_plane, endpoint])
        line.lines = o3d.utility.Vector2iVector([[0, 1]])

        planes.append(line)
        planes.append(inlier_cloud)
    
    return current_pcd, planes
    


def main(plypath):
    pcd = o3d.io.read_point_cloud(plypath)

    remain_pcd, planes = plane_detection(pcd)
    for plane in planes:
        plane.paint_uniform_color(np.random.rand(3))
    
    mesh_frame = o3d.geometry.TriangleMesh.create_coordinate_frame(size=5.0, origin=[0,0,0])
    # 可视化结果
    o3d.visualization.draw_geometries([ remain_pcd, *planes,mesh_frame])
    
    
main(r"xxx.ply")

标签:--,plane,current,Open3d,planes,点云,平面,o3d,pcd
From: https://www.cnblogs.com/quenwaz/p/18178036

相关文章

  • java容器化项目改造
    dockerjava项目容器化改造前后端分离项目前端https://gitee.com/yuco/eladmin-web.git后端https://gitee.com/yuco/eladmin.git要素:vuenpmspringbootmysqlredisjava后端容器化思路:了解在物理机虚拟机的部署流程,然后编写dockerfile进行容器化部署。java项目,使用mv......
  • 程序员修炼之道阅读笔记1
    最近读了《程序员修炼之道:从小工到专家》这本书,受益匪浅,同时也让我发现了自己的一些问题。目前读到的章节中,最让我受益的就是温水煮青蛙的例子。这个例子告诉我要持续不断地观察周围发生的事情,而不仅仅是自己在做的事。尤其是项目的管理者必须要实时的掌控项目的方向,因为大多数项......
  • if __name__ == '__main__' 是什么意思
    __name__是python中的内置变量,表示当前模块/方法/函数的名称当.py文件被直接运行时,ifname=='main'之下的代码块将被运行;当.py文件以模块形式被导入时,ifname=='main'之下的代码块不被运行。如:定义const.pyPI=3.14defmain():print("PI:",PI)if__name__=='......
  • 2024.4.23
    继续之前任务@keyframescuIcon-spin{ 0%{ -webkit-transform:rotate(0); transform:rotate(0); } 100%{ -webkit-transform:rotate(359deg); transform:rotate(359deg); }}.cuIconfont-spin{ -webkit-animation:cuIcon-spin2sinfinitelinear; animation:cuIc......
  • Hutool 只复制不为空的属性
    在使用Hutool的工具类进行复制时,有时候需要只复制不为空的属性。比如在修改时,先把数据库中的数据查出来:SysAreasysArea=getById(dto.getId());此时再以传入值复制到数据库表对象中,但如果传入值中有属性为null,会将数据库表对象的属性也设为null,修改时这些属性就会被清空,......
  • 2024.4.25
    radio.radio[checked]::after,radio.radio.uni-radio-input-checked::after{ content:""; background-color:transparent; display:block; position:absolute; width:8px; height:8px; z-index:999; top:0upx; left:0upx; right:0; bottom:0; margin:......
  • 构建之法读后感6
    《构建之法》这本书深入探讨了软件架构设计的重要性和方法。通过阅读这本书,我对软件架构设计有了更深入的理解,并从中获得了以下几点启示:首先,作者强调了分层架构的重要性。分层架构能够将系统划分为不同的层次,如数据层、业务逻辑层和表示层,使系统更加模块化和易于维护。同时,分层架......
  • 个人分工06——团队冲刺
    个人分工06——团队冲刺今日任务:前端优化开发代码import{createRouter,createWebHistory}from'vue-router'//导入组件importLoginVuefrom'@/views/Login.vue'importLayoutVuefrom'@/views/Layout.vue'importArticleCategoryVuefrom'@/views......
  • 2024.4.24
    /*==================初始化====================*/body{ background-color:#f1f1f1; font-size:28upx; color:#333333; font-family:HelveticaNeue,Helvetica,sans-serif;}view,scroll-view,swiper,button,input,textarea,label,navigator,image{ box-sizi......
  • bat
    1、BAT脚本编写echo、@、call、pause、rem(小技巧:用::代替rem)是批处理文件最常用的几个命令,我们就从他们开始学起。echo表示显示此命令后的字符echooff表示在此语句后所有运行的命令都不显示命令行本身@与echooff相象,但它是加在每个命令行的最前面,表示运行时不显示这一行......