点云体素下采样 ❤️(体素质心 | 体素中心)
文章目錄
- 1 體素質心下采樣
- 1.1 原理
- 1.2 體素質心的計算
- 1.2 代碼實現
- 1.3 與 PCL:VoxelGrid 點云體素下采樣結果對比
- 2 體素中心下采樣
- 2.1 原理
- 2.2 體素中心的計算
- 2.2 代碼實現
- 2.3 與體素質心下采樣結果對比
- 3 總結
1 體素質心下采樣
1.1 原理
體素質心下采樣的原理很簡單,首先將點云進行 體素劃分,然后計算非空體素的質心代替該體素內的所有點,實現點云的下采樣。
1.2 體素質心的計算
已知一個非空體素 V V V,其中包含 m m m 個點,則該體素的質心 P c e n t r o i d ( x c e n t r o i d , y c e n t r o i d , z c e n t r o i d ) P_{centroid}(x_{centroid},y_{centroid},z_{centroid}) Pcentroid?(xcentroid?,ycentroid?,zcentroid?) 為:
{ x c e n t r o i d = ∑ i = 1 m x i m y c e n t r o i d = ∑ i = 1 m y i m z c e n t r o i d = ∑ i = 1 m z i m \begin{cases} x_{centroid}=\cfrac{\sum_{i=1}^{m}{x_i}}{m}\\ y_{centroid}=\cfrac{\sum_{i=1}^{m}{y_i}}{m}\\ z_{centroid}=\cfrac{\sum_{i=1}^{m}{z_i}}{m}\\ \end{cases} ??????????????xcentroid?=m∑i=1m?xi??ycentroid?=m∑i=1m?yi??zcentroid?=m∑i=1m?zi???
1.2 代碼實現
代碼分為三部分:
- main.cpp
- voxel_centroid_down_sampling.h
- voxel_centroid_down_sampling.cpp
main.cpp
#include "voxel_centroid_down_sampling.h" #include <pcl/console/time.h>int main() {//----------------------------- 加載點云 -----------------------------pcl::PointCloud<PointT>::Ptr cloud_in(new pcl::PointCloud<PointT>);if (pcl::io::loadPCDFile("grid.pcd", *cloud_in) < 0){PCL_ERROR("/a->點云文件不存在!/n");system("pause");abort();}cout << "->共加載 " << cloud_in->points.size() << " 個數據點" << endl;//===================================================================pcl::console::TicToc time;time.tic();//------------------------- 三維體素格網化點云 ------------------------pcl::PointCloud<PointT>::Ptr cloud_downSample(new pcl::PointCloud<PointT>);VoxelCentriodDownSample vcds; //創建三維體素格網化對象vcds.setInputCloud(cloud_in); //設置輸入點云vcds.setGridStep(0.2); //設置體素格網邊長vcds.downSample(cloud_downSample); //執行體素質心下采樣,并將采樣結果保存到cloud_downSample中cout << "->體素質心下采樣用時:" << time.toc() / 1000 << " s" << endl;//===================================================================//------------------------- 保存體素質心下采樣點云 ------------------------if (!cloud_downSample->empty()){pcl::io::savePCDFileASCII("downSample_centroid.pcd", *cloud_downSample);cout << "->下采樣點云的點數為:" << cloud_downSample->points.size() << endl;}else{PCL_ERROR("\a->下采樣點云為空!\n");system("pause");}//===================================================================return 0; }輸出結果:
->共加載 5746 個數據點 ->體素質心下采樣用時:0.243 s ->下采樣點云的點數為:856voxel_centroid_down_sampling.h
#pragma once #include <pcl/io/pcd_io.h> #include <pcl/common/common.h>using namespace std;typedef pcl::PointXYZ PointT;//體素質心下采樣類 class VoxelCentriodDownSample { public:/*** @brief :設置輸入點云* @param[I]:cloud_in(輸入點云)* @param[O]:none* @return : none* @note :**/void setInputCloud(pcl::PointCloud<PointT>::Ptr cloud_in);/*** @brief :設置體素格網邊長* @param[I]:step(體素格網邊長)* @param[O]:none* @return : none* @note :**/void setGridStep(double step);/*** @brief :體素下采樣* @param[I]:none* @param[O]:downSampleCloud(下采樣點云)* @return : none* @note :**/void downSample(pcl::PointCloud<PointT>::Ptr downSampleCloud);private:pcl::PointCloud<PointT>::Ptr m_cloud_in; //輸入點云bool is_setInputCloud = false;double m_step; //格網邊長bool is_setGridStep = false;int m_row; //格網總行數int m_col; //格網總列數int m_lay; //格網總層數};voxel_centroid_down_sampling.cpp
#include "voxel_centroid_down_sampling.h"/** * @brief :設置輸入點云 * @param[I]:cloud_in(輸入點云) * @param[O]:none * @return : none * @note : **/ void VoxelCentriodDownSample::setInputCloud(pcl::PointCloud<PointT>::Ptr cloud_in) {m_cloud_in = cloud_in;is_setInputCloud = true; }/** * @brief :設置體素格網邊長 * @param[I]:step(體素格網邊長) * @param[O]:none * @return : none * @note : **/ void VoxelCentriodDownSample::setGridStep(double step) {if (step > 0){m_step = step;is_setGridStep = true;}else{PCL_ERROR("\a->格網邊長應為正數!\n");system("pause");abort();} }/** * @brief :體素質心下采樣 * @param[I]:none * @param[O]:downSampleCloud(下采樣點云) * @return : none * @note : **/ void VoxelCentriodDownSample::downSample(pcl::PointCloud<PointT>::Ptr downSampleCloud) {if (!is_setGridStep){PCL_ERROR("\a->請先設置格網邊長!\n");system("pause");abort();}pcl::PointXYZ min;pcl::PointXYZ max;pcl::getMinMax3D(*m_cloud_in, min, max);m_row = (int)((max.y - min.y) / m_step) + 1;m_col = (int)((max.x - min.x) / m_step) + 1;m_lay = (int)((max.z - min.z) / m_step) + 1;int row_i; //每一點的行號,從1開始int col_i; //每一點的列號,從1開始int lay_i; //每一點的層號,從1開始int grid_id_pt; //逐行對應的一維格網編號id,從1開始multimap<int, PointT> m_grid3D; //存放水平面格網點云的容器//遍歷點云,進行三維體素格網化size_t num_cp = m_cloud_in->points.size();for (size_t i = 0; i < num_cp; i++){row_i = (int)((m_cloud_in->points[i].y - min.y) / m_step) + 1; //每一點的行號,從1開始col_i = (int)((m_cloud_in->points[i].x - min.x) / m_step) + 1; //每一點的列號,從1開始lay_i = (int)((m_cloud_in->points[i].z - min.z) / m_step) + 1;//每一點的列號,從1開始grid_id_pt = (lay_i - 1) * (m_row * m_col) + (row_i - 1) * m_col + col_i; //格網一維索引,從1開始m_grid3D.insert(pair<int, PointT>(grid_id_pt, m_cloud_in->points[i])); //將每一個id對應的點坐標存入容器grids2D中}//判斷體素是否為空,若非空,則計算體素內點云質心,以質心代替該體素內的所有點for (int lay = 1; lay <= m_lay; lay++) //層掃描{for (int row = 1; row <= m_row; row++) //行掃描{for (int col = 1; col <= m_col; col++) //列掃描{int grid_id; //逐行對應的一維格網編號id,從1開始grid_id = (lay - 1) * (m_row * m_col) + (row - 1) * m_col + col;if (m_grid3D.count(grid_id)) //若體素格網內有點,則計算體素質心{float sum_x, sum_y, sum_z;sum_x = sum_y = sum_z = 0;auto range = m_grid3D.equal_range(grid_id);for(auto it = range.first;it!=range.second; ++it){sum_x += (*it).second.x;sum_y += (*it).second.y;sum_z += (*it).second.z;}PointT temp; //臨時存放體素質心temp.x = sum_x / m_grid3D.count(grid_id);temp.y = sum_y / m_grid3D.count(grid_id);temp.z = sum_z / m_grid3D.count(grid_id);downSampleCloud->push_back(temp);}}}} }1.3 與 PCL:VoxelGrid 點云體素下采樣結果對比
PCL提供了 VoxelGrid點云體素下采樣 的接口,直接調用即可,輸出結果如下:
->加載了 5746 個數據點 ->正在進行體素下采樣... ->體素下采樣用時:0.058 s ->下采樣點云的點數為:836 下采樣結果對比| 方法 | 用時 | 點數 |
| 本文方法 | 0.243s | 856 |
| PCL:VoxelGrid | 0.058s | 836 |
| 原始點云 | 本文下采樣點云 | PCL下采樣點云 |
2 體素中心下采樣
2.1 原理
與體素質心下采樣類似,首先將點云進行 體素劃分,然后計算非空體素的中心代替該體素內的所有點,實現點云的下采樣。
2.2 體素中心的計算
已知一個點的行列層數 ( r o w , c o l , l a y ) (row,col,lay) (row,col,lay),體素邊長 s t e p step step,坐標最小值 x m i n 、 y m i n 、 z m i n x_{min}、y_{min}、z_{min} xmin?、ymin?、zmin?, 則該點所在體素的中心 P c e n t e r ( x c e n t e r , y c e n t e r , z c e n t e r ) P_{center}(x_{center},y_{center},z_{center}) Pcenter?(xcenter?,ycenter?,zcenter?) 為:
{ x c e n t e r = x m i n + ( c o l ? 0.5 ) ? s t e p y c e n t e r = y m i n + ( r o w ? 0.5 ) ? s t e p z c e n t e r = z m i n + ( l a y ? 0.5 ) ? s t e p \begin{cases} x_{center}=x_{min}+(col-0.5)*step\\ y_{center}=y_{min}+(row-0.5)*step\\ z_{center}=z_{min}+(lay-0.5)*step\\ \end{cases} ??????xcenter?=xmin?+(col?0.5)?stepycenter?=ymin?+(row?0.5)?stepzcenter?=zmin?+(lay?0.5)?step?
2.2 代碼實現
代碼分為三部分:
- main.cpp
- voxel_center_down_sampling.h
- voxel_center_down_sampling.cpp
main.cpp
#include "voxel_center_down_sampling.h" #include <pcl/console/time.h>int main() {//----------------------------- 加載點云 -----------------------------pcl::PointCloud<PointT>::Ptr cloud_in(new pcl::PointCloud<PointT>);if (pcl::io::loadPCDFile("grid.pcd", *cloud_in) < 0){PCL_ERROR("/a->點云文件不存在!/n");system("pause");abort();}cout << "->共加載 " << cloud_in->points.size() << " 個數據點" << endl;//===================================================================pcl::console::TicToc time;time.tic();//------------------------- 體素中心下采樣點云 ------------------------pcl::PointCloud<PointT>::Ptr cloud_downSample(new pcl::PointCloud<PointT>);VoxelCenterDownSample vcds;vcds.setInputCloud(cloud_in);vcds.setGridStep(0.2);vcds.downSample(cloud_downSample);cout << "->體素中心下采樣用時:" << time.toc() / 1000 << " s" << endl;//===================================================================//------------------------- 保存體素下采樣點云 ------------------------if (!cloud_downSample->empty()){pcl::io::savePCDFileASCII("downSample_center.pcd", *cloud_downSample);cout << "->下采樣點云的點數為:" << cloud_downSample->points.size() << endl;}else{PCL_ERROR("\a->下采樣點云為空!\n");system("pause");}//===================================================================return 0; }輸出結果:
->共加載 5746 個數據點 ->體素中心下采樣用時:0.201 s ->下采樣點云的點數為:856voxel_center_down_sampling.h
#pragma once #include <pcl/io/pcd_io.h> #include <pcl/common/common.h>using namespace std;typedef pcl::PointXYZ PointT;//體素中心下采樣類 class VoxelCenterDownSample { public:/*** @brief :設置輸入點云* @param[I]:cloud_in(輸入點云)* @param[O]:none* @return : none* @note :**/void setInputCloud(pcl::PointCloud<PointT>::Ptr cloud_in);/*** @brief :設置體素格網邊長* @param[I]:step(體素格網邊長)* @param[O]:none* @return : none* @note :**/void setGridStep(double step);/*** @brief :體素中心下采樣* @param[I]:none* @param[O]:downSampleCloud(下采樣點云)* @return : none* @note :**/void downSample(pcl::PointCloud<PointT>::Ptr downSampleCloud);private:pcl::PointCloud<PointT>::Ptr m_cloud_in; //輸入點云bool is_setInputCloud = false;double m_step; //格網邊長bool is_setGridStep = false;int m_row; //格網總行數int m_col; //格網總列數int m_lay; //格網總層數};voxel_center_down_sampling.cpp
#include "voxel_center_down_sampling.h"/** * @brief :設置輸入點云 * @param[I]:cloud_in(輸入點云) * @param[O]:none * @return : none * @note : **/ void VoxelCenterDownSample::setInputCloud(pcl::PointCloud<PointT>::Ptr cloud_in) {m_cloud_in = cloud_in;is_setInputCloud = true; }/** * @brief :設置體素格網邊長 * @param[I]:step(體素格網邊長) * @param[O]:none * @return : none * @note : **/ void VoxelCenterDownSample::setGridStep(double step) {if (step > 0){m_step = step;is_setGridStep = true;}else{PCL_ERROR("\a->格網邊長應為正數!\n");system("pause");abort();} }/** * @brief :體素中心下采樣 * @param[I]:none * @param[O]:downSampleCloud(下采樣點云) * @return : none * @note : **/ void VoxelCenterDownSample::downSample(pcl::PointCloud<PointT>::Ptr downSampleCloud) {if (!is_setGridStep){PCL_ERROR("\a->請先設置格網邊長!\n");system("pause");abort();}pcl::PointXYZ min;pcl::PointXYZ max;pcl::getMinMax3D(*m_cloud_in, min, max);m_row = (int)((max.y - min.y) / m_step) + 1;m_col = (int)((max.x - min.x) / m_step) + 1;m_lay = (int)((max.z - min.z) / m_step) + 1;int row_i; //每一點的行號,從1開始int col_i; //每一點的列號,從1開始int lay_i; //每一點的層號,從1開始int grid_id_pt; //逐行對應的一維格網編號id,從1開始multimap<int, PointT> m_grid3D; //存放水平面格網點云的容器//遍歷點云,進行三維體素格網化size_t num_cp = m_cloud_in->points.size();for (size_t i = 0; i < num_cp; i++){row_i = (int)((m_cloud_in->points[i].y - min.y) / m_step) + 1; //每一點的行號,從1開始col_i = (int)((m_cloud_in->points[i].x - min.x) / m_step) + 1; //每一點的列號,從1開始lay_i = (int)((m_cloud_in->points[i].z - min.z) / m_step) + 1;//每一點的列號,從1開始grid_id_pt = (lay_i - 1) * (m_row * m_col) + (row_i - 1) * m_col + col_i; //格網一維索引,從1開始m_grid3D.insert(pair<int, PointT>(grid_id_pt, m_cloud_in->points[i])); //將每一個id對應的點坐標存入容器grids2D中}//判斷體素是否為空,若非空,則計算體素內點云中心,以中心代替該體素內的所有點for (int lay = 1; lay <= m_lay; lay++) //層掃描{for (int row = 1; row <= m_row; row++) //行掃描{for (int col = 1; col <= m_col; col++) //列掃描{int grid_id; //逐行對應的一維格網編號id,從1開始grid_id = (lay - 1) * (m_row * m_col) + (row - 1) * m_col + col;//若體素格網內有點,則計算體素中心if (m_grid3D.count(grid_id)) {PointT temp;temp.x = min.x + (col - 0.5)*m_step;temp.y = min.y + (row - 0.5)*m_step;temp.z = min.z + (lay -0.5)*m_step;downSampleCloud->push_back(temp);}}}} }2.3 與體素質心下采樣結果對比
下采樣結果對比| 方法 | 用時 | 點數 |
| 體素質心 | 0.243s | 856 |
| 體素中心 | 0.201s | 856 |
| 原始點云 | 體素質心下采樣點云 | 體素中心下采樣點云 |
3 總結
- 體素質心下采樣方法雖然耗時長,但采樣結果更加準確;體素中心下采樣方法耗時短,但容易失真。(由于本次實驗數據點數較少,耗時接近,可嘗試采樣大規模點云驗證)
- 體素質心下采樣和體素中心下采樣,.h 文件是一樣的,只是下采樣實現的方式(.cpp)不同。
- 注意下面的代碼,行列層都是從1開始,并非0。因此for循環的判斷條件是 “<=”,并非“=”。
總結
以上是生活随笔為你收集整理的点云体素下采样 ❤️(体素质心 | 体素中心)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 体素化(Voxelization) 3D
- 下一篇: java打包好的jar包在Linux服务