【第二期: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 树是点云处理中的基础工具。后续很多操作,例如法向量估计、曲率计算、边界点识别和点云分割,都会用到邻域搜索。

相关推荐
Lank_M2 分钟前
关键帧从1秒拉到10秒,录屏小87.3%,摄像头小10.8%
前端
补码缩写4 分钟前
用 ffprobe 拆录屏,八成字节在 29 个关键帧上
前端
百思不得小李6 分钟前
一个复杂的动态表单是如何实现的
前端·设计模式
黄啊码8 分钟前
从 0 到 1 做 FDE:如何获客、成交、交付?百万级 AI 项目实战复盘
人工智能
斯维赤12 分钟前
限流、熔断、降级,你真的分得清吗?
java·后端·架构
玩美数据19 分钟前
海外市场数据服务常用术语与名词解释权威指南
大数据·人工智能
陈随易20 分钟前
Cmder 停更 17 个月,2.0 支持 Windows Terminal
前端·后端·程序员
风骏时光牛马31 分钟前
前沿视界
前端
kyriewen35 分钟前
我扫了 814 个依赖,97 个还敢要 Node 10
前端·javascript·程序员
Lansonli37 分钟前
Claude Code 写 Shell 脚本翻车实录:一份避坑指南
人工智能