【第二期:MATLAB点云处理基础】KD树与点云邻域搜索

本文以 bunny_base.las 点云为例,介绍 MATLAB 中 KD 树的基础使用方法,包括 K 近邻搜索、半径邻域搜索、局部邻域提取、区域提取和局部密度显示。

开篇说明

在点云处理中,经常需要找到某个点周围的一批邻近点。比如法向量估计、曲率计算、边界点提取、点云滤波、局部特征分析等,都离不开邻域搜索。

如果每次都遍历全部点,计算效率会比较低。KD 树可以理解为一种空间索引结构,能够帮助我们更快地查找最近邻点或指定半径范围内的邻域点。

本期使用 bunny_base.las 作为示例数据。相比桥梁这类线状工程点云,兔子点云的轮廓更集中,查询点、邻域点和局部密度的显示效果更直观。

1. 读取 bunny_base.las 点云数据

首先读取 LAS 点云,并提取点云坐标矩阵 XYZ。

复制代码
clc; clear; close all;
%% ================== 1. 读取 bunny_base.las ==================
fileName = "bunny_base.las";
lasReader = lasFileReader(fileName);
ptCloud = readPointCloud(lasReader);
XYZ = ptCloud.Location;
fprintf('点云数量:%d\n', size(XYZ,1));

其中,XYZ(:,1)、XYZ(:,2)、XYZ(:,3) 分别对应点云的 X、Y、Z 坐标。

2. 建立 KD 树

读取点云坐标后,可以使用 KDTreeSearcher 建立 KD 树。后续的 K 近邻搜索和半径邻域搜索都基于这个模型完成。

复制代码
%% ================== 2. 建立 KD 树 ==================
Mdl = KDTreeSearcher(XYZ);
disp('KD树建立完成');

3. 选择查询点

为了让示例显示更稳定,这里先计算点云中心,再选择距离中心最近的点作为查询点。这样查询点通常位于模型主体区域,邻域显示效果更清楚。

复制代码
%% ================== 3. 选择查询点 ==================
centerPoint = mean(XYZ,1);
[queryIndex, ~] = knnsearch(Mdl, centerPoint, 'K',1);
queryPoint = XYZ(queryIndex,:);

4. K近邻搜索

K 近邻搜索的意思是:对于一个查询点,查找距离它最近的 K 个点。这里设置 K=30。

复制代码
%% ================== 4. K近邻搜索 ==================
K = 30;
[idxKNN, distKNN] = knnsearch(Mdl, queryPoint, 'K', K);
neighborPoints = XYZ(idxKNN,:);

得到的 idxKNN 是邻近点索引,distKNN 是这些邻近点到查询点的距离。可视化时,红色点表示查询点,绿色点表示 K 个最近邻点。

复制代码
figure;pcshow(XYZ, 'MarkerSize', 10);hold on;
plot3(queryPoint(1),
queryPoint(2), queryPoint(3), ...
'ro', 'MarkerSize', 10, 'LineWidth', 2);
plot3(neighborPoints(:,1),
neighborPoints(:,2), neighborPoints(:,3), ...
   'g.', 'MarkerSize', 20);
 axis equal;
xlabel('X');
ylabel('Y');
zlabel('Z');
title('K近邻搜索结果');
ax = gca;
set(ax, 'XColor','k',
'YColor','k', 'ZColor','k', 'Color','w');
set(gcf, 'Color','w');
 grid off;
box on;
legend('点云', '查询点', 'K近邻点');
view([12.416,17.9678]);

5. 半径邻域搜索

半径邻域搜索的意思是:以查询点为中心,查找指定半径范围内的所有点。这里先根据点云包围盒对角线长度自适应确定半径。

复制代码
%% ================== 5. 半径邻域搜索 ==================
bboxSize = max(XYZ,[],1) - min(XYZ,[],1);
diagLen = norm(bboxSize);
r = 0.03 * diagLen;
idxRadiusCell = rangesearch(Mdl, queryPoint, r);
idxRadius = idxRadiusCell{1};
radiusPoints = XYZ(idxRadius,:);

需要注意,rangesearch 返回的是 cell 类型结果,因此需要使用 idxRadiusCell{1} 提取第一个查询点对应的邻域索引。

复制代码
figure;
pcshow(XYZ, 'MarkerSize', 10);
hold on;
 plot3(queryPoint(1), queryPoint(2), queryPoint(3), ...
'ro', 'MarkerSize', 10, 'LineWidth', 2);
 plot3(radiusPoints(:,1), radiusPoints(:,2), radiusPoints(:,3), ...
'y.', 'MarkerSize', 20);
 axis equal;
xlabel('X');
ylabel('Y');
zlabel('Z');
title('半径邻域搜索结果');
ax = gca;
set(ax, 'XColor','k', 'YColor','k', 'ZColor','k', 'Color','w');
set(gcf, 'Color','w');
 grid off;
box on;
legend('点云', '查询点', '半径邻域点');
view([12.416,17.9678]);

6. 提取半径邻域点云

通过半径搜索得到邻域点后,可以直接生成新的 pointCloud 对象,用于单独显示或后续处理。

复制代码
%% ================== 6. 提取半径邻域点云 ==================
ptCloud_radius = pointCloud(radiusPoints);
figure;pcshow(ptCloud_radius.Location, 'MarkerSize', 30);
axis equal;
xlabel('X');
ylabel('Y');
zlabel('Z');
title('提取的局部邻域点云');
ax = gca;
set(ax, 'XColor','k', 'YColor','k', 'ZColor','k', 'Color','w');
set(gcf, 'Color','w');
grid off;
box on;
view([12.416,17.9678]);

7. 指定区域点云提取

除了基于查询点进行邻域搜索,也可以直接按照坐标范围提取点云局部区域。下面示例提取 X 方向后半部分区域。

复制代码
%% ==================7. 指定区域点云提取 ==================
xRange = range(XYZ(:,1));
xmin = min(XYZ(:,1)) + 0.45 *
xRange;
xmax = max(XYZ(:,1));
ymin = min(XYZ(:,2));
ymax = max(XYZ(:,2));
zmin = min(XYZ(:,3));
zmax = max(XYZ(:,3));
idx_roi = XYZ(:,1) >= xmin
& XYZ(:,1) <= xmax & ...
          XYZ(:,2) >= ymin & XYZ(:,2)
<= ymax & ...
          XYZ(:,3) >= zmin & XYZ(:,3)
<= zmax;
XYZ_roi =XYZ(idx_roi,:);
figure;
pcshow(XYZ_roi, 'MarkerSize', 20);
axis equal;
xlabel('X');
ylabel('Y');
zlabel('Z');
title('指定区域内的点云');
ax = gca;
set(ax, 'XColor','k',
'YColor','k', 'ZColor','k', 'Color','w');
set(gcf, 'Color','w');
grid off;
box on;
view([12.416,17.9678]);

8. 计算局部密度

局部密度可以简单理解为:在某个半径范围内,邻域点数量越多,说明该位置附近点云越密集。为了提高演示速度,这里随机抽取 5000 个点进行密度显示。

复制代码
%% ==================8. 计算局部密度 ==================
sampleNum = 5000;
if size(XYZ,1) > sampleNum
sampleIdx = randperm(size(XYZ,1), sampleNum);
else
sampleIdx = 1:size(XYZ,1);
end
XYZ_sample = XYZ(sampleIdx,:);
r_density = 0.03 * diagLen;
idxDensity = rangesearch(Mdl,
XYZ_sample, r_density);
densityValue =zeros(length(idxDensity),1);
for i = 1:length(idxDensity)
densityValue(i) = length(idxDensity{i});
end
figure;
pcshow(XYZ_sample, densityValue,'MarkerSize', 20);
axis equal;
colormap(jet);
colorbar;
xlabel('X');
ylabel('Y');
zlabel('Z');
title('基于邻域点数量的局部密度显示');
ax = gca;
set(ax, 'XColor','k',
'YColor','k', 'ZColor','k', 'Color','w');
set(gcf, 'Color','w');
grid off;
box on;
view([12.416,17.9678]);

9. 完整代码

下面给出本文完整代码,可直接复制运行。

复制代码
clc; clear; close all;

%% ================== 1. 读取 bunny_base.las ==================
fileName = "bunny_base.las";

lasReader = lasFileReader(fileName);
ptCloud = readPointCloud(lasReader);
XYZ = ptCloud.Location;
fprintf('点云数量:%d\n', size(XYZ,1));
%% ================== 2. 建立 KD 树 ==================
Mdl = KDTreeSearcher(XYZ);
disp('KD树建立完成');
%% ================== 3. 选择查询点 ==================
centerPoint = mean(XYZ,1);
[queryIndex, ~] = knnsearch(Mdl, centerPoint, 'K', 1);
queryPoint = XYZ(queryIndex,:);
%% ================== 4. K近邻搜索 ==================
K = 30;
[idxKNN, distKNN] = knnsearch(Mdl, queryPoint, 'K', K);
neighborPoints = XYZ(idxKNN,:);
figure;
pcshow(XYZ, 'MarkerSize', 10);
hold on;
plot3(queryPoint(1), queryPoint(2), queryPoint(3), ...
    'ro', 'MarkerSize', 10, 'LineWidth', 2);
plot3(neighborPoints(:,1), neighborPoints(:,2), neighborPoints(:,3), ...
    'g.', 'MarkerSize', 20);
axis equal;
xlabel('X');
ylabel('Y');
zlabel('Z');
title('K近邻搜索结果');
ax = gca;
set(ax, 'XColor','k', 'YColor','k', 'ZColor','k', 'Color','w');
set(gcf, 'Color','w');
grid off;
box on;
legend('点云', '查询点', 'K近邻点');
view(3);
%% ================== 5. 半径邻域搜索 ==================
bboxSize = max(XYZ,[],1) - min(XYZ,[],1);
diagLen = norm(bboxSize);
r = 0.03 * diagLen;
idxRadiusCell = rangesearch(Mdl, queryPoint, r);
idxRadius = idxRadiusCell{1};
radiusPoints = XYZ(idxRadius,:);
figure;
pcshow(XYZ, 'MarkerSize', 10);
hold on;
plot3(queryPoint(1), queryPoint(2), queryPoint(3), ...
    'ro', 'MarkerSize', 10, 'LineWidth', 2);
plot3(radiusPoints(:,1), radiusPoints(:,2), radiusPoints(:,3), ...
    'y.', 'MarkerSize', 20);
axis equal;
xlabel('X');
ylabel('Y');
zlabel('Z');
title('半径邻域搜索结果');
ax = gca;
set(ax, 'XColor','k', 'YColor','k', 'ZColor','k', 'Color','w');
set(gcf, 'Color','w');
grid off;
box on;
legend('点云', '查询点', '半径邻域点');
view(3);
%% ================== 6. 提取半径邻域点云 ==================
ptCloud_radius = pointCloud(radiusPoints);
figure;
pcshow(ptCloud_radius.Location, 'MarkerSize', 30);
axis equal;
xlabel('X');
ylabel('Y');
zlabel('Z');
title('提取的局部邻域点云');
ax = gca;
set(ax, 'XColor','k', 'YColor','k', 'ZColor','k', 'Color','w');
set(gcf, 'Color','w');
grid off;
box on;
view(3);
%% ================== 7. 指定区域点云提取 ==================
xRange = range(XYZ(:,1));
xmin = min(XYZ(:,1)) + 0.45 * xRange;
xmax = max(XYZ(:,1));
ymin = min(XYZ(:,2));
ymax = max(XYZ(:,2));
zmin = min(XYZ(:,3));
zmax = max(XYZ(:,3));
idx_roi = XYZ(:,1) >= xmin & XYZ(:,1) <= xmax & ...
          XYZ(:,2) >= ymin & XYZ(:,2) <= ymax & ...
          XYZ(:,3) >= zmin & XYZ(:,3) <= zmax;
XYZ_roi = XYZ(idx_roi,:);
figure;
pcshow(XYZ_roi, 'MarkerSize', 20);
axis equal;
xlabel('X');
ylabel('Y');
zlabel('Z');
title('指定区域内的点云');
ax = gca;
set(ax, 'XColor','k', 'YColor','k', 'ZColor','k', 'Color','w');
set(gcf, 'Color','w');
grid off;
box on;
view(3);
%% ================== 8. 计算局部密度 ==================
sampleNum = 5000;
if size(XYZ,1) > sampleNum
    sampleIdx = randperm(size(XYZ,1), sampleNum);
else
    sampleIdx = 1:size(XYZ,1);
end
XYZ_sample = XYZ(sampleIdx,:);
r_density = 0.03 * diagLen;
idxDensity = rangesearch(Mdl, XYZ_sample, r_density);
densityValue = zeros(length(idxDensity),1);
for i = 1:length(idxDensity)
    densityValue(i) = length(idxDensity{i});
end
figure;
pcshow(XYZ_sample, densityValue, 'MarkerSize', 20);
axis equal;
colormap(jet);
colorbar;
xlabel('X');
ylabel('Y');
zlabel('Z');
title('基于邻域点数量的局部密度显示');
ax = gca;
set(ax, 'XColor','k', 'YColor','k', 'ZColor','k', 'Color','w');
set(gcf, 'Color','w');
grid off;
box on;
view(3);

10. 本文小结

本文以 bunny_base.las 点云为例,介绍了 MATLAB 中 KD 树与邻域搜索的基础流程。主要包括:

使用 lasFileReader 和 readPointCloud 读取 LAS 点云;

使用 KDTreeSearcher 建立 KD 树;

使用 knnsearch 进行 K 近邻搜索;

使用 rangesearch 进行半径邻域搜索;

基于索引提取局部点云和指定区域点云;

通过邻域点数量显示点云局部密度。

KD 树是点云处理中的基础工具。后续很多操作,例如法向量估计、曲率计算、边界点识别和点云分割,都会用到邻域搜索。

相关推荐
2501_938140073 小时前
做内容很省心,2026好用AI笔记,支持抖音小红书视频无水印保存
人工智能
sell-cloud6 小时前
Prompt Engineering要凉了?Fable 5.5能“猜“意图,但银行正在封杀AI Agent——企业AI的真正护城河是什么
人工智能·prompt
java1234_小锋6 小时前
【技术专题】HTML5 网页技术 - HTML5 Canvas 基础以及动画入门
前端·html·html5
Navicat中国7 小时前
Navicat 18 内置 AI 助手让数据库开发从繁琐操作迈向智能协作
大数据·人工智能·数据库开发
4SAPI8 小时前
多模型请求路由怎么设计?成本、速度和能力如何平衡
人工智能·python·gateway·php
魔乐社区8 小时前
翼芯・智算测试与适配优化专区,让国产算力“选得准、跑得通、用得好“
人工智能·开源·大模型
Pniubi9 小时前
HashMap的扩容和树化边界
java·开发语言
Mikko79 小时前
用 Java 复刻了高中时在塞班上玩的「熔岩矿车」:纯 Swing、零依赖、没有一张图片,一个 jar 双击就能玩
java·游戏开发·swing
知了学历孙老师9 小时前
小自考的校考和统考,出题方和难度差在哪
java·大数据·前端
Java爱好狂.9 小时前
Java初学者如何一周速通SpringBoot!
java·spring boot·后端·spring·高并发·架构师·java面试