本次分享使用Matlab进行点云的统计滤波。点云统计滤波是一种基于邻域统计信息去除离群点、提升数据质量的常用方法,广泛应用于自动驾驶、地形建模、工业检测等领域。下面将从算法原理、实现流程和典型应用三个方面进行系统介绍。
一、算法原理
统计滤波(Statistical Outlier Removal)假设点云中每个点与其邻域点的距离服从高斯分布。通过计算每个点到其最近 *k* 个邻域点的平均距离,判断其是否显著偏离全局统计特性:
若某点的平均邻域距离超出 **均值 + α × 标准差**(α 为阈值系数),则视为离群点并剔除;
该方法能有效去除稀疏分布的噪声点,同时保留点云的几何结构。
二、MATLAB 实现流程
读取点云数据
ptCloud = pcread('input.pcd'); % 支持 .pcd、.ply 等格式
设置滤波参数
numNeighbors = 50; % 邻域点数 k
stdThreshold = 1.0; % 标准差倍数阈值执行统计滤波
filteredPtCloud = pcfilterstatistical(ptCloud, numNeighbors, stdThreshold);
若需保留强度信息,可结合索引手动筛选:
[~, idx] = pcfilterstatistical(ptCloud, numNeighbors, stdThreshold); filteredPtCloud = select(ptCloud, idx); % 同时保留 Location 与 Intensity
可视化对比
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吧!
就酱,下次见^-^