Matlab通过GUI实现点云的统计滤波(附最简版)

本次分享使用Matlab进行点云的统计滤波。点云统计滤波是一种基于邻域统计信息去除离群点、提升数据质量的常用方法,广泛应用于自动驾驶、地形建模、工业检测等领域。下面将从算法原理、实现流程和典型应用三个方面进行系统介绍。

一、算法原理

统计滤波(Statistical Outlier Removal)假设点云中每个点与其邻域点的距离服从高斯分布。通过计算每个点到其最近 *k* 个邻域点的平均距离,判断其是否显著偏离全局统计特性:

  • 若某点的平均邻域距离超出 **均值 + α × 标准差**(α 为阈值系数),则视为离群点并剔除;

  • 该方法能有效去除稀疏分布的噪声点,同时保留点云的几何结构。

二、MATLAB 实现流程

  1. 读取点云数据

    ptCloud = pcread('input.pcd'); % 支持 .pcd、.ply 等格式

  2. 设置滤波参数

    numNeighbors = 50; % 邻域点数 k
    stdThreshold = 1.0; % 标准差倍数阈值

  3. 执行统计滤波

    filteredPtCloud = pcfilterstatistical(ptCloud, numNeighbors, stdThreshold);

若需保留强度信息,可结合索引手动筛选:

复制代码
[~, idx] = pcfilterstatistical(ptCloud, numNeighbors, stdThreshold);
filteredPtCloud = select(ptCloud, idx);  % 同时保留 Location 与 Intensity
  1. 可视化对比

    figure;
    subplot(1,2,1); pcshow(ptCloud); title('原始点云');
    subplot(1,2,2); pcshow(filteredPtCloud); title('统计滤波后');

三、应用领域

|--------|---------------------------|
| 领域 | 应用示例 |
| 自动驾驶 | 去除车载激光雷达采集中的飘移点,提升障碍物检测精度 |
| 地形测绘 | 滤除航空点云中的孤立噪声,保留地面与建筑物边缘特征 |
| 工业检测 | 清除扫描模型中的离群点,提高后续配准、建模的鲁棒性 |
| 文物保护 | 优化文物三维扫描数据,保留细节的同时消除环境噪声 |

四、小结

统计滤波以其原理简单、计算高效、结构保持性好的优势,成为 MATLAB 点云预处理的首选方法之一。结合 KD 树加速邻域搜索,可进一步提升处理效率。在实际应用中,建议与半径滤波、直通滤波等方法组合使用,以适应不同场景需求。如需进一步处理,可引入双边滤波或移动最小二乘(MLS)平滑以提升表面质量。

本次使用的数据是------------------兔砸!

一、点云统计滤波程序

1、最简版

复制代码
clc; clear; close all;

%% 1. 读入点云
plyFile = 'E:\CSDN\规则点云\实拍\窗帘.ply';   % <-- 换成你的实际路径
ptCloud = pcread(plyFile);                    % MATLAB 内置函数
fprintf('原始点云有 %d 个点\n', ptCloud.Count);

%% 2. 统计离群滤波
% 参数与 Open3D 对应:
%   nb_neighbors = 100
%   std_ratio    = 2.0
% pcdenoise 的 'NumNeighbors' 就是 K,'Threshold' 是倍数 σ
filteredPtCloud = pcdenoise(ptCloud, ...
                            'NumNeighbors', 100, ...
                            'Threshold', 2.0);   % 2*sigma
fprintf('统计滤波有 %d 个点\n', filteredPtCloud.Count);

%% 3. 可视化
figure('Name','原始点云','Position',[50 50 1200 800]);
pcshow(ptCloud);
title('原始点云');

figure('Name','统计滤波','Position',[50 50 1200 800]);
pcshow(filteredPtCloud);
title('统计滤波');

2、GUI版本

复制代码
function statFilterGUI
% 统计滤波 GUI ------ 2020a 兼容
% 1. 浏览选点云  2. 邻域数滑块  3. std 滑块  4. 滤波结果保存

fig = figure('Name','统计滤波工具','NumberTitle','off',...
             'MenuBar','none','ToolBar','none','Position',[100 100 1280 720]);

%% ---------- 左侧图像区(78 %) ----------
imgWidth = 0.78;
panelW   = imgWidth/2 - 0.01;   % 只放两图:原始 + 滤波

pnlOrig = uipanel('Parent',fig,'Units','normalized',...
                  'FontSize',16,... 
                  'Position',[0.02 0.02 panelW 0.96],'Title','原始点云');
pnlFilt = uipanel('Parent',fig,'Units','normalized',...
                  'FontSize',16,... 
                  'Position',[0.02+panelW+0.01 0.02 panelW 0.96],'Title','统计滤波');

axOrig = axes('Parent',pnlOrig,'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;

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

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

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

sliderStd = uicontrol('Parent',pnlCtrl,'Style','slider','Min',0.5,'Max',3.0,'Value',2.0,...
                      'FontSize',16,... 
                      'Units','normalized','Position',[0.05 yTop-btnH 0.65 btnH],...
                      'Callback',@refreshFilter);
txtStd    = uicontrol('Parent',pnlCtrl,'Style','edit','String','2.0',...
                      'FontSize',16,... 
                      'Units','normalized','Position',[0.75 yTop-btnH 0.20 btnH],...
                      'Callback',@editStdCB);
yTop = yTop - btnH - gap;

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

%% ---------- 数据 ----------
ptCloudOrig = pointCloud.empty;
ptCloudFilt = pointCloud.empty;

    %% ---------- 回调 ----------
    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
        showPointCloud(axOrig,ptCloudOrig);  % ← 必须显式给句柄
        N = ptCloudOrig.Count;
        set(lblInfo,'String',sprintf('已加载:%s  (%d 点)',file,N));
        refreshFilter();
    end

    function refreshFilter(~,~)
        if isempty(ptCloudOrig), return; end
        k   = round(get(sliderK,'Value'));
        std = get(sliderStd,'Value');
        ptCloudFilt = pcdStatFilter(ptCloudOrig,k,std);
        showPointCloud(axFilt,ptCloudFilt);
        set(txtK,'String',num2str(k));
        set(txtStd,'String',num2str(std));
    end

    function editKCB(src,~)
        v = str2double(get(src,'String'));
        if isnan(v), v = 50; end
        v = max(10,min(200,round(v)));
        set(sliderK,'Value',v);
        refreshFilter();
    end

    function editStdCB(src,~)
        v = str2double(get(src,'String'));
        if isnan(v), v = 2.0; end
        v = max(0.5,min(3.0,v));
        set(sliderStd,'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 暖启动(防 rotateFromCenter)
        pcshow(pc,'Parent',ax,'MarkerSize',35);
        axis(ax,'tight'); grid(ax,'on'); view(ax,3);
    end

    %% ---------- 统计滤波核心 ----------
    function out = pcdStatFilter(pc,k,stdRatio)
    xyz = pc.Location;
    n   = size(xyz,1);

    % 1. 找 k 邻域(去掉自己)
    [idx,~] = knnsearch(xyz,xyz,'K',k+1);
    idx = idx(:,2:end);          % n×k

    % 2. 逐点计算到邻域的平均距离
    dist = zeros(n,1);
    for i = 1:n
        dist(i) = mean(sqrt(sum((xyz(i,:) - xyz(idx(i,:),:)).^2,2)));
    end

    % 3. 统计门限
    mu  = mean(dist);
    sig = std(dist);
    th  = mu + stdRatio * sig;

    % 4. 提取内点
    out = select(pc,find(dist <= th));
    end
end

二、点云统计滤波结果

可以看到,本次使用的依旧是GUI画面,确实好用啊,统计滤波的邻域和倍数信息也可以自由的调节,总体功能实现完整。有兴趣的同学自己play吧!

就酱,下次见^-^

相关推荐
代码村新手2 小时前
C语言-操作符
开发语言·c++
风已经起了2 小时前
FPGA学习笔记——图像锐化之Sobel算子
图像处理·笔记·学习·fpga开发·fpga
天天进步20153 小时前
Python项目--交互式VR教育应用开发
开发语言·python·vr
啃啃大瓜3 小时前
字典 dictionary
开发语言·python
无所事事的海绵宝宝4 小时前
使用python+flask设置挡板
开发语言·python·flask
koddnty4 小时前
数据结构:字符串匹配 kmp算法
算法
-一杯为品-4 小时前
【足式机器人算法】#1 强化学习基础
算法·机器人
A.A呐4 小时前
【QT第一章】QT基础知识
开发语言·c++·qt
叫我詹躲躲4 小时前
🌟 回溯算法原来这么简单:10道经典题,一看就明白!
前端·算法·leetcode