Matlab通过GUI实现点云的导向(引导)滤波(附最简版)

本次分享点云的引导滤波。众索粥汁,点云滤波是三维数据处理中的核心步骤,其目的是去除噪声、保留关键几何特征,为后续的分割、配准、重建等任务提供高质量数据。导向滤波(Guided Filter) 作为一种经典的边缘保持滤波算法,最初用于二维图像处理,后被拓展至三维点云领域。它通过引入 "导向点集"(通常为原始点云或其降采样版本)来指导滤波过程,在平滑噪声的同时,精准保留点云的边缘、褶皱等细节特征,在 MATLAB 环境中可通过点云处理工具箱(Point Cloud Processing Toolbox)高效实现。

一、点云导向滤波的核心原理与优势

  1. 核心原理

导向滤波的本质是基于局部邻域的加权平滑,其核心思想是:在计算某一目标点的滤波后坐标时,以 "导向点" 的局部结构为约束,确保滤波结果既贴合原始点云的几何趋势,又不模糊边缘。具体逻辑如下:

  • 导向点集(Guide Set):作为滤波的 "结构参考",通常选择原始点云的降采样版本(如通过体素降采样得到),或原始点云中特征显著的点(如边缘点),其数量远少于原始点云,可降低计算复杂度。
  • 局部邻域搜索:对原始点云中的每个目标点,以其为中心构建局部邻域(常用 K 近邻 KNN 或固定半径搜索),并找到该邻域内对应的导向点。
  • 加权平滑计算:根据目标点与邻域内导向点的距离、法向量夹角等几何相似度,分配权重(相似度越高,权重越大),最终通过加权平均更新目标点的坐标,实现噪声平滑。
  1. 关键优势

与传统的高斯滤波、双边滤波等相比,点云导向滤波的核心优势体现在:

  • 强边缘保持能力:通过导向点的局部结构约束,避免滤波过程中 "模糊" 点云的边缘(如物体表面的棱角、两个平面的交线);
  • 自适应平滑:对平坦区域(噪声集中区域)平滑效果显著,对特征区域(边缘、褶皱)保留完整;
  • 低计算复杂度:通过导向点集减少邻域搜索和计算量,适用于百万级甚至千万级的大规模点云;
  • 鲁棒性强:对高斯噪声、离群点(少量)均有较好的抑制效果,且对邻域参数(如 K 值、半径)的敏感度较低。

二、MATLAB 点云导向滤波的主要实现流程

MATLAB 的Point Cloud Processing Toolbox提供了点云读取、降采样、邻域搜索、滤波等一站式函数,导向滤波的实现可分为 6 个核心步骤,以下结合代码示例说明:

步骤 1:环境准备与点云读取

首先确保安装 "Point Cloud Processing Toolbox",然后读取原始点云(支持 PLY、PCD 等格式),并可视化原始数据(便于后续对比滤波效果)。

cpp 复制代码
% 检查工具箱是否安装

if ~license('test', 'pointcloud')

error('需安装 Point Cloud Processing Toolbox');

end

% 读取原始点云(以PLY格式为例,可替换为PCD等)

ptCloudRaw = pcread('original_point_cloud.ply');

% 可视化原始点云

figure('Name', '原始点云');

pcshow(ptCloudRaw);

title('原始点云(含噪声)');

xlabel('X (m)'); ylabel('Y (m)'); zlabel('Z (m)');

grid on;

步骤 2:原始点云预处理

预处理的核心是去除离群点 (避免极端噪声影响导向点选择)和估计法向量(用于后续邻域权重计算,衡量点与点的几何相似度)。

cpp 复制代码
% 1. 去除离群点(使用统计离群点移除,默认3倍标准差)

ptCloudDenoised = pcdenoise(ptCloudRaw);

% 2. 估计点云法向量(K近邻数设为20,可根据点云密度调整)

ptCloudWithNormals = pcnormals(ptCloudDenoised, 20);

% 可视化预处理后的点云

figure('Name', '预处理后点云');

pcshow(ptCloudWithNormals);

title('去除离群点+估计法向量后点云');

xlabel('X (m)'); ylabel('Y (m)'); zlabel('Z (m)');

grid on;

步骤 3:生成导向点集

导向点集需满足 "数量少、结构准" 的特点,常用体素降采样(Voxel Grid Downsampling) 生成(通过pcdownsample函数),降采样分辨率需根据点云密度调整(如 0.02m 表示体素边长为 2cm)。

cpp 复制代码
% 体素降采样生成导向点集(分辨率根据点云密度调整,此处设为0.02m)

voxelSize = 0.02;

ptCloudGuide = pcdownsample(ptCloudWithNormals, 'gridAverage', voxelSize);

% 可视化导向点集(红色)与原始预处理点云(蓝色)的对比

figure('Name', '导向点集与预处理点云对比');

hold on;

pcshow(ptCloudWithNormals, 'Color', 'blue', 'MarkerSize', 2); % 预处理点云(蓝色)

pcshow(ptCloudGuide, 'Color', 'red', 'MarkerSize', 5); % 导向点集(红色)

title('导向点集(红)与预处理点云(蓝)');

legend('预处理点云', '导向点集');

xlabel('X (m)'); ylabel('Y (m)'); zlabel('Z (m)');

grid on;

hold off;

步骤 4:局部邻域搜索与权重计算

对预处理点云中的每个点,搜索其局部邻域内的导向点,并根据空间距离法向量夹角计算权重(权重越大,说明该导向点对目标点的 "指导作用" 越强)。

cpp 复制代码
% 获取预处理点云和导向点的坐标与法向量

pointsRaw = ptCloudWithNormals.Location; % 预处理点云坐标 (N×3)

normalsRaw = ptCloudWithNormals.Normal; % 预处理点云法向量 (N×3)

pointsGuide = ptCloudGuide.Location; % 导向点坐标 (M×3, M<<N)

normalsGuide = ptCloudGuide.Normal; % 导向点法向量 (M×3)

% 设置邻域参数:K近邻数(搜索每个目标点的15个最近导向点)

K = 15;

% 构建导向点的KD树(加速邻域搜索,大规模点云必备)

kdtreeGuide = KDTreeSearcher(pointsGuide);

% 初始化权重矩阵和滤波后坐标

weights = zeros(size(pointsRaw, 1), K); % 每个目标点对应K个导向点的权重

pointsFiltered = zeros(size(pointsRaw)); % 滤波后点云坐标

% 逐点计算权重与滤波后坐标

for i = 1:size(pointsRaw, 1)

% 步骤4.1:搜索目标点i的K个最近导向点(返回索引和距离)

[idxNeighbors, distNeighbors] = knnsearch(kdtreeGuide, pointsRaw(i, :), 'K', K);

% 步骤4.2:提取邻域内导向点的坐标和法向量

neighborsLoc = pointsGuide(idxNeighbors, :);

neighborsNorm = normalsGuide(idxNeighbors, :);

% 步骤4.3:计算权重(结合距离和法向量相似度,权重归一化)

% 1. 距离权重:距离越小,权重越大(使用高斯函数)

sigmaDist = max(distNeighbors) / 2; % 距离尺度参数(自适应)

weightDist = exp(-distNeighbors.^2 / (2 * sigmaDist^2));

% 2. 法向量相似度权重:法向量夹角越小,权重越大(点积表示相似度)

dotNorm = max(dot(normalsRaw(i, :), neighborsNorm, 2), 0); % 点积非负(避免反向法向量)

weightNorm = (dotNorm + 1) / 2; % 归一化到[0.5, 1]

% 3. 总权重:距离权重 × 法向量权重,归一化后总和为1

weights(i, :) = weightDist .* weightNorm;

weights(i, :) = weights(i, :) / sum(weights(i, :));

% 步骤4.4:加权平均计算滤波后坐标

pointsFiltered(i, :) = weights(i, :) * neighborsLoc;

end

步骤 5:生成滤波后点云并可视化

将滤波后坐标与原始法向量(或重新估计法向量)结合,生成最终点云,并与原始点云对比,验证滤波效果。

cpp 复制代码
% 生成滤波后点云(保留原始法向量,也可重新调用pcnormals估计)

ptCloudFiltered = pointCloud(pointsFiltered, 'Normal', normalsRaw);

% 对比可视化:原始点云 vs 滤波后点云

figure('Name', '滤波效果对比');

subplot(1, 2, 1);

pcshow(ptCloudRaw);

title('原始点云(含噪声)');

xlabel('X'); ylabel('Y'); zlabel('Z');

grid on;

subplot(1, 2, 2);

pcshow(ptCloudFiltered);

title('导向滤波后点云');

xlabel('X'); ylabel('Y'); zlabel('Z');

grid on;

步骤 6:滤波效果定量评估(可选)

通过计算点云粗糙度 (表面起伏程度)或均方根误差(RMSE)(若有标准无噪声点云)定量验证滤波效果:

cpp 复制代码
% 计算点云粗糙度(局部邻域内点到拟合平面的平均距离,值越小越平滑)

% 原始点云粗糙度

roughnessRaw = pcroughness(ptCloudRaw, 10); % 10个近邻拟合平面

% 滤波后点云粗糙度

roughnessFiltered = pcroughness(ptCloudFiltered, 10);

% 输出定量结果

fprintf('原始点云平均粗糙度:%.6f m\n', mean(roughnessRaw));

fprintf('滤波后点云平均粗糙度:%.6f m\n', mean(roughnessFiltered));

% (可选)若有标准无噪声点云,计算RMSE

if exist('gt_point_cloud.ply', 'file')

ptCloudGT = pcread('gt_point_cloud.ply');

% 配准后计算RMSE(确保标准点云和滤波点云对齐)

[ptCloudAligned, ~] = pcregistericp(ptCloudFiltered, ptCloudGT);

rmse = sqrt(mean(sum((ptCloudAligned.Location - ptCloudGT.Location).^2, 2)));

fprintf('滤波后点云与标准点云的RMSE:%.6f m\n', rmse);

end

三、点云导向滤波的典型应用领域

导向滤波因 "边缘保持 + 高效平滑" 的特性,在需要高质量三维数据的领域中广泛应用,以下为主要场景:

  1. 工业检测与质量控制
  • 应用场景:机械零件(如齿轮、轴承)的表面缺陷检测、尺寸精度测量;汽车车身、飞机蒙皮的外形一致性验证。
  • 核心作用:去除工业扫描(如激光扫描、结构光扫描)中的测量噪声(如设备振动、环境光干扰导致的点云抖动),保留零件的边缘、倒角、孔洞等关键特征,确保后续尺寸测量(如pcmeasure函数)和缺陷识别的准确性。
  • MATLAB 工具链:导向滤波 → pcsegdist(距离分割)→ pcmeasure(尺寸测量)→ 缺陷判定。
  1. 逆向工程与三维重建
  • 应用场景:文物数字化(如雕塑、古建筑构件)、产品设计逆向(如复制已有产品的三维模型)、人体器官建模(如牙科扫描、骨科扫描)。
  • 核心作用:对扫描得到的密集点云(通常含大量噪声)进行平滑,同时保留文物的纹理细节(如雕刻纹路)、人体器官的解剖结构(如骨骼的关节面、牙齿的咬合面),为后续的网格重建(如pc2mesh函数)和模型优化提供干净的点云数据。
  • 典型流程:扫描点云 → 导向滤波 → 网格重建 → 模型修复 → 3D 打印 / 设计优化。
  1. 自动驾驶与环境感知
  • 应用场景:自动驾驶车辆的激光雷达(LiDAR)点云处理(如道路分割、障碍物检测);无人机低空测绘中的地面点云去噪。
  • 核心作用:去除 LiDAR 点云中的噪声(如雨滴、灰尘反射导致的离群点,车辆抖动导致的点云偏移),保留道路边缘、护栏、行人、其他车辆等目标的轮廓特征,确保后续分割(如pcsegplane分割地面、pcsegcluster聚类障碍物)和目标识别的可靠性。
  • MATLAB 优势:可结合自动驾驶工具箱(Automated Driving Toolbox),将导向滤波后的点云与相机图像融合,提升环境感知精度。
  1. 机器人视觉与抓取规划
  • 应用场景:工业机器人抓取物体(如机械臂抓取零件)、服务机器人识别家居物品(如杯子、书籍)。
  • 核心作用:去除机器人视觉传感器(如深度相机 Kinect、激光扫描仪)获取的点云中的环境噪声(如桌面反光、背景杂物干扰),清晰保留目标物体的外形特征(如物体的把手、棱角),为机器人抓取点规划(如graspit工具箱)提供准确的物体几何信息。
  1. 文化遗产与数字孪生
  • 应用场景:古建筑、石窟佛像的数字化保护(如敦煌莫高窟、故宫文物);城市数字孪生中的建筑、道路点云处理。
  • 核心作用:对大场景扫描(如地面三维激光扫描、无人机倾斜摄影)得到的海量点云进行高效平滑(导向滤波的低复杂度适合大规模点云),保留古建筑的飞檐、斗拱、雕刻等细节,为数字孪生模型的构建和文化遗产的长期保存提供高质量的三维数据。

四、MATLAB 点云导向滤波的关键参数调优建议

在实际应用中,导向滤波的效果受参数影响较大,以下为核心参数的调优原则:

  1. 导向点降采样分辨率( voxelSize
  • 点云密度高(如工业零件扫描):分辨率设为点云平均间距的 2-3 倍,避免导向点过少导致结构丢失;
  • 点云密度低(如大场景测绘):分辨率可适当增大(如 0.1-0.5m),减少计算量。
  1. 邻域 K 值
  • 平坦区域为主的点云(如桌面、墙面):K=10-15,避免过度平滑;
  • 特征复杂区域为主的点云(如雕塑、齿轮):K=15-25,确保邻域覆盖足够的导向点,提升平滑稳定性。
  1. 距离尺度参数( sigmaDist
  • 通常设为邻域内最大距离的 1/2-1/3,确保距离权重的区分度(避免权重过于平均或极端)。

五、总结

MATLAB 的点云导向滤波通过 "导向点约束 + 局部加权平滑",实现了噪声去除与特征保留的平衡,其实现流程清晰(预处理→导向点生成→邻域权重计算→滤波),且可结合 MATLAB 丰富的工具箱(如点云处理、自动驾驶、机器人工具箱)构建完整的三维数据处理 pipeline。该算法在工业检测、逆向工程、自动驾驶等领域中具有不可替代的作用,是提升三维点云数据质量的核心技术之一。

本次使用的数据,不是窗帘啦,那么会是谁呢,好难猜啊,真的不知道是谁啊

一、点云进行引导滤波的程序

1、最简版

cpp 复制代码
%% 0. 清空环境
clear; clc; close all;

%% 1. 读入点云
[file, path] = uigetfile({'*.ply;*.pcd;*.xyz', '点云文件 (*.ply,*.pcd,*.xyz)'}, ...
                         '请选择点云');
if file == 0; return; end  % 用户取消选择时退出
fname = fullfile(path, file);
ptCloud = pcread(fname);
N = ptCloud.Count;
fprintf('原始点云有 %d 个点\n', N);

%% 2. 计算点云平均密度(用于确定滤波参数)
kdtree = createns(ptCloud.Location, 'NSMethod', 'kdtree');
[~, dists] = knnsearch(kdtree, ptCloud.Location, 'K', 2);  % 搜索最近邻
mean_dist = mean(dists(:,2));  % 取第二个元素(最近邻距离)的平均值
fprintf('点云平均密度(最近邻距离): %.4f mm\n', mean_dist);

%% 3. 添加高斯噪声(创建新的pointCloud对象)
noise_level = 2 * mean_dist;  % 噪声水平基于点云密度
% 复制原始点并添加噪声
noisyPoints = ptCloud.Location + noise_level * randn(N, 3);
% 创建新的点云对象,保留原始颜色和法向量
noisyCloud = pointCloud(noisyPoints, 'Color', ptCloud.Color, 'Normal', ptCloud.Normal);
fprintf('已添加高斯噪声(标准差: %.4f mm)\n', noise_level);

%% 4. 应用自适应引导滤波
radius = 10 * mean_dist;      % 邻域半径(基于点云密度)
epsilon = 100 * mean_dist;    % 正则化参数(基于点云密度)
filteredCloud = pcdGuidedFilter(noisyCloud, radius, epsilon);
fprintf('引导滤波后点云有 %d 个点\n', filteredCloud.Count);

%% 5. 可视化结果
figure('Name', '原始点云', 'NumberTitle', 'off');
pcshow(ptCloud); axis on; view(3); title('原始点云');

figure('Name', '添加噪声的点云', 'NumberTitle', 'off');
pcshow(noisyCloud); axis on; view(3); title('添加噪声的点云');

figure('Name', '自适应引导滤波后的点云', 'NumberTitle', 'off');
pcshow(filteredCloud); axis on; view(3); title('自适应引导滤波后的点云');

%% 自适应引导滤波函数
function out = pcdGuidedFilter(pc, radius, epsilon)
% 对输入点云应用自适应引导滤波
% 输入:
%   pc        - pointCloud对象,原始点云
%   radius    - 邻域搜索半径
%   epsilon   - 正则化参数
% 输出:
%   out       - 滤波后的pointCloud对象

xyz = pc.Location;
n = size(xyz, 1);
newXYZ = xyz;  % 初始化新坐标矩阵(默认保留原始点)

% 创建KD树用于高效邻域搜索
kd = createns(xyz, 'NSMethod', 'kdtree');

% 分块处理以优化内存使用
block_size = 5000;
for i = 1:block_size:n
    block_end = min(i + block_size - 1, n);
    % 搜索当前块内所有点的邻域
    idxCell = rangesearch(kd, xyz(i:block_end, :), radius);
    
    % 逐个处理块内的点
    for j = i:block_end
        % 获取当前点的邻域索引
        idx = idxCell{j - i + 1};
        
        % 邻域点少于3个时,保留原始点(保护边缘)
        if length(idx) < 3
            newXYZ(j, :) = xyz(j, :);
            continue;
        end
        
        % 提取邻域点并计算统计特征
        neighbors = xyz(idx, :);
        mean_neigh = mean(neighbors, 1);    % 邻域均值 (1×3)
        cov_neigh = cov(neighbors);         % 邻域协方差矩阵 (3×3)
        
        % 计算正则化协方差矩阵的逆矩阵
        inv_cov = inv(cov_neigh + epsilon * eye(3));
        
        % 计算引导滤波系数 (修正矩阵维度问题)
        A = cov_neigh * inv_cov;            % 系数矩阵A (3×3)
        
        % 计算偏移向量b (确保维度正确)
        b = mean_neigh' - A * mean_neigh';  % 偏移向量b (3×1)
        
        % 计算滤波后的点坐标 (修正维度不匹配问题)
        newXYZ(j, :) = (A * xyz(j, :)' + b)';  % 结果为1×3
    end
end

% 重建点云对象,保留颜色和法向量信息
out = pointCloud(newXYZ, 'Color', pc.Color, 'Normal', pc.Normal);
end

2、GUI版本

cpp 复制代码
function guidedFilterGUI
% 自适应引导滤波 GUI ------ 2020a 兼容
% 1. 浏览选点云  2. 调整滤波参数  3. 实时引导滤波  4. 保存结果

fig = figure('Name','自适应引导滤波工具','NumberTitle','off',...
             'MenuBar','none','ToolBar','none','Position',[100 100 1280 720]);

%% ---------- 左侧图像区(78 %) ----------
imgWidth = 0.78;
panelW   = imgWidth/3 - 0.01;  % 三面板布局

% 原始点云面板
pnlOrig = uipanel('Parent',fig,'Units','normalized',...
                  'FontSize',16,'Position',[0.02 0.02 panelW 0.96],'Title','原始点云');
% 含噪声点云面板
pnlNoisy = uipanel('Parent',fig,'Units','normalized',...
                  'FontSize',16,'Position',[0.02+panelW+0.01 0.02 panelW 0.96],'Title','含噪声点云');
% 滤波结果面板
pnlFilt = uipanel('Parent',fig,'Units','normalized',...
                  'FontSize',16,'Position',[0.02+2*(panelW+0.01) 0.02 panelW 0.96],'Title','滤波结果');

% 三个坐标轴
axOrig = axes('Parent',pnlOrig,'Units','normalized','Position',[0.05 0.05 0.90 0.90]);
axNoisy = axes('Parent',pnlNoisy,'Units','normalized','Position',[0.05 0.05 0.90 0.90]);
axFilt = axes('Parent',pnlFilt,'Units','normalized','Position',[0.05 0.05 0.90 0.90]);

%% ---------- 右侧控制区(22 %) ----------
pnlCtrl = uipanel('Parent',fig,'Units','normalized',...
                  'FontSize',16,'Position',[0.78 0 0.22 1],'Title','控制');

txtH  = 0.04;
btnH  = 0.06;
gap   = 0.02;
yTop  = 0.94;

% 1. 浏览加载点云
uicontrol('Parent',pnlCtrl,'Style','pushbutton','String','浏览...',...
          'FontSize',16,'Units','normalized','Position',[0.05 yTop-btnH 0.90 btnH],...
          'Callback',@loadCloud);
yTop = yTop - btnH - gap;

% 点云信息显示
lblInfo = uicontrol('Parent',pnlCtrl,'Style','text','String','未加载点云',...
                    'FontSize',10,'Units','normalized','Position',[0.05 yTop-txtH 0.90 txtH],...
                    'HorizontalAlignment','left');
yTop = yTop - txtH - gap;

% 点云密度信息
lblDensity = uicontrol('Parent',pnlCtrl,'Style','text','String','平均密度: -- mm',...
                    'FontSize',10,'Units','normalized','Position',[0.05 yTop-txtH 0.90 txtH],...
                    'HorizontalAlignment','left');
yTop = yTop - txtH - gap;

% 2. 噪声水平设置
uicontrol('Parent',pnlCtrl,'Style','text','String','噪声水平倍数',...
          'FontSize',12,'FontWeight','bold','Units','normalized','Position',[0.05 yTop-txtH 0.90 txtH],...
          'HorizontalAlignment','left');
yTop = yTop - txtH - gap;

sliderNoise = uicontrol('Parent',pnlCtrl,'Style','slider','Min',0.5,'Max',5,'Value',2,...
                    'FontSize',16,'Units','normalized','Position',[0.05 yTop-btnH 0.65 btnH],...
                    'Callback',@refreshFilter);
txtNoise    = uicontrol('Parent',pnlCtrl,'Style','edit','String','2',...
                    'FontSize',16,'Units','normalized','Position',[0.75 yTop-btnH 0.20 btnH],...
                    'Callback',@editNoiseCB);
yTop = yTop - btnH - gap;

% 3. 邻域半径设置
uicontrol('Parent',pnlCtrl,'Style','text','String','邻域半径倍数',...
          'FontSize',12,'FontWeight','bold','Units','normalized','Position',[0.05 yTop-txtH 0.90 txtH],...
          'HorizontalAlignment','left');
yTop = yTop - txtH - gap;

sliderRadius = uicontrol('Parent',pnlCtrl,'Style','slider','Min',5,'Max',20,'Value',10,...
                    'FontSize',16,'Units','normalized','Position',[0.05 yTop-btnH 0.65 btnH],...
                    'Callback',@refreshFilter);
txtRadius    = uicontrol('Parent',pnlCtrl,'Style','edit','String','10',...
                    'FontSize',16,'Units','normalized','Position',[0.75 yTop-btnH 0.20 btnH],...
                    'Callback',@editRadiusCB);
yTop = yTop - btnH - gap;

% 4. 正则化参数设置
uicontrol('Parent',pnlCtrl,'Style','text','String','正则化参数倍数',...
          'FontSize',12,'FontWeight','bold','Units','normalized','Position',[0.05 yTop-txtH 0.90 txtH],...
          'HorizontalAlignment','left');
yTop = yTop - txtH - gap;

sliderEpsilon = uicontrol('Parent',pnlCtrl,'Style','slider','Min',50,'Max',200,'Value',100,...
                    'FontSize',16,'Units','normalized','Position',[0.05 yTop-btnH 0.65 btnH],...
                    'Callback',@refreshFilter);
txtEpsilon    = uicontrol('Parent',pnlCtrl,'Style','edit','String','100',...
                    'FontSize',16,'Units','normalized','Position',[0.75 yTop-btnH 0.20 btnH],...
                    'Callback',@editEpsilonCB);
yTop = yTop - btnH - gap;

% 5. 保存按钮
uicontrol('Parent',pnlCtrl,'Style','pushbutton','String','保存滤波结果',...
          'FontSize',16,'Units','normalized','Position',[0.05 yTop-btnH 0.90 btnH],...
          'Callback',@(s,e)saveCloud(filteredCloud));
yTop = yTop - btnH - gap;

% 6. 重新生成噪声按钮
uicontrol('Parent',pnlCtrl,'Style','pushbutton','String','重新生成噪声',...
          'FontSize',14,'Units','normalized','Position',[0.05 yTop-btnH 0.90 btnH],...
          'Callback',@regenerateNoise);
yTop = yTop - btnH - gap;

%% ---------- 数据存储 ----------
ptCloudOrig = pointCloud.empty;    % 原始点云
noisyCloud = pointCloud.empty;     % 含噪声点云
filteredCloud = pointCloud.empty;  % 滤波后点云
mean_dist = 0;                     % 平均密度

    %% ---------- 回调函数 ----------
    function loadCloud(~,~)
        [file,path] = uigetfile({'*.pcd;*.ply;*.xyz','点云文件'},'选择点云');
        if isequal(file,0), return; end
        try
            ptCloudOrig = pcread(fullfile(path,file));
        catch ME
            errordlg(ME.message,'读取失败'); return;
        end
        
        % 计算点云平均密度
        calculateDensity();
        
        % 显示原始点云
        showPointCloud(axOrig,ptCloudOrig);
        
        % 生成含噪声点云
        regenerateNoise();
        
        % 更新信息
        N = ptCloudOrig.Count;
        set(lblInfo,'String',sprintf('已加载:%s  (%d 点)',file,N));
        set(lblDensity,'String',sprintf('平均密度: %.4f mm',mean_dist));
    end

    function calculateDensity()
        % 计算点云平均密度(最近邻距离)
        if ~isempty(ptCloudOrig)
            kdtree = createns(ptCloudOrig.Location, 'NSMethod', 'kdtree');
            [~, dists] = knnsearch(kdtree, ptCloudOrig.Location, 'K', 2);
            mean_dist = mean(dists(:,2));  % 取第二个元素(最近邻距离)的平均值
        end
    end

    function regenerateNoise(~,~)
        % 重新生成含噪声点云
        if isempty(ptCloudOrig), return; end
        
        noise_level = get(sliderNoise,'Value') * mean_dist;
        N = ptCloudOrig.Count;
        
        % 添加高斯噪声
        noisyPoints = ptCloudOrig.Location + noise_level * randn(N, 3);
        noisyCloud = pointCloud(noisyPoints, 'Color', ptCloudOrig.Color, 'Normal', ptCloudOrig.Normal);
        
        % 显示含噪声点云并更新滤波结果
        showPointCloud(axNoisy,noisyCloud);
        refreshFilter();
    end

    function refreshFilter(~,~)
        % 刷新滤波结果
        if isempty(noisyCloud), return; end
        
        % 获取当前参数
        noise_mult = get(sliderNoise,'Value');
        radius_mult = get(sliderRadius,'Value');
        epsilon_mult = get(sliderEpsilon,'Value');
        
        % 更新参数显示
        set(txtNoise,'String',num2str(noise_mult));
        set(txtRadius,'String',num2str(radius_mult));
        set(txtEpsilon,'String',num2str(epsilon_mult));
        
        % 计算实际参数值
        radius = radius_mult * mean_dist;
        epsilon = epsilon_mult * mean_dist;
        
        % 应用引导滤波
        filteredCloud = pcdGuidedFilter(noisyCloud, radius, epsilon);
        
        % 显示滤波结果
        showPointCloud(axFilt,filteredCloud);
    end

    % 参数编辑框回调函数
    function editNoiseCB(src,~)
        v = str2double(get(src,'String'));
        if isnan(v), v = 2; end
        v = max(0.5,min(5,v));
        set(sliderNoise,'Value',v);
        regenerateNoise();  % 噪声变化需要重新生成噪声点云
    end

    function editRadiusCB(src,~)
        v = str2double(get(src,'String'));
        if isnan(v), v = 10; end
        v = max(5,min(20,v));
        set(sliderRadius,'Value',v);
        refreshFilter();
    end

    function editEpsilonCB(src,~)
        v = str2double(get(src,'String'));
        if isnan(v), v = 100; end
        v = max(50,min(200,v));
        set(sliderEpsilon,'Value',v);
        refreshFilter();
    end

    function saveCloud(cloud)
        % 保存滤波结果
        if isempty(cloud)
            errordlg('请先完成引导滤波','提示'); return;
        end
        [file,path] = uiputfile({'*.pcd','PCD';'*.ply','PLY';'*.xyz','XYZ'},'保存滤波点云');
        if isequal(file,0), return; end
        try
            pcwrite(cloud,fullfile(path,file),'Precision','double');
            msgbox('保存成功!','提示');
        catch ME
            errordlg(ME.message,'保存失败');
        end
    end

    function showPointCloud(ax,pc)
        % 显示点云的统一函数
        cla(ax); set(ax,'Color','w');
        pcshow(pointCloud(nan(0,3)),'Parent',ax);  % 2020a 兼容
        pcshow(pc,'Parent',ax,'MarkerSize',35);
        axis(ax,'tight'); grid(ax,'on'); view(ax,3);
    end

    %% ---------- 自适应引导滤波核心函数 ----------
    function out = pcdGuidedFilter(pc, radius, epsilon)
        % 对输入点云应用自适应引导滤波
        % 输入:
        %   pc        - pointCloud对象,原始点云
        %   radius    - 邻域搜索半径
        %   epsilon   - 正则化参数
        % 输出:
        %   out       - 滤波后的pointCloud对象

        xyz = pc.Location;
        n = size(xyz, 1);
        newXYZ = xyz;  % 初始化新坐标矩阵(默认保留原始点)

        % 创建KD树用于高效邻域搜索
        kd = createns(xyz, 'NSMethod', 'kdtree');

        % 分块处理以优化内存使用
        block_size = 5000;
        for i = 1:block_size:n
            block_end = min(i + block_size - 1, n);
            % 搜索当前块内所有点的邻域
            idxCell = rangesearch(kd, xyz(i:block_end, :), radius);
            
            % 逐个处理块内的点
            for j = i:block_end
                % 获取当前点的邻域索引
                idx = idxCell{j - i + 1};
                
                % 邻域点少于3个时,保留原始点(保护边缘)
                if length(idx) < 3
                    newXYZ(j, :) = xyz(j, :);
                    continue;
                end
                
                % 提取邻域点并计算统计特征
                neighbors = xyz(idx, :);
                mean_neigh = mean(neighbors, 1);    % 邻域均值 (1×3)
                cov_neigh = cov(neighbors);         % 邻域协方差矩阵 (3×3)
                
                % 计算正则化协方差矩阵的逆矩阵
                inv_cov = inv(cov_neigh + epsilon * eye(3));
                
                % 计算引导滤波系数
                A = cov_neigh * inv_cov;            % 系数矩阵A (3×3)
                
                % 计算偏移向量b
                b = mean_neigh' - A * mean_neigh';  % 偏移向量b (3×1)
                
                % 计算滤波后的点坐标
                newXYZ(j, :) = (A * xyz(j, :)' + b)';  % 结果为1×3
            end
        end

        % 重建点云对象,保留颜色和法向量信息
        out = pointCloud(newXYZ, 'Color', pc.Color, 'Normal', pc.Normal);
    end

end

二、点云进行引导滤波的结果

可以看到,通过调节邻域搜索半径和正则化系数,点云引导滤波被很好的完成了。而且结果又快又好,完全不像前面的某些方法(说你呢,坡度滤波)。同学们可以自己尝试下啊!

就酱,下次见^-^

相关推荐
熊猫_豆豆2 小时前
MATLAB 九大行星太阳系运行程序
开发语言·nginx·matlab
程序员三明治2 小时前
二分查找思路详解,包含二分算法的变种,针对不同题的做法
java·数据结构·算法·二分查找
枣伊吕波2 小时前
五十三、bean的管理-bean的获取、bean的作用域、第三方bean
java·开发语言
丁浩6662 小时前
Python---14.Python数据存储EXCEl和MySQL
开发语言·python
Tisfy2 小时前
MacOS - Clang使用bits/stdc++.h - 非官方(竞赛用) - 通用方法
开发语言·c++·macos
xiaoningaijishu3 小时前
MATLAB中的Excel文件操作:从入门到精通
其他·算法·matlab·excel
啦工作呢3 小时前
Sass:CSS 预处理器
开发语言·后端·rust
豆豆·丁3 小时前
kettle 执行java脚本生成SQL
java·开发语言·数据库
吹晚风吧3 小时前
线程安全之《Sychronized的八锁案例》
java·开发语言··sychronized