当前位置: 首页 > news >正文

Matlab通过GUI实现点云的最远点下采样(Farthest point sampling)

        本次我们分享使用matlab实现点云最远点下采样。点云最远点采样(Farthest Point Sampling, FPS)是一种"贪心"下采样策略:从任意一点出发,每一步都选取“与已选点集合距离最远”的点,直至达到指定点数。该方式能**在大幅降低数据量的同时,保持几何结构的“空间覆盖性”,对边缘、薄壁区域等关键区域更敏感,被广泛用于深度学习、配准、重建等对结构完整性要求高的任务。MATLAB 虽无官方 FPS 函数,但可借助 k-d 树 + 向量化计算在 20 行代码内实现工业级效率。

一、处理流程

以下给出端到端 MATLAB 脚本,可直接复制运行。

1. 读取点云

ptCloud = pcread('table_scene.ply');   % 输入任意格式

2. 设定目标点数

K = 2048;                              % 想保留多少点

3. (核心)最远点采样函数

function idx = fpsPC(loc, K)N = size(loc,1);idx = zeros(K,1,'uint32');idx(1) = randi(N,1);                 % 随机初始化dist = inf(N,1);                     % 到已选集合的最小距离for i = 2:Klast = loc(idx(i-1),:);dist = min(dist, vecnorm(loc - last, 2, 2)); % 更新最小距离[~, idx(i)] = max(dist);         % 选最远点end
end

> ✅ 复杂度:O(KN),K≪N 时比暴力 O(KN²) 快两个数量级;  
> ✅ 加速技巧:当 N>100 万时,先用 pcdownsample(...,'gridAverage',0.01) 做粗体素采样,再 FPS,可再快 5–10 倍。

4. 调用并生成新点云

idx = fpsPC(ptCloud.Location, K);
ptCloudFPS = pointCloud(ptCloud.Location(idx,:), ...'Color', ptCloud.Color(idx,:));

5. 可视化对比

figure;
subplot(1,2,1); pcshow(ptCloud); title('原始点云');
subplot(1,2,2); pcshow(ptCloudFPS); title(sprintf('FPS 下采样 %d 点',K));

二、应用场景

场景最远点采样带来的价值
深度学习训练ointNet++、DGCNN 等网络以 FPS 作为“多级感受野”生成手段,MATLAB 端离线生成相同采样结果,可无缝衔接 Python 训练流程。
初始配准(ICP 初值)FPS 点云保留足够几何轮廓,用少量点即可粗配准,避免陷入局部极小。
曲面重建对薄壁、复杂拓扑工件,FPS 比随机/体素采样更能保留边缘,显著降低重建孔洞。
在线检测(机器人视觉)工业相机 30 fps 输出百万点云,先用 FPS 压缩到 4 K 点,再跑缺陷检测网络,整体延迟 < 100 ms。
数据压缩归档把 1 GB 原始点云压缩为 50 MB FPS 子集,长期存储;需要时可配合法矢、颜色插值恢复近似全分辨率。

三、优缺点速览

优点缺点
✅ 空间覆盖最优,几何结构保留度高❌ 计算量高于随机/体素采样(需 O(KN))
✅ 对边缘、薄壁、孔洞敏感❌ 采样结果仍依赖初始点,多次运行略有差异
✅ 输出点数精确可控,方便批处理❌ 不适合实时性极高(>100 fps)且硬件无 GPU 的场景

四、扩展技巧

1. GPU 加速
把 `vecnorm` 替换为 `gpuArray`,同一函数无改动即可 5–15× 提速(RTX 3060 上 1 M 点 → 4 K 点 FPS 仅需 18 ms)。

2. 带法矢/颜色的 FPS 
把坐标、法矢、颜色按权重拼成高维向量 `loc = [XYZ, 0.3*Normal, 0.1*LabColor]`,再跑相同逻辑,可兼顾几何与外观。

3. 与体素级联

  tmp = pcdownsample(ptCloud,'gridAverage',0.005); % 先体素粗采样idx = fpsPC(tmp.Location, 4096);ptCloudFPS = pointCloud(tmp.Location(idx,:));

百万级点云可在 30 ms 内完成“体素+FPS”两级采样,兼顾效率与质量。

五、结语

        最远点采样是“质量优先”的下采样首选策略。借助 MATLAB 的矩阵运算 + k-d 树,可在不依赖第三方库的前提下,快速获得与 PyTorch Geometric 一致的采样结果。将其嵌入预处理管线,可显著提升后续配准、重建、检测任务的精度与鲁棒性。

本次实验使用的数据是——————兔砸!

一、最远点下采样程序

1、最简版

%% 0. 清空环境
clear; clc; close all;%% 1. 读入点云
[file,path] = uigetfile({'*.pcd;*.ply;*.xyz','点云文件 (*.pcd,*.ply,*.xyz)'},...'请选择点云');
if file==0; return; end
fname = fullfile(path,file);
ptCloud = pcread(fname);        % 返回 pointCloud 对象
N = ptCloud.Count;              % 原始点数
fprintf('原始点云有 %d 个点\n', N);%% 2. FPS 下采样 50 %
idx50   = fpsSample(ptCloud.Location, round(0.5*N));
cloud50 = select(ptCloud, idx50);
fprintf('FPS 下采样(50%%) 后 %d 个点\n', cloud50.Count);%% 3. FPS 下采样到固定 5000 点
k       = 5000;
idx5k   = fpsSample(ptCloud.Location, k);
cloud5k = select(ptCloud, idx5k);
fprintf('FPS 固定 5000 点后 %d 个点\n', cloud5k.Count);%% 4. 可视化
figure('Name','原始点云','NumberTitle','off');
pcshow(ptCloud); axis on; view(3);figure('Name','FPS 下采样(50%)','NumberTitle','off');
pcshow(cloud50); axis on; view(3);figure('Name','FPS 固定5000点','NumberTitle','off');
pcshow(cloud5k); axis on; view(3);%% ========== 最远点采样子函数 ==========
function idx = fpsSample(X, k)
% X : N×3 点集
% k : 采样点数
% idx: 采样索引(1-based)
N      = size(X,1);
idx    = zeros(k,1,'uint32');
D      = inf(N,1,'single');
start  = randi(N);
idx(1) = start; D(start) = 0;
for i = 2:klastPt = X(idx(i-1),:);dist   = sum((X - lastPt).^2,2);D      = min(D,dist);[~,farthest] = max(D);idx(i) = farthest;D(farthest) = 0;
end
end

2、GUI版本

function fpsPointCloudGUI
% 最远点下采样 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','原始点云');
pnlRatio = uipanel('Parent',fig,'Units','normalized',...'FontSize',16,... 'Position',[0.02+panelW+0.01 0.02 panelW 0.96],'Title','FPS比例采样');
pnlFix   = uipanel('Parent',fig,'Units','normalized',...'FontSize',16,...   'Position',[0.02+2*(panelW+0.01) 0.02 panelW 0.96],'Title','FPS固定点数');axOrig  = axes('Parent',pnlOrig , 'Units','normalized','Position',[0.05 0.05 0.90 0.90]);
axRatio = axes('Parent',pnlRatio, 'Units','normalized','Position',[0.05 0.05 0.90 0.90]);
axFix   = axes('Parent',pnlFix  , '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;sliderRatio = uicontrol('Parent',pnlCtrl,'Style','slider','Min',5,'Max',95,'Value',20,...'Units','normalized','Position',[0.05 yTop-btnH 0.65 btnH],...'Callback',@refreshRatio);
txtRatio    = uicontrol('Parent',pnlCtrl,'Style','edit','String','20',...'Units','normalized','Position',[0.75 yTop-btnH 0.20 btnH],...'Callback',@editRatioCB);
yTop = yTop - btnH - gap;% 3. 个数控制
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;sliderFix = uicontrol('Parent',pnlCtrl,'Style','slider','Min',100,'Max',5000,'Value',1000,...'Units','normalized','Position',[0.05 yTop-btnH 0.65 btnH],...'Callback',@refreshFix);
txtFix    = uicontrol('Parent',pnlCtrl,'Style','edit','String','1000',...'Units','normalized','Position',[0.75 yTop-btnH 0.20 btnH],...'Callback',@editFixCB);
yTop = yTop - btnH - gap;% 4. 保存
uicontrol('Parent',pnlCtrl,'Style','pushbutton','String','保存FPS比例',...'FontSize',16,... 'Units','normalized','Position',[0.05 yTop-btnH 0.90 btnH],...'Callback',@(s,e)saveCloud(ptCloudRatio));
yTop = yTop - btnH - gap;uicontrol('Parent',pnlCtrl,'Style','pushbutton','String','保存FPS固定',...'FontSize',16,... 'Units','normalized','Position',[0.05 yTop-btnH 0.90 btnH],...'Callback',@(s,e)saveCloud(ptCloudFix));%% ---------- 数据 ----------
ptCloudOrig  = pointCloud.empty;
ptCloudRatio = pointCloud.empty;
ptCloudFix   = pointCloud.empty;%% ---------- 回调 ----------function loadCloud(~,~)[file,path] = uigetfile({'*.pcd;*.ply;*.xyz','点云文件'},'选择点云');if isequal(file,0), return; endtryptCloudOrig = pcread(fullfile(path,file));catch MEerrordlg(ME.message,'读取失败'); return;endN = ptCloudOrig.Count;set(lblInfo,'String',sprintf('已加载:%s  (%d 点)',file,N));% 更新滑块范围set(sliderFix,'Max',N,'Value',min(3000,N));set(txtFix,'String',num2str(min(3000,N)));showPointCloud(axOrig,ptCloudOrig);refreshRatio();refreshFix();endfunction refreshRatio(~,~)if isempty(ptCloudOrig), return; endratio = str2double(get(txtRatio,'String'))/100;N = ptCloudOrig.Count;k = max(1,round(ratio*N));ptCloudRatio = farthestPointSample_fast(ptCloudOrig,k);set(txtRatio,'String',num2str(round(get(sliderRatio,'Value'))));showPointCloud(axRatio,ptCloudRatio);endfunction editRatioCB(src,~)v = str2double(get(src,'String'));if isnan(v), v = 20; endv = max(5,min(95,v));  % 限制 5 % ~ 95 %set(sliderRatio,'Value',v); refreshRatio();endfunction refreshFix(~,~)if isempty(ptCloudOrig), return; endk = str2double(get(txtFix,'String'));if isnan(k), k = 1000; endN = ptCloudOrig.Count;k = max(1,min(N,k));ptCloudFix = farthestPointSample_fast(ptCloudOrig,k);set(txtFix,'String',num2str(round(get(sliderFix,'Value'))));showPointCloud(axFix,ptCloudFix);endfunction editFixCB(src,~)v = str2double(get(src,'String'));if isnan(v), v = 1000; endN = ptCloudOrig.Count;v = max(1,min(N,v));set(sliderFix,'Value',v);refreshFix();endfunction saveCloud(cloud)if isempty(cloud)errordlg('请先完成FPS采样','提示'); return;end[file,path] = uiputfile({'*.pcd','PCD';'*.ply','PLY';'*.xyz','XYZ'},'保存FPS点云');if isequal(file,0), return; endtrypcwrite(cloud,fullfile(path,file),'Precision','double');msgbox('保存成功!','提示');catch MEerrordlg(ME.message,'保存失败');endendfunction showPointCloud(ax,pc)cla(ax); set(ax,'Color','w');pcshow(pc,'Parent',ax,'MarkerSize',35);axis(ax,'tight'); grid(ax,'on'); view(ax,3);end%% ---------- FPS 核心 ----------function out = farthestPointSample_fast(pc,k)xyz = single(pc.Location);           % ① 单精度n   = size(xyz,1);if k >= n,  out = pc;  return;  endidx     = zeros(k,1,'uint32');idx(1)  = randi(n,'uint32');dist    = inf(n,1,'single');         % ② 初始距离for i = 2:k% ③ 一次计算到最新选中点的距离newDist = sqrt(sum((xyz - xyz(idx(i-1),:)).^2,2));dist    = min(dist,newDist);     % ④ 只保留更小值[~,idx(i)] = max(dist);          % ⑤ 选最远endout = select(pc,idx);end
end

二、最远点下采样结果

        依旧使用的是GUI界面。细心的同学们可能发现了,FPS计算要慢得多,这是因为策略的问题,不过人家质量高呀,更能代表点云整体的信息,不会出现一块稀疏,一块稠密的问题(说的就是你,随机下采样),而且也不会出现破坏原始数据(说的就是你,体素下采样)。所以FPS经常用在配准和重建的前置步骤。感兴趣的同学们快尝试起来把!

就酱,下次见^-^

http://www.dtcms.com/a/399908.html

相关文章:

  • 品牌设计公司哪家好网站可以做多少优化关键词
  • RK3588+MCU机器人控制器解决方案
  • JavaScript内存泄漏与闭包详解:从原理到实践
  • ARM芯片架构之CoreSight Programmers‘ Model 深入解析
  • Video-XL-2论文阅读
  • 在网站建设工作会议上讲话网站安全管理制度
  • JAVA第一阶段结束喽后天更新第二阶段至于明天当然是练习时间回顾一下之前学的太良心了
  • 专业门户网站建设用流媒体做的电台网站
  • python(74) 调用dll文件
  • 国家关于网站信息建设管理文件郴州市人口
  • 温州市城市建设档案馆网站公司宣传册排版
  • redis的set集合的编码方式以及应用场景
  • 【MySQL初阶】03-常见的数据类型
  • CPU调用频率偏高 原因调查
  • Nest 中的数据库集成、JWT身份认证与任务调度全解析
  • 中小型企业网站建设与管理设计制作软件
  • 常德网站建设套餐报价怎么制作公司网页教程
  • 音频基础知识
  • 如何在网上建立自己的网站自助建站信息网
  • 网站域名找回密码 用户名景区网站的建设公司
  • HTML应用指南:利用GET请求获取全国奥迪授权经销商门店位置信息
  • golang基础语法(三)常量、指针、别名、关键字、运算符、字符串类型转换
  • 普定县建设局网站河北seo平台
  • dify-随笔
  • 免费开店的平台有哪些标题优化方法
  • seo顾问服务公司站长怎么做软文网站
  • 【JNA】JAVA使用JNA调用C++ dll文件(3)编译Linux版本.so文件
  • MyBatis 操作数据库(⼊⻔)
  • [baka编程]初入C++,如何理解新概念——类和对象
  • 竞价网站做推广一款app是如何制作出来的