【PCL】——filtrage des nuages de points

Lors de l'acquisition de données de nuage de points, en raison de l'influence de la précision de l'équipement, de l'expérience de l'opérateur et des facteurs environnementaux, ainsi que des caractéristiques de diffraction des ondes électromagnétiques, des modifications des propriétés de surface de l'objet mesuré et de l'influence des opérations de raccordement et d'enregistrement des données, les données de nuage de points entraîneront inévitablement du bruit.

Plusieurs situations nécessitant un filtrage des nuages ​​de points sont les suivantes :

  • La densité des données des nuages ​​de points est irrégulière et doit être lissée ;
  • Les valeurs aberrantes doivent être supprimées en raison de problèmes tels que l'occlusion ;
  • Une grande quantité de données doit être sous-échantillonnée ;
  • Les données de bruit doivent être supprimées.

Les méthodes de filtrage conventionnelles PCL sont bien conditionnées et le filtrage des nuages ​​de points est complété en appelant chaque objet filtre. Les principaux filtres sont le filtre de passage , le filtre de voxel , le filtre statistique , le filtre de rayon , etc.

Les méthodes courantes sont les suivantes :

  • Filtrer le nuage de points à l'aide d'un filtre passe-système
  • Sous-échantillonnage des nuages ​​de points à l'aide du filtrage Voxel
  • Supprimer les valeurs aberrantes à l'aide de filtres statistiques
  • Supprimer les valeurs aberrantes à l'aide du filtrage conditionnel ou du filtrage par rayon

passer le filtre

Le filtre passe-système consiste à définir la plage sur l'attribut du point en fonction de l'attribut du nuage de points (attributs tels que x, y, z, valeur de couleur, etc.), filtrer le point, conserver la plage ou conserver en dehors de la plage.

Tutoriel officiel du site Web - https://pcl.readthedocs.io/projects/tutorials/en/latest/passthrough.html#passthrough
cas de test 1 (conserver le nuage de points entre 0 et 1 m dans la plage de l'axe z)

#include <iostream>
#include <pcl/point_types.h>
#include <pcl/filters/passthrough.h>
#include <pcl/visualization/cloud_viewer.h>

typedef pcl::PointXYZ PointT;

int
main(int argc, char **argv) {
    
    
    pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
    pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered(new pcl::PointCloud<pcl::PointXYZ>);

    //写入点云
    cloud->width = 5;
    cloud->height = 1;
    cloud->points.resize(cloud->width * cloud->height);

    for (size_t i = 0; i < cloud->points.size(); ++i) {
    
    
        cloud->points[i].x = 1024 * rand() / (RAND_MAX + 1.0f);
        cloud->points[i].y = 1024 * rand() / (RAND_MAX + 1.0f);
        cloud->points[i].z = 1024 * rand() / (RAND_MAX + 1.0f);
    }

    std::cerr << "Cloud before filtering: " << std::endl;
    for (size_t i = 0; i < cloud->points.size(); ++i)
        std::cerr << "    " << cloud->points[i].x << " "
                  << cloud->points[i].y << " "
                  << cloud->points[i].z << std::endl;

    // Create the filtering object
    pcl::PassThrough<pcl::PointXYZ> pass;
    pass.setInputCloud(cloud);          // 1. 设置输入源
    pass.setFilterFieldName("z");       // 2. 设置过滤域名
    pass.setFilterLimits(0.0, 1.0);     // 3. 设置过滤范围
//    pass.setFilterLimitsNegative(true); // 设置获取Limits之外的内容
    pass.filter(*cloud_filtered);       // 4. 执行过滤,并将结果输出到cloud_filtered

    std::cerr << "Cloud after filtering: " << std::endl;
    for (size_t i = 0; i < cloud_filtered->points.size(); ++i)
        std::cerr << "    " << cloud_filtered->points[i].x << " "
                  << cloud_filtered->points[i].y << " "
                  << cloud_filtered->points[i].z << std::endl;

    pcl::visualization::CloudViewer viewer("Cloud Viewer");

    //这里会一直阻塞直到点云被渲染
    viewer.showCloud(cloud);
    while (!viewer.wasStopped()) {
    
    
    }
    return (0);
}

Après l'exécution du programme, il y aura les résultats suivants

Cloud before filtering:
    0.352222 -0.151883 -0.106395
    -0.397406 -0.473106 0.292602
    -0.731898 0.667105 0.441304
    -0.734766 0.854581 -0.0361733
    -0.4607 -0.277468 -0.916762
Cloud after filtering:
    -0.397406 -0.473106 0.292602
    -0.731898 0.667105 0.441304

Un affichage graphique du processus de filtrage est illustré ci-dessous, où le rouge est (x), le vert est (y) et le bleu est (z) dans le système de coordonnées. Parmi les cinq points de la figure, les points verts sont les points filtrés et les points rouges sont les points filtrés.
insérez la description de l'image ici

Cas d'essai 2

#include <iostream>
#include <pcl/point_types.h>
#include <pcl/io/io.h>
#include <pcl/io/pcd_io.h>
#include <pcl/filters/passthrough.h>
#include <pcl/visualization/cloud_viewer.h>

int
main(int argc, char **argv) {
    
    
    pcl::PointCloud<pcl::PointXYZI>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZI>);
    pcl::PointCloud<pcl::PointXYZI>::Ptr cloud_filtered(new pcl::PointCloud<pcl::PointXYZI>);

    // 加载pcd文件到cloud
    if (pcl::io::loadPCDFile("../data/result.pcd", *cloud) == -1)
    {
    
    
        std::cout << "Could not load pcd file!" << std::endl;
        return -1;
    }

    // Create the filtering object
    pcl::PassThrough<pcl::PointXYZI> pass;
    pass.setInputCloud(cloud);          // 1. 设置输入源
    pass.setFilterFieldName("z");       // 2. 设置过滤域名
    pass.setFilterLimits(-1.0, 1.0);     // 3. 设置过滤范围
    // pass.setFilterLimitsNegative(true); // 设置获取Limits之外的内容
    pass.filter(*cloud_filtered);       // 4. 执行过滤,并将结果输出到cloud_filtered

    pcl::visualization::PCLVisualizer viewer_filter("Cloud-Filtered Viewer");
    //背景颜色设置
    viewer_filter.setBackgroundColor(0, 0, 0);
    //按照z字段进行渲染
    pcl::visualization::PointCloudColorHandlerGenericField<pcl::PointXYZI> fildColor_filter(cloud_filtered, "intensity");
    //显示点云,其中fildColor为颜色显示
    viewer_filter.addPointCloud<pcl::PointXYZI>(cloud_filtered, fildColor_filter, "sample");
    //设置点云大小
    viewer_filter.setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 1, "sample");
    // // 循环判断是否退出
    // while (!viewer_filter.wasStopped()) {
    
    
    //     viewer_filter.spinOnce();
    // }

    pcl::visualization::PCLVisualizer viewer("Cloud Viewer");
    //背景颜色设置
    viewer.setBackgroundColor(0, 0, 0);
    //按照z字段进行渲染
    pcl::visualization::PointCloudColorHandlerGenericField<pcl::PointXYZI> fildColor(cloud, "intensity");
    //显示点云,其中fildColor为颜色显示
    viewer.addPointCloud<pcl::PointXYZI>(cloud, fildColor, "sample");
    //设置点云大小
    viewer.setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 1, "sample");
    
    // 循环判断是否退出
    while (!viewer.wasStopped() && !viewer_filter.wasStopped()) {
    
    
        viewer_filter.spinOnce();
        viewer.spinOnce();
    }
    return 0;
}

Effet d'essai

insérez la description de l'image ici

sous-échantillonnage

Le filtrage Voxel, c'est-à-dire la réduction du nombre de points et de données de nuage de points tout en conservant les caractéristiques de forme du nuage de points, est très pratique pour améliorer la vitesse des algorithmes tels que l'enregistrement, la reconstruction de surface et la reconnaissance de forme.

La classe VoxelGrid implémentée par PCL crée une grille de voxels 3D à travers les données de nuage de points d'entrée (la grille de voxels peut être imaginée comme une collection de minuscules cubes spatiaux 3D), puis dans chaque voxel (c'est-à-dire cube 3D), utilisez le volume Le barycentre de tous les points du voxel sont utilisés pour approximer d'autres points du voxel , de sorte que tous les points du voxel sont finalement représentés par un point barycentre, et le nuage de points filtré est obtenu après traitement pour tous les voxels.

insérez la description de l'image ici

Tutoriel officiel - https://pcl-tutorials.readthedocs.io/en/latest/voxel_grid.html#voxelgrid

#include <iostream>
#include <pcl/io/io.h>
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
#include <pcl/filters/voxel_grid.h>
#include <pcl/visualization/cloud_viewer.h>

int main (int argc, char** argv)
{
    
    
  pcl::PCLPointCloud2::Ptr cloud (new pcl::PCLPointCloud2 ());
  pcl::PCLPointCloud2::Ptr cloud_filtered (new pcl::PCLPointCloud2 ());

  // Fill in the cloud data
  pcl::PCDReader reader; // 也可以用创建reader的方式来读PCD文件
  // Replace the path below with the path where you saved your file
  reader.read ("../data/result.pcd", *cloud); // Remember to download the file first!

  std::cerr << "PointCloud before filtering: " << cloud->width * cloud->height 
       << " data points (" << pcl::getFieldsList (*cloud) << ")." << std::endl;

  // Create the filtering object
  pcl::VoxelGrid<pcl::PCLPointCloud2> sor;
  sor.setInputCloud (cloud);
  sor.setLeafSize (0.5f, 0.5f, 0.5f);
  sor.filter (*cloud_filtered);

  std::cerr << "PointCloud after filtering: " << cloud_filtered->width * cloud_filtered->height 
       << " data points (" << pcl::getFieldsList (*cloud_filtered) << ")." << std::endl;

  pcl::PCDWriter writer;
  writer.write ("../data/result_downsampled.pcd", *cloud_filtered, 
         Eigen::Vector4f::Zero (), Eigen::Quaternionf::Identity (), false);

  return (0);
}
PointCloud before filtering: 98322 data points (x y z intensity).
PointCloud after filtering: 11733 data points (x y z intensity).

pcl_viewer pour visualiser l'effet, le nombre de nuages ​​de points est considérablement réduit.

insérez la description de l'image ici

Utiliser le filtrage statistique (statisticalOutlierRemoval) pour supprimer les valeurs aberrantes

Le balayage laser produit généralement des ensembles de données de nuages ​​de points avec une densité inégale. De plus, les erreurs de mesure produisent également des valeurs aberrantes éparses. Les calculs de caractéristiques des nuages ​​de points locaux (tels que les vecteurs normaux ou les taux de changement de courbure aux points d'échantillonnage) sont complexes, ce qui peut facilement conduire à point Le post-traitement tel que l'enregistrement dans le cloud a échoué.

Idées de solutions communes : effectuer une analyse statistique sur le voisinage de chaque point, et élaguer certains points qui ne répondent pas à certaines normes. En supposant que le résultat obtenu est une distribution gaussienne dont la forme est déterminée par la moyenne et l'écart type, les points dont la distance moyenne est en dehors de la plage standard peuvent être définis comme des valeurs aberrantes et supprimés des données.

insérez la description de l'image ici
Les étapes du filtrage statistique pour supprimer les valeurs aberrantes sont les suivantes :

  1. Trouver tous les voisins de chaque point
  2. Calculer la distance dij d_{ij} de chaque point à ses voisinsdje, où je = [ 1 , 2 , . . . , m ] je=[1,2,...,m]je=[ 1 ,2 ,... ,m ] signifie un total demmm points,j = [ 1 , 2 , . . . , k ] j=[1,2,...,k]j=[ 1 ,2 ,... ,k ] signifie un total dekkk points de voisinage
  3. Selon la distribution gaussienne d N ( μ , σ ) d~N(\mu,\sigma)d N ( μ , σ ) pour modéliser le paramètre de distance et calculer les μ \mude tous les points et des points voisinsμ (moyenne des distances),σ \sigmaσ (écart type de la distance), comme suit : μ = 1 nk ∑ i = 1 m ∑ j = 1 kdij \mu {\rm{ = }}\frac{ { \rm{1}}}{ {nk} }\ sum\limits_{i = 1}^m {\sum\limits_{j = 1}^k { {d_{ij}}} }m =nk1je = 1mj = 1kdjeσ = 1 nk ∑ je = 1 m ∑ j = 1 k ( dij - μ ) 2 \sigma {\rm{ = }}\sqrt {\frac{ {\rm{1}}}{ {nk}} \ sum \limits_{i = 1}^m {\sum\limits_{j = 1}^k { {{ ( {d_{ij}} - \mu )}^2}} } }σ =nk1je = 1mj = 1k( djem )2 A cette étape, la distance moyenne entre chaque point et ses points voisins est également calculée ∑ j = 1 kdij {\sum\limits_{j = 1}^k { { { { d_ {ij}} }}} }j = 1kdje
    Traversez tous les points, si la valeur moyenne de leur distance est supérieure à la confiance spécifiée de la distribution gaussienne, puis supprimez, par exemple : ∑ j = 1 kdij > μ + 3 σ ou ∑ j = 1 kdij < μ − 3 σ \ sum\limits_{j = 1}^k { {d_{ij}}} > \mu + 3\sigma or\sum\limits_{j = 1}^k { { d_{ ij}}} < \mu - 3 \sigmaj = 1kdje>m+ou _j = 1kdje<m3 p
#include <iostream>
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
#include <pcl/filters/statistical_outlier_removal.h>

int main (int argc, char** argv)
{
    
    
    pcl::PointCloud<pcl::PointXYZI>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZI>);
    pcl::PointCloud<pcl::PointXYZI>::Ptr cloud_filtered (new pcl::PointCloud<pcl::PointXYZI>);

    // 从文件读取点云
    // Fill in the cloud data
    pcl::PCDReader reader;
    // Replace the path below with the path where you saved your file
    reader.read<pcl::PointXYZI> ("../data/result.pcd", *cloud);

    std::cerr << "Cloud before filtering: " << std::endl;
    std::cerr << *cloud << std::endl;

    // 创建过滤器,每个点分析计算时考虑的最近邻居个数为50个;
    // 设置标准差阈值为1,这意味着所有距离查询点的平均距离的标准偏差均大于1个标准偏差的所有点都将被标记为离群值并删除。
    // 计算输出并将其存储在cloud_filtered中

    // Create the filtering object
    pcl::StatisticalOutlierRemoval<pcl::PointXYZI> sor;
    sor.setInputCloud (cloud);
    // 设置平均距离估计的最近邻居的数量K
    sor.setMeanK (50);
    // 设置标准差阈值系数
    sor.setStddevMulThresh (1.0);
    // 执行过滤
    sor.filter (*cloud_filtered);

    std::cerr << "Cloud after filtering: " << std::endl;
    std::cerr << *cloud_filtered << std::endl;
    // 将留下来的点保存到后缀为_inliers.pcd的文件
    pcl::PCDWriter writer;
    writer.write<pcl::PointXYZI> ("../data/result_inliers.pcd", *cloud_filtered, false);

    // 使用个相同的过滤器,但是对输出结果取反,则得到那些被过滤掉的点,保存到_outliers.pcd文件
    sor.setNegative (true);
    sor.filter (*cloud_filtered);
    writer.write<pcl::PointXYZI> ("../data/result_outliers.pcd", *cloud_filtered, false);

    return 0;
}

Exécutez le programme et affichez les résultats suivants

Cloud before filtering: 
header: seq: 0 stamp: 0 frame_id: 

points[]: 98322
width: 98322
height: 1
is_dense: 1
sensor origin (xyz): [0, 0, 0] / orientation (xyzw): [0, 0, 0, 1]

Cloud after filtering: 
header: seq: 0 stamp: 0 frame_id: 

points[]: 90637
width: 90637
height: 1
is_dense: 1
sensor origin (xyz): [0, 0, 0] / orientation (xyzw): [0, 0, 0, 1]

Afficher avec pcl_viewer
insérez la description de l'image ici

Utilisez le filtrage conditionnel (ConditionalRemoval) ou le filtrage de rayon (RadiusOutlinerRemoval) pour supprimer les valeurs aberrantes

Filtre conditionnel : filtre en définissant des conditions de filtre, ce qui est un peu comme une fonction par morceaux. Lorsque le nuage de points est dans une certaine plage, il sera conservé, et s'il ne l'est pas, il sera supprimé.

Filtre de rayon : Dessinez un cercle avec un certain point comme centre pour calculer le nombre de points tombant au milieu du cercle. Lorsque le nombre est supérieur à une valeur donnée, le point sera conservé, et si le nombre est inférieur à la valeur donnée, le point sera éliminé. Cet algorithme s'exécute rapidement et les points laissés par des itérations séquentielles doivent être les plus denses, mais le rayon du cercle et le nombre de points dans le cercle doivent être spécifiés manuellement.

L'image ci-dessous permet de visualiser l'objet de filtre RadiusOutlierRemoval en action. L'utilisateur spécifie le nombre de voisins, et chaque point doit avoir le nombre spécifié de voisins dans le rayon spécifié pour être conservé dans PointCloud. Par exemple, si 1 voisin est spécifié, seuls les points jaunes seront supprimés du PointCloud. Si 2 voisins sont spécifiés, les points jaune et vert seront supprimés du PointCloud.

insérez la description de l'image ici

#include <iostream>
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
#include <pcl/filters/radius_outlier_removal.h>
#include <pcl/filters/conditional_removal.h>
#include <pcl/visualization/pcl_visualizer.h>

void showPointClouds(const pcl::PointCloud<pcl::PointXYZI>::Ptr &cloud){
    
    
    pcl::visualization::PCLVisualizer viewer("Cloud Viewer");
    //背景颜色设置
    viewer.setBackgroundColor(0, 0, 0);
    //按照z字段进行渲染
    pcl::visualization::PointCloudColorHandlerGenericField<pcl::PointXYZI> fildColor(cloud, "intensity");
    //显示点云,其中fildColor为颜色显示
    viewer.addPointCloud<pcl::PointXYZI>(cloud, fildColor, "sample");
    //设置点云大小
    viewer.setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 1, "sample");

    while (!viewer.wasStopped())
    {
    
    
        viewer.spinOnce();
    }
    
}

int main(int argc, char **argv) {
    
    
    if (argc != 2)
    {
    
    
        //选择-r为半径滤波;选择-c为条件滤波
        std::cerr << "please specify command line arg '-r' or '-c'" << std::endl;
        exit(0);
    }
    pcl::PointCloud<pcl::PointXYZI>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZI>);
    pcl::PointCloud<pcl::PointXYZI>::Ptr cloud_filtered(new pcl::PointCloud<pcl::PointXYZI>);

    // 从文件读取点云
    // Fill in the cloud data
    pcl::PCDReader reader;
    // Replace the path below with the path where you saved your file
    reader.read<pcl::PointXYZI> ("../data/result.pcd", *cloud);

    if (strcmp(argv[1], "-r") == 0) 
    {
    
    
        //半径滤波
        pcl::RadiusOutlierRemoval<pcl::PointXYZI> outrem;
        // build the filter
        outrem.setInputCloud(cloud);
        outrem.setRadiusSearch(0.4); //设置半径滤波时圆的半径
        outrem.setMinNeighborsInRadius(2); //设置圆内的点数
        // apply filter
        outrem.filter(*cloud_filtered);

        //保存相应pcd文件,以方便后续查看
        pcl::PCDWriter writer;
        writer.write<pcl::PointXYZI>("../data/result_remove_outliers_r.pcd", *cloud_filtered, false);
    } 
    else if (strcmp(argv[1], "-c") == 0) 
    {
    
    
        //条件滤波
        // build the condition
        // 过滤条件:z轴方向上,大于0.0,小于8.0
        pcl::ConditionAnd<pcl::PointXYZI>::Ptr range_cond(new pcl::ConditionAnd<pcl::PointXYZI>());
        range_cond->addComparison(pcl::FieldComparison<pcl::PointXYZI>::ConstPtr(
                new pcl::FieldComparison<pcl::PointXYZI>("z", pcl::ComparisonOps::GT, 0.0)));
        range_cond->addComparison(pcl::FieldComparison<pcl::PointXYZI>::ConstPtr(
                new pcl::FieldComparison<pcl::PointXYZI>("z", pcl::ComparisonOps::LT, 0.8)));

        // build the filter
        pcl::ConditionalRemoval<pcl::PointXYZI> condrem;
        condrem.setCondition(range_cond);
        condrem.setInputCloud(cloud);
        condrem.setKeepOrganized(false);
        // apply filter
        condrem.filter(*cloud_filtered);

        //保存相应pcd文件,以方便后续查看
        pcl::PCDWriter writer;
        writer.write<pcl::PointXYZI>("../data/result_remove_outliers_c.pcd", *cloud_filtered, false);
    } 
    else 
    {
    
    
        std::cerr << "please specify command line arg '-r' or '-c'" << std::endl;
        exit(0);
    }


    std::cerr << "Cloud before filtering: " << std::endl;
    std::cerr << *cloud << std::endl;

    // display pointcloud after filtering
    std::cerr << "Cloud after filtering: " << std::endl;
    std::cerr << *cloud_filtered << std::endl;

    showPointClouds(cloud_filtered);
    return (0);
}

filtre de rayon

Cloud before filtering: 
header: seq: 0 stamp: 0 frame_id: 

points[]: 98322
width: 98322
height: 1
is_dense: 1
sensor origin (xyz): [0, 0, 0] / orientation (xyzw): [0, 0, 0, 1]

Cloud after filtering: 
header: seq: 0 stamp: 0 frame_id: 

points[]: 95967
width: 95967
height: 1
is_dense: 1
sensor origin (xyz): [0, 0, 0] / orientation (xyzw): [0, 0, 0, 1]

insérez la description de l'image ici

filtre conditionnel

Cloud before filtering: 
header: seq: 0 stamp: 0 frame_id: 

points[]: 98322
width: 98322
height: 1
is_dense: 1
sensor origin (xyz): [0, 0, 0] / orientation (xyzw): [0, 0, 0, 1]

Cloud after filtering: 
header: seq: 0 stamp: 0 frame_id: 

points[]: 9904
width: 9904
height: 1
is_dense: 1
sensor origin (xyz): [0, 0, 0] / orientation (xyzw): [0, 0, 0, 1]

insérez la description de l'image ici

Je suppose que tu aimes

Origine blog.csdn.net/sinat_52032317/article/details/130435585
conseillé
Classement