1、边界提取采用PCL库中的方法,基于法线估计来实现的边界检测与提取:由点云估计出法线,再由法线和点云数据估计出边界。
有多个参数需要用户根据自己的数据进行调整,其中影响主要是估计法线的半径设置ne.RadiusSearch,设置为分辨率的10倍时,效果较好,主要是对于法线估计。邻域半径选择太小了,噪声较大,估计的法线就容易出错,而搜索邻域半径设置的太大估计速度就比较慢。boundEst.RadiusSearch ,也设置10倍,太小则内部的很多点就都当成边界点了。最后一个参数是边界判断时的角度阈值,默认值为PI/2,此处设置为PI/4,用户也可以根据需要进行更改。
2、源码
注:测试代码均使用PclSharp1.12.0库
using PclSharp;
using PclSharp.Features;
using PclSharp.Helpers;
using PclSharp.IO;
using PclSharp.Search;
using PclSharp.Struct;
using PclSharp.Surface;
using System;
namespace PclSharpTest
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine($"C#--PclSharp算法库测试:");
//读取点云数据
var cloud = new PointCloudOfXYZ();
using (var reader = new PCDReader())
reader.Read(AppDomain.CurrentDomain.BaseDirectory + $"//pcd//table_scene_lms400.pcd", cloud);
//体素滤波
using (var sor = new PclSharp.Filters.VoxelGridOfXYZ())
{
sor.SetInputCloud(cloud);
sor.LeafSize = new PointXYZ { X = 0.01f, Y = 0.01f, Z = 0.01f };//体素点的大小
sor.filter(cloud);
}
//剔除离群点
using (var sor = new PclSharp.Filters.StatisticalOutlierRemovalOfXYZ())
{
sor.SetInputCloud(cloud);
sor.MeanK = 100; //设置在进行统计时考虑查询点临近点数
sor.StdDevMulThresh = 1.0; //设置判断是否为离群点的阀值
sor.filter(cloud);//输出滤波后点云
}
//保存边界估计结果
var boundaries = new PointCloudOfBoundary();
//定义一个进行边界特征估计的对象
var boundEst = new BoundaryEstimationOfPointXYZAndNormal();
// 计算法向量
var normals = new PointCloudOfNormal();//法线点云
using (var ne = new NormalEstimationOfPointXYZAndNormal())//法线估计对象
using (var tree = new KdTreeOfXYZ()) //创建用于最近邻搜索的KD-Tree
{
//法线估计
//ne.SetSearchMethod(tree);
ne.SetInputCloud(cloud);
ne.RadiusSearch = 0.1;//设置法线估计的半径,需要根据自己点云情况调整这个参数
ne.Compute(normals);//计算法线,结果存储在normals中
}
boundEst.SetInputCloud(cloud);//设置输入的点云
boundEst.SetInputNormals(normals); //设置边界估计的法线,因为边界估计依赖于法线
boundEst.RadiusSearch = 0.1; //设置边界估计所需要的半径
boundEst.AngleThreshold = Math.PI / 4; //边界估计时的角度阈值
boundEst.SetSearchMethod(new KdTreeOfXYZ()); //设置搜索方式KdTree
boundEst.Compute(boundaries); //将边界估计结果保存在boundaries
var cloud_boundary = new PointCloudOfXYZ();//边界点
//存储估计为边界的点云数据,将边界结果保存为PointXYZ类型
for (int i = 0; i < cloud.Count; i++)
{
if (boundaries.Points[i].boundary_point > 0)
{
cloud_boundary.Add(cloud.Points[i]);
}
}
Console.WriteLine($"输出边界点的个数:{cloud_boundary.Points.Count}");
using (var visualizer = new PclSharp.Vis.Visualizer("a window"))
{
//创建两个观察视点
int v1 = 1;
int v2 = 2;
visualizer.CreateViewPort(0.0, 0.0, 0.5, 1.0, v1);
visualizer.CreateViewPort(0.5, 0.0, 1.0, 1.0, v2);
visualizer.SetBackgroundColor_ViewPort(0f, 0f, 0f, v1);
visualizer.SetBackgroundColor_ViewPort(0.05f, 0f, 0f, v2);
visualizer.AddPointCloud(cloud, "v1", v1);
visualizer.AddPointCloud(cloud_boundary, "v2",v2);
visualizer.SetPointCloudColor(0.5, 1, 1, "v1");//原始点云附色
visualizer.SetPointCloudColor(0, 1, 0, "v2");//原始点云附色
while (!visualizer.WasStopped)
visualizer.SpinOnce(100);
}
Console.ReadKey();
}
}
}
3、编译结果
去噪之前的边界估计 :
统计滤波去噪之后的边界估计: