Matlab通过GUI实现点云的随机(Random)下采样(附最简版)

本次我们分享使用Matlab进行点云的随机下采样。点云随机下采样是一种通过随机选取部分点来减少点云数据量的方法,旨在降低计算复杂度、提高处理效率,同时保留原始点云的整体结构特征。在 MATLAB 中,随机下采样可通过内置函数或自定义代码实现,适用于无需保留局部几何特征的快速预处理场景。

一、处理流程

以下是使用 MATLAB 进行点云随机下采样的标准流程:

  1. 读取点云数据

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

  2. 设置采样比例(如保留 20% 的点)

    percentage = 0.2;

  3. 执行随机下采样

    ptCloudDownsampled = pcdownsample(ptCloud, 'random', percentage);

✅ 说明:`pcdownsample` 是 MATLAB 中专门用于点云下采样的函数,支持多种采样方式,包括 `'random'`、`'gridAverage'` 等。

  1. 可视化对比(可选)

    figure;
    subplot(1,2,1);
    pcshow(ptCloud); title('原始点云');

    subplot(1,2,2);
    pcshow(ptCloudDownsampled); title(['随机下采样 ', num2str(percentage*100), '%']);

二、应用场景

|-----------|-----------------------------------|
| 场景 | 说明 |
| 快速预览与可视化 | 原始点云数据量过大,直接显示卡顿,先随机采样降低点数以便快速查看。 |
| 深度学习预处理 | 在训练点云分类/分割模型前,统一输入规模,减少计算负担。 |
| SLAM 前端处理 | 在实时定位与建图任务中,降低点云密度以加快配准与建图速度。 |
| 数据压缩与传输 | 减少点云数据量,便于存储、传输或上传至云端平台。 |
| 算法测试与原型开发 | 在开发新算法时,使用小规模点云快速验证逻辑,避免处理完整大数据集。 |

三、优缺点总结

|----------------|------------------------|
| 优点 | 缺点 |
| ✅ 实现简单,计算速度快 | ❌ 可能丢失关键几何特征(如边缘、角点) |
| ✅ 不依赖点云结构,适用性广 | ❌ 采样结果不稳定(每次运行结果可能不同) |
| ✅ 可控制保留比例,灵活性强 | ❌ 不适用于对细节要求高的任务(如特征提取) |

四、扩展建议

  • 若需保留几何特征,可结合 体素下采样(`pcdownsample(ptCloud, 'gridAverage', gridStep)`)或 曲率采样。

  • 若需固定输出点数,可自定义随机索引选择逻辑:

    numPoints = 2048;
    indices = randperm(ptCloud.Count, numPoints);
    ptCloudDown = pointCloud(ptCloud.Location(indices, :));

五、结语

点云随机下采样是 MATLAB 中最简单高效的点云精简方法之一,适用于对几何精度要求不高的快速处理场景。在实际应用中,建议根据任务需求组合使用多种采样策略,以平衡效率与精度。如需进一步处理(如去噪、配准、重建),可继续结合 MATLAB 的点云工具箱进行后续分析。

本次数据,大家猜猜,除了窗帘。猜对了,兔砸!!

一、点云随机下采样程序

1、最简版

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

%% 1. 读入点云
% pcdFile = 'bunny.pcd';            % 换成你的完整路径
[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. 随机下采样 50 %
idx50   = randperm(N, round(0.5*N));
cloud50 = select(ptCloud, idx50);
fprintf('随机下采样(50%%) 后 %d 个点\n', cloud50.Count);

%% 3. 随机下采样到固定 5000 点
k       = 5000;
idx5k   = randperm(N, k);
cloud5k = select(ptCloud, idx5k);
fprintf('随机固定 5000 点后 %d 个点\n', cloud5k.Count);

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

figure('Name','随机下采样(50%)','NumberTitle','off');
pcshow(cloud50); axis on; view(3);

figure('Name','随机固定5000点','NumberTitle','off');
pcshow(cloud5k); axis on; view(3);

2、GUI版本

复制代码
function pointCloudGUI_safe
% 2020a 兼容 / 空点云暖启动 / 标题不遮挡 / 双路保存

fig = figure('Name','点云随机下采样','NumberTitle','off',...
             'MenuBar','none','ToolBar','none','Position',[100 100 1280 720]);

%% 左侧三栏 panel(标题栏自带,永不被冲)
imgWidth = 0.78;                 % ← 图像区总宽
panelW   = imgWidth/3 - 0.01;    % 每个 panel 再留 1 % 间隙

pnlOrig  = uipanel('Parent',fig,'Units','normalized',...
                   'Position',[0.02, 0.02, panelW, 0.96],...
                   'FontSize',16,... 
                   'Title','原始点云');   

pnlRatio = uipanel('Parent',fig,'Units','normalized',...
                   'Position',[0.02+panelW+0.01, 0.02, panelW, 0.96],...
                   'FontSize',16,... 
                   'Title','比例采样');

pnlFix   = uipanel('Parent',fig,'Units','normalized',...
                   'Position',[0.02+2*(panelW+0.01), 0.02, panelW, 0.96],...
                   'FontSize',16,... 
                   'Title','固定点数');

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]);

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

uicontrol('Parent',pnlCtrl,'Style','pushbutton','String','浏览...',...
          'Units','normalized','Position',[0.05 0.87 0.90 0.06],...
          'FontSize',16,... 
          'Callback',@loadCloud);
lblInfo = uicontrol('Parent',pnlCtrl,'Style','text','String','未加载点云',...
                    'Units','normalized','Position',[0.05 0.81 0.90 0.04],...
                    'FontSize',10,... 
                    'HorizontalAlignment','left');

uicontrol('Parent',pnlCtrl,'Style','text','String','比例控制 (%)',...
          'Units','normalized','Position',[0.05 0.72 0.90 0.04],...
          'FontSize',12,'FontWeight','bold','HorizontalAlignment','left');
% 比例
sliderRatio = uicontrol('Parent',pnlCtrl,'Style','slider','Min',0,'Max',100,'Value',50,...
                        'FontSize',12,... 
                        'Units','normalized','Position',[0.05 0.68 0.65 0.04],...
                        'Callback',@refreshRatio);
txtRatio    = uicontrol('Parent',pnlCtrl,'Style','edit','String','50',...
                        'FontSize',12,... 
                        'Units','normalized','Position',[0.75 0.68 0.20 0.04],...
                        'Callback',@editRatioCB);

uicontrol('Parent',pnlCtrl,'Style','text','String','个数控制',...
          'Units','normalized','Position',[0.05 0.58 0.90 0.04],...
          'FontSize',12,'FontWeight','bold','HorizontalAlignment','left');
% 固定点数
sliderFix = uicontrol('Parent',pnlCtrl,'Style','slider','Min',1,'Max',100,'Value',50,...
                      'FontSize',12,... 
                      'Units','normalized','Position',[0.05 0.55 0.65 0.04],...
                      'Callback',@refreshFix);
txtFix    = uicontrol('Parent',pnlCtrl,'Style','edit','String','50',...
                      'FontSize',12,... 
                      'Units','normalized','Position',[0.75 0.55 0.20 0.04],...
                      'Callback',@editFixCB);

% 保存
uicontrol('Parent',pnlCtrl,'Style','pushbutton','String','保存比例采样',...
          'Units','normalized','Position',[0.05 0.40 0.90 0.06],...
          'FontSize',16,... 
          'Callback',@(s,e)saveCloud(ptCloudRatio));
uicontrol('Parent',pnlCtrl,'Style','pushbutton','String','保存固定点数',...
          'Units','normalized','Position',[0.05 0.32 0.90 0.06],...
          'FontSize',16,... 
          '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; end
        try
            ptCloudOrig = pcread(fullfile(path,file));
        catch ME
            errordlg(ME.message,'读取失败'); return;
        end
        N = ptCloudOrig.Count;
        set(lblInfo,'String',sprintf('已加载:%s  (%d 点)',file,N));
        set(sliderFix,'Max',N,'Value',min(5000,N));
        set(txtFix,'String',num2str(min(5000,N)));
        safePcshow(axOrig,ptCloudOrig);
        refreshRatio();
        refreshFix();
    end

    function refreshRatio(~,~)
        if isempty(ptCloudOrig), return; end
        ratio = get(sliderRatio,'Value')/100;
        N = ptCloudOrig.Count;
        k = max(1,round(ratio*N));
        idx = randperm(N,k);
        ptCloudRatio = select(ptCloudOrig,idx);
        safePcshow(axRatio,ptCloudRatio);
        set(txtRatio,'String',num2str(round(ratio*100)));
    end

    function editRatioCB(src,~)
        v = str2double(get(src,'String'));
        if isnan(v), v = 50; end
        v = max(0,min(100,v));
        set(sliderRatio,'Value',v);
        refreshRatio();
    end

    function refreshFix(~,~)
        if isempty(ptCloudOrig), return; end
        k = round(get(sliderFix,'Value'));
        N = ptCloudOrig.Count;
        k = max(1,min(N,k));
        idx = randperm(N,k);
        ptCloudFix = select(ptCloudOrig,idx);
        safePcshow(axFix,ptCloudFix);
        set(txtFix,'String',num2str(k));
    end

    function editFixCB(src,~)
        v = str2double(get(src,'String'));
        if isnan(v), v = 100; end
        N = ptCloudOrig.Count;
        v = max(1,min(N,v));
        set(sliderFix,'Value',v);
        refreshFix();
    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

    %% -------- 安全 pcshow(2020a 兼容) --------
    function safePcshow(ax,pc)
        cla(ax);
        set(ax,'Color','w');                      % 白背景
        pcshow(pointCloud(nan(0,3)),'Parent',ax); % ① 暖启动
        pcshow(pc,'Parent',ax,'MarkerSize',15);   % ② 真点云
        axis(ax,'tight'); grid(ax,'on'); view(ax,3);
    end
end

二、点云随机下采样结果

这里只演示了GUI版本的(简单版本有手就行,没必要演示了。。。),功能有比例随机下采样和固定点数随机下采样,而且可以旋转图像观看3D效果。不过中间有点小瑕疵,显示之前需要暖启动下(就是先点一下图像,然后再处理),然后才能旋转。不过总体没有大的问题。(虽然运行显示有错误,但是不影响总体的,程序员第一准则,能跑就行)有兴趣的同学可以多试试发展下新功能。

就酱,下次见^_^

相关推荐
荼蘼3 小时前
OpenCV 人脸检测、微笑检测 原理及案例解析
人工智能·opencv·计算机视觉
一碗白开水一4 小时前
【第30话:路径规划】自动驾驶中Hybrid A星(A*)搜索算法的详细推导及代码示例
人工智能·算法·机器学习·计算机视觉·数学建模·自动驾驶
Dfreedom.4 小时前
随机裁剪 vs. 中心裁剪:深度学习中图像预处理的核心技术解析
图像处理·人工智能·深度学习·计算机视觉
ShowMaker.wins6 小时前
目标检测进化史
人工智能·python·神经网络·目标检测·计算机视觉·自动驾驶·视觉检测
算法打盹中6 小时前
计算机视觉:基于YOLOv11 实例分割与OpenCV 在 Java 中的实现图像实例分割
opencv·yolo·计算机视觉·图像分割·实例分割·yolo11
CLTHREE7 小时前
处理视频抽帧并转换成json
python·opencv·计算机视觉
Monkey的自我迭代9 小时前
光流估计(可用于目标跟踪)
人工智能·opencv·计算机视觉
WeiJingYu.15 小时前
O3.6opencv
人工智能·opencv·计算机视觉
_nirvana_w_17 小时前
PyQt6+OpenCV 实战:打造功能完备的数字图像处理 GUI 系统
人工智能·python·qt·opencv·计算机视觉