一、引言
点云数据在三维空间信息表示中具有重要地位,广泛应用于计算机视觉、机器人导航、地理信息系统等众多领域。然而,原始点云数据往往具有大规模、无序性等特点,这给数据的存储、处理和分析带来了诸多挑战。八叉树结构作为一种有效的空间划分和数据组织方式,能够将三维空间递归地划分为八个子空间,从而对复杂的三维数据进行层次化表示和高效管理。本文将深入探讨如何将点云数据集合转换为八叉树结构,并详细介绍转换后的八叉树在不同场景下的应用,同时给出 C# 和 Python 的实现代码,旨在帮助读者全面理解和掌握这一关键技术及其实际应用。
二、八叉树基本原理
(一)空间划分概念
八叉树是一种基于树状数据结构的空间划分方法,它将三维空间递归地细分为八个大小相等的子立方体(八叉)。根节点表示整个三维空间,然后根据一定的规则将其划分为八个子节点,每个子节点对应一个子立方体。这个过程可以持续递归进行,直到满足特定的终止条件,例如子立方体中的点数量小于某个阈值,或者达到预定的树的深度。
例如,考虑一个简单的三维场景,包含一个长方体形状的物体。八叉树的根节点代表整个包含该长方体的空间范围。在第一次划分时,根节点被划分为八个子立方体,其中只有部分子立方体包含长方体的点云数据。对于包含点云数据的子立方体,可以进一步细分,直到达到所需的精度或满足其他停止标准。这种层次化的划分方式使得八叉树能够有效地组织和表示三维空间中的数据,减少数据存储和处理的复杂度。
(二)节点属性与数据存储
在八叉树结构中,每个节点具有一些关键属性。除了表示其对应的空间范围(如最小坐标和最大坐标界定的立方体)外,节点还可能包含指向其子节点的引用(如果有子节点),以及与该节点相关的点云数据信息。
对于叶节点(即没有子节点的节点),通常会存储属于该节点所代表空间范围内的点云数据点。这些点可以以不同的方式存储,例如直接存储点的坐标列表,或者采用一些更紧凑的数据结构来减少内存占用。而内部节点主要用于组织空间划分,通过指向子节点的引用构建起整个八叉树的层次结构。
在实际应用中,根据具体需求和场景,可以灵活设计节点的数据结构。例如,可以在节点中添加额外的信息,如该节点内点云数据的统计信息(如均值、方差等),以便在后续的处理中能够更快速地进行一些分析和决策。
三、点云数据转换为八叉树的算法流程
(一)确定空间范围
首先,需要确定点云数据所占据的三维空间范围。遍历点云数据中的所有点,找到其在x 、y、z三个维度上的最小值和最大值,从而确定一个能够包含所有点云数据的最小立方体空间。这一步骤为后续的空间划分提供了基础的边界信息。
例如,对于一个包含n个点的点云数据集合P={p1,p2,...,pn},其中pi={xi,yi,zi}。计算,同理可得 。则整个点云数据的空间范围可以表示为。
(二)递归划分空间
从根节点开始,将确定的三维空间范围划分为八个子空间,对应八个子节点。对于每个子空间,判断点云数据中有哪些点落在该子空间内。如果子空间内的点数量超过设定的阈值,或者还未达到预定的树的深度,则继续对该子空间进行递归划分,重复上述过程。
在划分过程中,可以根据点的坐标与子空间边界的关系来确定点所属的子空间。例如,对于一个子空间范围,一个点,如果(其中),则点p在x维度上可能属于前四个子空间之一,再结合y和z维度的判断,可以确定其具体所属的子空间。
(三)分配点到节点
在划分空间的同时,将点云数据中的点分配到相应的叶节点中。当一个子空间不再进行划分时,该子空间对应的叶节点就存储落在其范围内的点云数据点。这样,通过不断地划分和分配,最终构建起完整的八叉树结构,将原始无序的点云数据组织成层次化的空间数据结构。
四、C# 实现
(一)数据结构定义
在 C# 中,使用 OpenTK
库中的 List<Vector3>
来存储点云数据。首先定义八叉树节点的数据结构:
using OpenTK;
using System.Collections.Generic;
class OctreeNode
{
// 节点对应的空间范围
public Vector3 Min { get; set; }
public Vector3 Max { get; set; }
// 子节点列表
public List<OctreeNode> Children { get; set; }
// 存储在该节点的点云数据点(叶节点)
public List<Vector3> Points { get; set; }
public OctreeNode(Vector3 min, Vector3 max)
{
Min = min;
Max = max;
Children = new List<OctreeNode>();
Points = new List<Vector3>();
}
}
(二)八叉树构建函数
接下来实现构建八叉树的函数:
class Octree
{
private OctreeNode root;
private int maxPointsPerNode;
private int maxDepth;
public Octree(List<Vector3> pointCloud, int maxPoints = 10, int depth = 10)
{
// 确定点云空间范围
Vector3 min = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue);
Vector3 max = new Vector3(float.MinValue, float.MinValue, float.MinValue);
foreach (var point in pointCloud)
{
min = Vector3.ComponentMin(min, point);
max = Vector3.ComponentMax(max, point);
}
// 创建根节点
root = new OctreeNode(min, max);
// 设置最大节点点数和最大深度
maxPointsPerNode = maxPoints;
maxDepth = depth;
// 开始构建八叉树
BuildOctree(root, pointCloud, 0);
}
private void BuildOctree(OctreeNode node, List<Vector3> points, int depth)
{
if (depth >= maxDepth || points.Count <= maxPointsPerNode)
{
// 将点分配到叶节点
node.Points.AddRange(points);
return;
}
// 划分空间并创建子节点
List<List<Vector3>> subPointLists = new List<List<Vector3>>();
for (int i = 0; i < 8; i++)
{
subPointLists.Add(new List<Vector3>());
}
Vector3 center = (node.Min + node.Max) / 2.0f;
foreach (var point in points)
{
int index = GetOctantIndex(node, point, center);
subPointLists[index].Add(point);
}
// 创建子节点并递归构建八叉树
for (int i = 0; i < 8; i++)
{
if (subPointLists[i].Count > 0)
{
Vector3 subMin = GetSubMin(node.Min, node.Max, i);
Vector3 subMax = GetSubMax(node.Min, node.Max, i);
OctreeNode childNode = new OctreeNode(subMin, subMax);
node.Children.Add(childNode);
BuildOctree(childNode, subPointLists[i], depth + 1);
}
}
}
private int GetOctantIndex(OctreeNode node, Vector3 point, Vector3 center)
{
int index = 0;
if (point.X >= center.X) index |= 4;
if (point.Y >= center.Y) index |= 2;
if (point.Z >= center.Z) index |= 1;
return index;
}
private Vector3 GetSubMin(Vector3 min, Vector3 max, int octant)
{
Vector3 center = (min + max) / 2.0f;
Vector3 subMin = min;
if ((octant & 4) == 0) subMin.X = center.X;
if ((octant & 2) == 0) subMin.Y = center.Y;
if ((octant & 1) == 0) subMin.Z = center.Z;
return subMin;
}
private Vector3 GetSubMax(Vector3 min, Vector3 max, int octant)
{
Vector3 center = (min + max) / 2.0f;
Vector3 subMax = max;
if ((octant & 4)!= 0) subMax.X = center.X;
if ((octant & 2)!= 0) subMax.Y = center.Y;
if ((octant & 1)!= 0) subMax.Z = center.Z;
return subMax;
}
}
上述代码首先确定点云数据的空间范围并创建根节点,然后通过 BuildOctree
函数递归地构建八叉树。在构建过程中,根据点的位置将其分配到相应的子空间列表中,并为非空的子空间创建子节点,继续递归构建。GetOctantIndex
函数用于确定点所属的八叉树子空间索引,GetSubMin
和 GetSubMax
函数用于计算子空间的最小和最大坐标。
(三)八叉树的应用示例:空间查询
八叉树构建完成后,可以进行各种应用操作,例如空间查询。下面是一个简单的在八叉树中查询给定空间范围内点的函数示例:
public List<Vector3> QueryPointsInRange(Vector3 queryMin, Vector3 queryMax)
{
List<Vector3> result = new List<Vector3>();
QueryPointsInRangeRecursive(root, queryMin, queryMax, result);
return result;
}
private void QueryPointsInRangeRecursive(OctreeNode node, Vector3 queryMin, Vector3 queryMax, List<Vector3> result)
{
// 检查节点空间范围与查询范围的关系
if (IsIntersecting(node.Min, node.Max, queryMin, queryMax))
{
if (node.Children.Count == 0)
{
// 叶节点,直接添加符合条件的点
foreach (var point in node.Points)
{
if (IsPointInRange(point, queryMin, queryMax))
{
result.Add(point);
}
}
}
else
{
// 内部节点,递归查询子节点
foreach (var child in node.Children)
{
QueryPointsInRangeRecursive(child, queryMin, queryMax, result);
}
}
}
}
private bool IsIntersecting(Vector3 boxMin1, Vector3 boxMax1, Vector3 boxMin2, Vector3 boxMax2)
{
return!(boxMax1.X < boxMin2.X || boxMin1.X > boxMax2.X ||
boxMax1.Y < boxMin2.Y || boxMin1.Y > boxMax2.Y ||
boxMax1.Z < boxMin2.Z || boxMin1.Z > boxMax2.Z);
}
private bool IsPointInRange(Vector3 point, Vector3 queryMin, Vector3 queryMax)
{
return queryMin.X <= point.X && point.X < queryMax.X &&
queryMin.Y <= point.Y && point.Y < queryMax.Y &&
queryMin.Z <= point.Z && point.Z < queryMax.Z;
}
这个空间查询函数接受一个查询空间范围,通过递归遍历八叉树,检查每个节点与查询范围的相交情况,对于叶节点,进一步判断其中的点是否在查询范围内,并将符合条件的点添加到结果列表中。
五、Python 实现
(一)数据结构定义
在 Python 中,我们可以使用自定义的类来表示八叉树节点和八叉树。首先定义八叉树节点类:
class OctreeNode:
def __init__(self, min_corner, max_corner):
# 节点对应的空间范围
self.min = min_corner
self.max = max_corner
# 子节点列表
self.children = []
# 存储在该节点的点云数据点(叶节点)
self.points = []
(二)八叉树构建函数
class Octree:
def __init__(self, pointCloud, maxPoints=10, maxDepth=10):
# 确定点云空间范围
x_min, y_min, z_min = float('inf'), float('inf'), float('inf')
x_max, y_max, z_max = float('-inf'), float('-inf'), float('-inf')
for point in pointCloud:
x, y, z = point
x_min = min(x_min, x)
x_max = max(x_max, x)
y_min = min(y_min, y)
y_max = max(y_max, y)
z_min = min(z_min, z)
z_max = max(z_max, z)
self.root = OctreeNode((x_min, y_min, z_min), (x_max, y_max, z_max))
self.maxPointsPerNode = maxPoints
self.maxDepth = maxDepth
# 开始构建八叉树
self.buildOctree(self.root, pointCloud, 0)
def buildOctree(self, node, points, depth):
if depth >= self.maxDepth or len(points) <= self.maxPointsPerNode:
# 将点分配到叶节点
node.points = points
return
# 划分空间并创建子节点
subPointLists = [[] for _ in range(8)]
center = ((node.min[0] + node.max[0]) / 2, (node.min[1] + node.max[1]) / 2, (node.min[2] + node.max[2]) / 2)
for point in points:
index = self.getOctantIndex(node, point, center)
subPointLists[index].append(point)
# 创建子节点并递归构建八叉树
for i in range(8):
if len(subPointLists[i]) > 0:
subMin = self.getSubMin(node.min, node.max, i)
subMax = self.getSubMax(node.min, node.max, i)
childNode = OctreeNode(subMin, subMax)
node.children.append(childNode)
self.buildOctree(childNode, subPointLists[i], depth + 1)
def getOctantIndex(self, node, point, center):
index = 0
if point[0] >= center[0]: index |= 4
if point[1] >= center[1]: index |= 2
if point[2] >= center[2]: index |= 1
return index
def getSubMin(self, min_corner, max_corner, octant):
center = ((min_corner[0] + max_corner[0]) / 2, (min_corner[1] + max_corner[1]) / 2, (min_corner[2] + max_corner[2]) / 2)
subMin = list(min_corner)
if (octant & 4) == 0: subMin[0] = center[0]
if (octant & 2) == 0: subMin[1] = center[1]
if (octant & 1) == 0: subMin[2] = center[2]
return tuple(subMin)
def getSubMax(self, min_corner, max_corner, octant):
center = ((min_corner[0] + max_corner[0]) / 2, (min_corner[1] + max_corner[1]) / 2, (min_corner[2] + max_corner[2]) / 2)
subMax = list(max_corner)
if (octant & 4)!= 0: subMax[0] = center[0]
if (octant & 2)!= 0: subMax[1] = center[1]
if (octant & 1)!= 0: subMax[2] = center[2]
return tuple(subMax)
(三)八叉树的应用示例:空间查询
同样,在 Python 实现中,构建完八叉树后可以进行空间查询操作。以下是一个简单的空间查询函数示例:
def queryPointsInRange(self, queryMin, queryMax):
result = []
self.queryPointsInRangeRecursive(self.root, queryMin, queryMax, result)
return result
def queryPointsInRangeRecursive(self, node, queryMin, queryMax, result):
# 检查节点空间范围与查询范围的关系
if self.isIntersecting(node.min, node.max, queryMin, queryMax):
if len(node.children) == 0:
# 叶节点,直接添加符合条件的点
for point in node.points:
if self.isPointInRange(point, queryMin, queryMax):
result.append(point)
else:
# 内部节点,递归查询子节点
for child in node.children:
self.queryPointsInRangeRecursive(child, queryMin, queryMax, result)
def isIntersecting(self, boxMin1, boxMax1, boxMin2, boxMax2):
return not (boxMax1[0] < boxMin2[0] or boxMin1[0] > boxMax2[0] or
boxMax1[1] < boxMin2[1] or boxMin1[1] > boxMax2[1] or
boxMax1[2] < boxMin2[2] or boxMin1[2] > boxMax2[2])
def isPointInRange(self, point, queryMin, queryMax):
return queryMin[0] <= point[0] < queryMax[0] and \
queryMin[1] <= point[1] < queryMax[1] and \
queryMin[2] <= point[2] < queryMax[2]
这个 queryPointsInRange
函数接受一个查询空间范围(由 queryMin
和 queryMax
界定),通过递归遍历八叉树来查找在该范围内的点。首先判断节点的空间范围与查询范围是否相交,如果相交且是叶节点,则检查其中的点是否在查询范围内并添加到结果列表;如果是内部节点,则递归地对其每个子节点进行相同的操作。
(四)八叉树的可视化
为了更好地理解八叉树对空间的划分和点云数据的组织情况,可以进行八叉树的可视化。这里可以使用一些 Python 的可视化库,如 matplotlib
结合 mayavi
库来实现简单的三维可视化。
首先,安装所需库:
pip install mayavi matplotlib
然后,编写可视化函数:
from mayavi import mlab
import matplotlib.pyplot as plt
def visualizeOctree(octree):
# 绘制八叉树节点的立方体
def draw_cube(min_corner, max_corner, color=(0.5, 0.5, 0.5), opacity=0.1):
x = [min_corner[0], max_corner[0], max_corner[0], min_corner[0], min_corner[0], max_corner[0], max_corner[0], min_corner[0]]
y = [min_corner[1], min_corner[1], max_corner[1], max_corner[1], min_corner[1], min_corner[1], max_corner[1], max_corner[1]]
z = [min_corner[2], min_corner[2], min_corner[2], min_corner[2], max_corner[2], max_corner[2], max_corner[2], max_corner[2]]
mlab.plot3d(x, y, z, color=color, tube_radius=None, opacity=opacity)
# 递归绘制八叉树
def drawOctreeRecursive(node):
draw_cube(node.min, node.max)
if node.children:
for child in node.children:
drawOctreeRecursive(child)
# 绘制点云数据
def drawPoints(points):
x = [p[0] for p in points]
y = [p[1] for p in points]
z = [p[2] for p in points]
mlab.points3d(x, y, z, scale_factor=0.01)
# 绘制八叉树结构
drawOctreeRecursive(octree.root)
# 绘制点云数据
drawPoints(octree.getPoints())
mlab.show()
在上述代码中,draw_cube
函数用于绘制一个立方体表示八叉树的节点空间范围,drawOctreeRecursive
函数通过递归遍历八叉树来绘制所有节点的立方体,drawPoints
函数用于绘制原始点云数据中的点。最后调用 mlab.show
来显示可视化结果。
可以在创建八叉树后调用 visualizeOctree
函数进行可视化:
# 假设 pointCloud 是已有的点云数据列表
octree = Octree(pointCloud)
visualizeOctree(octree)
通过可视化,可以直观地看到八叉树如何将三维空间划分为不同层次的子空间,以及点云数据在这些子空间中的分布情况,有助于进一步分析和理解八叉树结构在点云数据处理中的作用和效果。这对于调试和优化八叉树相关算法,以及深入研究点云数据的空间特性都具有重要意义。
综上所述,无论是 C# 还是 Python 实现,将点云数据转换为八叉树结构都为点云数据的高效处理和分析提供了有力手段,并且可以根据具体需求进一步拓展八叉树的应用,如碰撞检测、空间索引等,在众多三维数据处理相关领域发挥重要作用。
标签:node,Vector3,为八叉,min,max,树结构,点云,corner,节点 From: https://blog.csdn.net/m0_60315436/article/details/144382229