Open3D 点云区域生长分割算法

news2024/11/15 20:10:22

目录

一、基本原理

二、代码实现

三、实现效果

3.1原始点云

3.2分割后点云


前期试读,后续会将博客加入该专栏,欢迎订阅
Open3D与点云深度学习的应用_白葵新的博客-CSDN博客

一、基本原理

        Open3D 的点云区域生长分割算法是一种基于区域生长的点云分割方法,用于将点云分割成不同的部分。

该算法的基本原理如下:
1.初始种子点选择:

  • 从点云中随机选择一个或多个种子点,或者按照一定的策略(如曲率最低点)选择种子点。

2.区域生长:

  • 从种子点开始,检查其邻近点,判断这些邻近点是否满足生长条件。如果满足,则将这些邻近点添加到当前区域,并继续从这些新添加的点进行生长。重复这个过程,直到没有新的点可以添加到当前区域。

3.生长条件:

  • 距离条件:邻近点与种子点之间的欧氏距离小于预定义的阈值。
  • 角度条件:邻近点的法向量与种子点的法向量之间的角度小于预定义的阈值。
  • 曲率条件:邻近点的曲率与种子点的曲率之差小于预定义的阈值。

4.区域分割:

  • 将点云中所有满足生长条件的点标记为一个区域。
  • 从剩余未标记的点中选择新的种子点,重复上述步骤,直到所有点都被分割到不同的区域中。

二、代码实现


import open3d as o3d
import numpy as np
from collections import deque


class RegionGrowing:

    # 构造函数
    def __init__(self, cloud,
                 min_pts_per_cluster=1,             # 每个聚类的最小点数
                 max_pts_per_cluster=np.inf,        # 每个聚类的最大点数
                 theta_threshold=30,                # 法向量夹角阈值
                 curvature_threshold=0.05,          # 曲率阈值
                 neighbour_number=30,               # 邻域搜索点数
                 point_neighbours=[],               # 近邻点集合
                 point_labels=[],                   # 点标签
                 num_pts_in_segment=[],             # 分类标签
                 clusters=[],                       # 聚类容器
                 number_of_segments=0):             # 聚类个数

        self.cure = None                                 # 存储每个点曲率的容器
        self.pcd = cloud                                 # 输入点云
        self.min_pts_per_cluster = min_pts_per_cluster
        self.max_pts_per_cluster = max_pts_per_cluster
        self.theta_threshold = np.deg2rad(theta_threshold)
        self.curvature_threshold = curvature_threshold
        self.neighbour_number = neighbour_number
        self.point_neighbours = point_neighbours
        self.point_labels = point_labels
        self.num_pts_in_segment = num_pts_in_segment
        self.clusters = clusters
        self.number_of_segments = number_of_segments

    # -------------------------------------参数准备--------------------------------------
    def prepare_for_segment(self):
        points = np.asarray(self.pcd.points)     # 点坐标
        normals = np.asarray(self.pcd.normals)   # 法向量
        # 判断点云是否为空
        if not points.shape[0]:
            return False
        # 判断是否有近邻点
        if self.neighbour_number == 0:
            return False
        # 点云需要包含法向量信息
        if points.shape[0] != normals.shape[0]:
            self.pcd.estimate_normals(o3d.geometry.KDTreeSearchParamKNN(self.neighbour_number))

        return True

    # ------------------------------------近邻点搜索-------------------------------------
    def find_neighbour_points(self):
        number = len(self.pcd.points)
        kdtree = o3d.geometry.KDTreeFlann(self.pcd)
        self.point_neighbours = np.zeros((number, self.neighbour_number))
        for ik in range(number):
            [_, idx, _] = kdtree.search_knn_vector_3d(self.pcd.points[ik], self.neighbour_number)  # K近邻搜索
            self.point_neighbours[ik, :] = idx

    # -----------------------------------判意点所属分类-----------------------------------
    def validate_points(self, point, nebor):
        is_seed = True
        cosine_threshold = np.cos(self.theta_threshold)  # 法向量夹角(平滑)阈值

        curr_seed_normal = self.pcd.normals[point]       # 当前种子点的法向量
        seed_nebor_normal = self.pcd.normals[nebor]      # 种子点邻域点的法向量
        dot_normal = np.fabs(np.dot(seed_nebor_normal, curr_seed_normal))
        # 如果小于平滑阈值
        if dot_normal < cosine_threshold:
            return False, is_seed
        # 如果小于曲率阈值
        if self.cure[nebor] > self.curvature_threshold:
            is_seed = False

        return True, is_seed

    # ----------------------------------对点附上分类标签----------------------------------
    def label_for_points(self, initial_seed, segment_number):
        seeds = deque([initial_seed])
        self.point_labels[initial_seed] = segment_number
        num_pts_in_segment = 1

        while len(seeds):
            curr_seed = seeds[0]
            seeds.popleft()
            i_nebor = 0
            while i_nebor < self.neighbour_number and i_nebor < len(self.point_neighbours[curr_seed]):
                index = int(self.point_neighbours[curr_seed, i_nebor])
                if self.point_labels[index] != -1:
                    i_nebor += 1
                    continue

                belongs_to_segment, is_seed = self.validate_points(curr_seed, index)
                if not belongs_to_segment:
                    i_nebor += 1
                    continue

                self.point_labels[index] = segment_number
                num_pts_in_segment += 1

                if is_seed:
                    seeds.append(index)

                i_nebor += 1

        return num_pts_in_segment

    # ------------------------------------区域生长过程------------------------------------
    def region_growing_process(self):
        num_of_pts = len(self.pcd.points)         # 点云点的个数
        self.point_labels = -np.ones(num_of_pts)  # 初始化点标签
        self.pcd.estimate_covariances(o3d.geometry.KDTreeSearchParamKNN(self.neighbour_number))
        cov_mat = self.pcd.covariances            # 获取每个点的协方差矩阵
        self.cure = np.zeros(num_of_pts)          # 初始化存储每个点曲率的容器
        # 计算每个点的曲率
        for i_n in range(num_of_pts):
            eignvalue, _ = np.linalg.eig(cov_mat[i_n])  # SVD分解求特征值
            idx = eignvalue.argsort()[::-1]
            eignvalue = eignvalue[idx]
            self.cure[i_n] = eignvalue[2] / (eignvalue[0] + eignvalue[1] + eignvalue[2])

        point_curvature_index = np.zeros((num_of_pts, 2))
        for i_cu in range(num_of_pts):
            point_curvature_index[i_cu, 0] = self.cure[i_cu]
            point_curvature_index[i_cu, 1] = i_cu

        # 按照曲率大小进行排序
        temp_cure = np.argsort(point_curvature_index[:, 0])
        point_curvature_index = point_curvature_index[temp_cure, :]

        seed_counter = 0
        seed = int(point_curvature_index[seed_counter, 1])  # 选取曲率最小值点

        segmented_pts_num = 0
        number_of_segments = 0

        while segmented_pts_num < num_of_pts:
            pts_in_segment = self.label_for_points(seed, number_of_segments)  # 根据种子点进行分类
            segmented_pts_num += pts_in_segment
            self.num_pts_in_segment.append(pts_in_segment)
            number_of_segments += 1

            # 寻找下一个种子
            for i_seed in range(seed_counter + 1, num_of_pts):
                index = int(point_curvature_index[i_seed, 1])
                if self.point_labels[index] == -1:
                    seed = index
                    seed_counter = i_seed
                    break

    # ----------------------------------根据标签进行分类-----------------------------------
    def region_growing_clusters(self):
        number_of_segments = len(self.num_pts_in_segment)
        number_of_points = np.asarray(self.pcd.points).shape[0]

        # 初始化聚类数组
        for i in range(number_of_segments):
            tmp_init = list(np.zeros(self.num_pts_in_segment[i]))
            self.clusters.append(tmp_init)

        counter = list(np.zeros(number_of_segments))
        for i_point in range(number_of_points):
            segment_index = int(self.point_labels[i_point])
            if segment_index != -1:
                point_index = int(counter[segment_index])
                self.clusters[segment_index][point_index] = i_point
                counter[segment_index] = point_index + 1

        self.number_of_segments = number_of_segments

    # ----------------------------------执行区域生长算法-----------------------------------
    def extract(self):
        if not self.prepare_for_segment():
            print("区域生长算法预处理失败!")
            return

        self.find_neighbour_points()
        self.region_growing_process()
        self.region_growing_clusters()

        # 根据设置的最大最小点数筛选符合阈值的分类
        all_cluster = []
        for i in range(len(self.clusters)):
            if self.min_pts_per_cluster <= len(self.clusters[i]) <= self.max_pts_per_cluster:
                all_cluster.append(self.clusters[i])
            else:
                self.point_labels[self.clusters[i]] = -1

        self.clusters = all_cluster
        return all_cluster



# ------------------------------读取点云---------------------------------------
pcd = o3d.io.read_point_cloud("walls.pcd")
o3d.visualization.draw_geometries([pcd], window_name="原始点云",
                                  width=1024, height=768,
                                  left=50, top=50,
                                  mesh_show_back_face=False)
# ------------------------------区域生长---------------------------------------
rg = RegionGrowing(pcd,
                       min_pts_per_cluster=50,     # 每个聚类的最小点数
                       max_pts_per_cluster=1000000,  # 每个聚类的最大点数
                       neighbour_number=30,         # 邻域搜索点数
                       theta_threshold=30,          # 平滑阈值(角度制)
                       curvature_threshold=0.005)    # 曲率阈值
# ---------------------------聚类结果分类保存----------------------------------
indices = rg.extract()
print("聚类个数为", len(indices))
segment = []  # 存储分割结果的容器
for i in range(len(indices)):
    ind = indices[i]
    clusters_cloud = pcd.select_by_index(ind)
    r_color = np.random.uniform(0, 1, (1, 3))  # 分类点云随机赋色
    clusters_cloud.paint_uniform_color([r_color[:, 0], r_color[:, 1], r_color[:, 2]])
    segment.append(clusters_cloud)
    # 保存到本地文件夹
    # file_name = "Region_growing_cluster" + str(i + 1) + ".pcd"
    # o3d.io.write_point_cloud(file_name, clusters_cloud)
# -----------------------------结果可视化------------------------------------
o3d.visualization.draw_geometries(segment, window_name="区域生长分割",
                                  width=1024, height=768,
                                  left=50, top=50,
                                  mesh_show_back_face=False)

三、实现效果

3.1原始点云

3.2分割后点云

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1928630.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot实战:密码处理

Controller层 Operation(summary "保存或更新后台用户信息") PostMapping("saveOrUpdate") public Result saveOrUpdate(RequestBody SystemUser systemUser) {if(systemUser.getPassword() ! null){systemUser.setPassword(DigestUtils.md5Hex(systemUs…

单链表的介绍和实现

前言 Hello,小伙伴们&#xff0c;你们的作者君又回来了&#xff0c;今天我将带领大家继续学习另一种线性表&#xff1a;单链表&#xff0c; 准备好的小伙伴三连打卡上车&#xff0c;你们的支持就是我更新的动力&#xff0c;一定不要吝啬手中的三连哟&#xff0c;万分感谢&…

微服务实战系列之玩转Docker(一)

前言 话说计算机的“小型化”发展&#xff0c;历经了大型机、中型机直至微型机&#xff0c;贯穿了整个20世纪的下半叶。同样&#xff0c;伴随着计算机的各个发展阶段&#xff0c;如何做到“资源共享、资源节约”&#xff0c;也一直是一代又一代计算机人的不懈追求和历史使命。今…

cleanshot Mac 上的截图工具

笔者闲来无事&#xff0c;最近在找一些mac上好用的工具其中一款就是cleanShot。为什么不用原有的mac自带的呢。因为相对来说编辑功能不算全面&#xff0c;不支持长截图。那有没有一款软件支持关于截图的好用工具呢。 所以笔者找了这款。安装包是直接安装就可使用的。请大家点赞…

校验el-table中表单项

需求&#xff1a; 表格中每一行都有几个必填项&#xff0c;如用户提交时有未填的选项&#xff0c;将该选项标红且给出提示&#xff0c;类似el-form 的那种校验 el-table本身并没有校验的方法&#xff0c;而且每一行的输入框也是通过插槽来实现的&#xff0c;因此我们要自己跟…

VUE前端HTML静默打印(不弹出打印对话框)PDF简单方案

前言 在做打印功能的时候&#xff0c;以前大部分客户端都是用C#做的&#xff0c;静默打印&#xff08;也就是不弹出打印对话框&#xff09;比较简单。 但是使用浏览器作为客户端&#xff0c;静默打印&#xff08;也就是不弹出打印对话框&#xff09;做起来就比较困难。困难的…

LLM-阿里 DashVector + langchain self-querying retriever 优化 RAG 实践【Query 优化】

文章目录 前言self querying 简介代码实现总结 前言 现在比较流行的 RAG 检索就是通过大模型 embedding 算法将数据嵌入向量数据库中&#xff0c;然后在将用户的查询向量化&#xff0c;从向量数据库中召回相似性数据&#xff0c;构造成 context template, 放到 LLM 中进行查询…

css - - - - - 去除图片默认的白色背景(混合模式 mix-blend-mode)

去除图片默认的白色背景&#xff08;mix-blend-mode&#xff09; 1. 需求描述2. 原图展示3. 原代码展示4. 使用混合模式(mix-blend-mode)5.修改后效果 1. 需求描述 图片含有白色地图&#xff0c;想要将其去掉 2. 原图展示 3. 原代码展示 <div><img src*****/> &…

基于高德地图实现Android定位功能实现(二)

基于高德地图实现Android定位功能实现&#xff08;二&#xff09; 在实现的高德地图的基本显示后&#xff0c;我们需要不断完善地图的功能 地图界面设计&#xff08;悬浮按钮等&#xff09; 首先就是地图页面的布局&#xff0c;这个根据大家的实际需求进行设计即可&#xff…

无人机图像目标检测

本仓库是人工智能课程的课程作业仓库&#xff0c;主要是完成无人机图像目标检测的任务&#xff0c;我们对visdrone数据集进行了处理&#xff0c;在yolo和ssd两种框架下进行了训练和测试&#xff0c;并编写demo用于实时的无人机图像目标检测。 requirements依赖&#xff1a; ss…

数据结构之‘串’

目录 一. 串的定义二. 串的基本操作三. 串的存储结构3.1 顺序存储3.2 链式存储3.3 基于顺序存储的基本操作 \quad 一. 串的定义 \quad \quad \quad \quad 二. 串的基本操作 \quad \quad 三. 串的存储结构 \quad \quad 3.1 顺序存储 \quad 得一个一个遍历 结合方案一和方案二的优…

[React 进阶系列] useSyncExternalStore hook

[React 进阶系列] useSyncExternalStore hook 前情提要&#xff0c;包括 yup 的实现在这里&#xff1a;yup 基础使用以及 jest 测试 简单的提一下&#xff0c;需要实现的功能是&#xff1a; yup schema 需要访问外部的 storage外部的 storage 是可变的React 内部也需要访问同…

SpringBoot整合阿里云RocketMQ对接,商业版

1.需要阿里云开通商业版RocketMQ 普通消息新建普通主题,普通组,延迟消息新建延迟消息主题,延迟消息组 2.结构目录 3.引入依赖 <!--阿里云RocketMq整合--><dependency><groupId>com.aliyun.openservices</groupId><artifactId>ons-client</…

【C language】扫雷

目录 1.概要2.实现核心思想3.实现过程3.1游戏框架3.2游戏逻辑初始化棋盘 MineInit打印棋盘 MinePrint设置雷 SetMines扫雷 ClearMines 4.总结 1.概要 为了提高C初学者对C语言基本语法的掌控能力&#xff0c;一个极简版的扫雷游戏是十分适合锻炼代码能力的。下面分享仅用数组、…

14_Shell重定向输入输出

14_Shell重定向输入输出 输出重定向&#xff1a;一般情况&#xff0c;输出是在终端直接显示&#xff0c;改变输出位置&#xff0c;改变到文件中&#xff0c;这就是输出重定向 输入重定向&#xff1a;一般情况&#xff0c;输入是读取用户终端输入&#xff0c;改变输入位置&#…

026-GeoGebra中级篇-曲线(2)_极坐标曲线、参数化曲面、分段函数曲线、分形曲线、复数平面上的曲线、随机曲线、非线性动力系统的轨迹

除了参数曲线、隐式曲线和显式曲线之外&#xff0c;还有其他类型的曲线表示方法。本篇主要概述一下极坐标曲线、参数化曲面、分段函数曲线、分形曲线、复数平面上的曲线、随机曲线、和非线性动力系统的轨迹&#xff0c;可能没有那么深&#xff0c;可以先了解下。 目录 1. 极坐…

VScode:前端项目中yarn包的安装和使用

一、首先打开PowerShell-管理员身份运行ISE 输入命令&#xff1a; set-ExecutionPolicy RemoteSigned 选择“全是”&#xff0c;表示允许在本地计算机上运行由本地用户创建的脚本&#xff0c;没有报错就行了 二、接着打开VScode集成终端 输入 npm install -g yarn 再次输入以下…

IoT数据采集网关在企业应用中扮演的角色-天拓四方

随着物联网&#xff08;IoT&#xff09;技术的不断发展&#xff0c;越来越多的企业开始利用IoT技术实现智能化、自动化的生产和管理。在这个过程中&#xff0c;Iot数据采集网关作为连接物理世界与数字世界的桥梁&#xff0c;发挥着至关重要的作用。 IoT数据采集网关是一种硬件…

4.定时器

原理 时钟源&#xff1a;定时器是内部时钟源&#xff08;晶振&#xff09;&#xff0c;计数器是外部计时长度&#xff1a;对应TH TL计数器初值寄存器(高八位,低八位)对应的中断触发函数 中断源中断处理函数Timer0Timer0_Routine(void) interrupt 1Timer1Timer1_Routine(void) …

c++初阶知识——类和对象(中)

目录 1.类的默认成员函数 2.构造函数 3.析构函数 4.拷贝构造函数 5.运算符重载 5.1 赋值运算符重载 5.2 使用运算符重载等特性实现日期类 6.取地址运算符重载 6.1 const成员函数 6.2 取地址运算符重载 1.类的默认成员函数 默认成员函数就是⽤⼾没有显式实现&#…