PCL笔记九:采样一致性

2023-05-16

模型拟合?

采样一致性的目的:用于排除错误的样本

基于采样一致性算法的应用主要是对点云进行分割,根据不同设定的几何模型,估计对应的几何模型的参数,在一定允许误差范围内分割出在模型上的点云

目前PCL中支持的几何模型分割空间平面直线二维或三维圆周圆球锥体

随机采样一致性的另一个应用就是点云的配准对的剔除。



RANSAC随机采样一致性算法简介

RANSAC是一种随机参数估计算法。

  1. RANSAC从样本中随机抽选出一个样本子集
  2. 使用最小方差估计算法对这个子集计算模型参数
  3. 然后计算所有样本与该模型的偏差
  4. 再使用一个预先设定好的阈值偏差比较
    1. 当偏差小于阈值时,该样本点属于模型内样本点inliers),局内点或内点。
    2. 否则为模型外样本点outliers),局外点或外点。
  5. 记录下当前的inliers的个数,然后重复这一过程。
  6. 每一次重复,都记录当前最佳的模型参数,所谓最佳:inliers的个数最多,此时对应的inliers个数为best_ninliers。
  7. 每次迭代的末尾,都会根据期望的误差率best_ninliers总样本个数当前迭代次数,计算一个迭代结束评判因子,据此决定是否迭代结束。(很像深度学习中的每轮训练几乎一致)。

迭代结束后,最佳模型参数就是最终的模型参数估计值。(跟深度学习一模一样呀。)


RANSAC理论上可以剔除outliers的影响,并得到全局最优的参数估计。但是RANSAC有几个问题:

  • 在每次迭代中都要区分inliers和outliers,因此需要事先设定阈值,当模型具有明显的物理意义时,这个阈值还比较容易设定,但是若模型比较抽象时,这个阈值就不那么容易设定了,而且固定阈值不适用于样本动态变化的应用;
  • RANSAC的迭代次数是运行期决定的,不能预知迭代的确切次数(当然迭代次数的范围是可以预测的)。
  • RANSAC只能从一个特定数据集中估计一个模型,当两个(或者更多个)模型存在时,RANSAC不能找到别的模型。

直线拟合案例:

通过RANSAC之后:得到

局外点是红色,局内点表示为蓝色,蓝色线就是基于RANSAC的结果。适应数据的结果(模型)就是一条线。




LMedS最小中值方差估计算法

LMedS也是一种随机参数估计算法。最小中值方差估计算法

  1. LMedS也从样本中随机抽选出一个样本子集
  2. 使用最小方差估计算法对子集计算模型参数
  3. 然后计算所有样本与该模型的偏差
  4. (与RANSAC不同)LMedS记录的是:
    1. 所有样本中,偏差值居中的那个样本的偏差[称为MeD偏差(这也是LMedS中Med的由来)],
    2. 以及本次计算得到的模型参数。(由于这一变化,LMedS 不需要预先设定阈值 来区分inliers和outliers)。
  5. 重复前面的过程N次,从N个Med偏差中挑选出最小的一个,其对应的模型参数就是最终的模型参数估计值。

其中迭代次数N是由样本子集中样本的个数(子集个数)、期望的模型误差、事先估计的样本中outliers比例所决定的。

LMedS理论上也可以剔除outliers的影响,并得到全局最优的参数估计,而且克服了RANSAC的两个缺点(虽然LMedS也需要实现设定样本中outliers的比例,但这个数字比较容易设定)。但是当outliers在样本中所占比例达到或超过50%时,LMedS就无能为力了。这与LMedS每次迭代记录的是Med偏差值有关。




PCL中Sample_consensus模型支持的几何模型


1、SACMODEL_PLANE模型:

  • 定义为平面模型,共设置4个参数[normal_x  normal_y  normal_z  d],其中(normal_xnormal_ynormal_z)为Hessian范式法向量的坐标及常量d值
  • ax + by + cz + d = 0
  • 其中a^2+b^2+c^2=1,d>0,(a,b,c)为平面法矢,d为原点至平面的距离,

从点云中分割提取的内点都处在估计参数对应的平面上与平面距离在一定范围内


2、SACMODEL_LINE模型:

  • 定义为直线模型,共设置6个参数[point_on_line.x point_on_line.y point_on_line.z line_direction.x  line_direction.y line_direction.z],
  • 其中(point_on_line.x,point_on_line.y,point_on_line.z)为直线上一点的三维坐标(x0, y0, z0)
  • (line_direction.x,line_direction.y,line_direction.z)为直线方向向量(a,b,c)
  • a(x-x_{0}) + b(y-y_{0}) +c(z-z_{0}) = 0

从点云中分割提取的内点都处在估计参数对应直线上直线距离在一定范围内


3、SACMODEL_CIRCLE2D模型:

  • 定义为二维圆的圆周模型,共设置3个参数[center.x center.y radius],
  • 其中(center.x, center.y)为圆周中心的二维坐标,
  • radius为圆周半径,

从点云中分割提取的内点都处在估计参数对应圆周距离圆周边线的距离在一定范围内


4、SACMODEL_SPHERE模型:

  • 定义为三维球体的圆周模型,共设置4个参数[center.x center.y center.z radius],
  • 其中(center.x, center.y center.z)为球体中心的三维坐标,
  • radius为球体半径,

从点云中分割提取的内点都处在估计参数对应球体上距离球体边线的距离在一定范围内


5、SACMODEL_CYLINDER模型:

  • 定义为圆柱体模型,共设置7个参数[point_on_axis.x point_on_axis.y point_on_axis.z axis_direction.x axis_direction.y axis_direction.z radius],
  • 其中(point_on_axis.x,point_on_axis.y,point_on_axis.z)为轴线上点的三维坐标,
  • (axis_direction.x,axis_direction.y,axis_direction.z)为轴线方向向量的三维坐标
  • radius为圆柱体半径
  • (x-x0)^2+(y-y0)^2+(z-z0)^2-r^2=[l(x-x0)+m(y-y0)+n(z-z0)]^2/(l^2+m^2+n^2)。

从点云中分割提取的内点都处在估计参数对应圆柱体上距离圆柱体边线的距离在一定范围内


6、SACMODEL_CONE模型

定义为圆锥模型,尚未实现


7、SACMODEL_TORUS模型:

定义为圆环面模型,尚未实现


8、SACMODEL_PARALLEL_LINE模型:

定义为有条件限制的直线模型,在规定的最大角度偏差限制下,直线模型与给定轴线平行,其参数设置参见SACMODEL_LINE模型。


9、SACMODEL_PERPENDICULAR_PLANE模型:

定义为有条件限制的平面模型,在规定的最大角度偏差限制下,平面模型与给定轴线垂直,参数设置参见SACMODEL_PLANE模型。


10、SACMODEL_NORMAL_PLANE模型:

定义为有条件限制的平面模型,在规定的最大角度偏差限制下,每一个局内点的法线必须与估计的平面模型的法线平行,参数设置参见SACMODEL_PLANE模型。


11、SACMODEL_PARALLEL_PLANE模型:

定义为有条件限制的平面模型,在规定的最大角度偏差限制下,平面模型与给定的轴线平行,参数设置参见SACMODEL_PLANE模型。


12、SACMODEL_NORMAL_PARALLEL_PLANE模型:

定义为有条件限制的平面模型,在法线约束下,三维平面模型必须与用户设定的轴线平行,参数设置参见SACMODEL_PLANE模型。




PCL中Sample_consensus模块及类

PCL中Sample_consensus库实现了随机采样一致性及其泛化估计算法。

平面、柱面等各种常见几何模型,用不同的估计算法和不同的几何模型自由结合估算点云中隐含的具体几何模型的系数,实现对点云中所处的几何模型的分割

线、平面、柱面和球面等模型已经在PCL库中实现,平面模型经常被应用到常见的室内平面分割模型提取中,比如墙、地板、桌面,其他模型常应用到根据几何结构检测识别和分割物体中(比如用一个圆柱体模型分割出一个杯子)。



案例一:

RandomSampleConsensus类获得点云的拟合平面模型

首先对两个点云进行定义初始化,并对其中一个点云填充点云数据,作为处理前的原始点云

其中,大部分点云数据是基于设定的圆球和平面模型计算而得到的坐标值,作为局内点;有五分之一的点云数据是被随机放置的,作为局外点

接下来,创建一个用于存储点云中局内点的位置的整数型向量,作为点的索引的向量,这里我们使用一个平面或者球面随机估计模型建立随机采样一致性对象,并根据命令行参数分别以点云作为输入,来进行随机参数估计,存储局内点。

最后复制适合模型的局内点到final点云中,然后根据命令行参数在三维窗口中显示原始点云或者估计得到的局内点组成的点云。

#include <iostream>
#include <pcl/console/parse.h>
#include <pcl/filters/extract_indices.h>
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
#include <pcl/sample_consensus/ransac.h>
#include <pcl/sample_consensus/sac_model_plane.h>
#include <pcl/sample_consensus/sac_model_sphere.h>
#include <pcl/visualization/pcl_visualizer.h>
#include <boost/thread/thread.hpp>

boost::shared_ptr<pcl::visualization::PCLVisualizer>
simpleVis(pcl::PointCloud<pcl::PointXYZ>::ConstPtr cloud)
{
    // --------------------------------------------
    // -----Open 3D viewer and add point cloud-----
    // --------------------------------------------
    boost::shared_ptr<pcl::visualization::PCLVisualizer> viewer(new pcl::visualization::PCLVisualizer("3D Viewer"));
    viewer->setBackgroundColor(0, 0, 0);
    viewer->addPointCloud<pcl::PointXYZ>(cloud, "sample cloud");
    viewer->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 3, "sample cloud");
    //viewer->addCoordinateSystem (1.0);
    viewer->initCameraParameters();
    return (viewer);
}

int
main(int argc, char** argv)
{
    srand(time(NULL));
    // initialize PointClouds
    pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
    pcl::PointCloud<pcl::PointXYZ>::Ptr final(new pcl::PointCloud<pcl::PointXYZ>);

    // populate our PointCloud with points
    cloud->width = 5000;
    cloud->height = 1;
    cloud->is_dense = false;
    cloud->points.resize(cloud->width * cloud->height);
    for (size_t i = 0; i < cloud->points.size(); ++i)
    {
        if (pcl::console::find_argument(argc, argv, "-s") >= 0 || pcl::console::find_argument(argc, argv, "-sf") >= 0)
        {
            cloud->points[i].x = rand() / (RAND_MAX + 1.0);
            cloud->points[i].y = rand() / (RAND_MAX + 1.0);
            if (i % 5 == 0)
                cloud->points[i].z = rand() / (RAND_MAX + 1.0);
            else if (i % 2 == 0)
                cloud->points[i].z = sqrt(1 - (cloud->points[i].x * cloud->points[i].x)
                    - (cloud->points[i].y * cloud->points[i].y));
            else
                cloud->points[i].z = -sqrt(1 - (cloud->points[i].x * cloud->points[i].x)
                    - (cloud->points[i].y * cloud->points[i].y));
        }
        else
        {
            cloud->points[i].x = rand() / (RAND_MAX + 1.0);
            cloud->points[i].y = rand() / (RAND_MAX + 1.0);
            if (i % 5 == 0)
                cloud->points[i].z = rand() / (RAND_MAX + 1.0);
            else
                cloud->points[i].z = -1 * (cloud->points[i].x + cloud->points[i].y);
        }
    }

    std::vector<int> inliers;// 存储局内点集合中点的索引向量。

    // created RandomSampleConsensus object and compute the appropriated model
    pcl::SampleConsensusModelSphere<pcl::PointXYZ>::Ptr
        model_s(new pcl::SampleConsensusModelSphere<pcl::PointXYZ>(cloud)); // 针对球模型的对象
    pcl::SampleConsensusModelPlane<pcl::PointXYZ>::Ptr
        model_p(new pcl::SampleConsensusModelPlane<pcl::PointXYZ>(cloud));  // 针对平面模型的对象
    if (pcl::console::find_argument(argc, argv, "-f") >= 0)
    {
        // 随机估算对应的平面模型,并存储估计的局内点。
        pcl::RandomSampleConsensus<pcl::PointXYZ> ransac(model_p);
        ransac.setDistanceThreshold(.01);// 与平面距离小于0.01的点作为局内点考虑
        ransac.computeModel();  // 执行随机参数估计
        ransac.getInliers(inliers);  // 存储估计所得的局内点。
    }
    else if (pcl::console::find_argument(argc, argv, "-sf") >= 0)
    {
        // 随机估算对应的圆球模型,并存储估计的局内点。
        pcl::RandomSampleConsensus<pcl::PointXYZ> ransac(model_s);
        ransac.setDistanceThreshold(.01); // 与球面距离小于0.01的点作为局内点考虑
        ransac.computeModel(); // 执行随机参数估计
        ransac.getInliers(inliers); // 存储估计所得的局内点。
    }

    // copies all inliers of the model computed to another PointCloud
    pcl::copyPointCloud<pcl::PointXYZ>(*cloud, inliers, *final);

    // creates the visualization object and adds either our orignial cloud or all of the inliers
    // depending on the command line arguments specified.
    boost::shared_ptr<pcl::visualization::PCLVisualizer> viewer;
    if (pcl::console::find_argument(argc, argv, "-f") >= 0 || pcl::console::find_argument(argc, argv, "-sf") >= 0)
        viewer = simpleVis(final);
    else
        viewer = simpleVis(cloud);
    while (!viewer->wasStopped())
    {
        viewer->spinOnce(100);
        boost::this_thread::sleep(boost::posix_time::microseconds(100000));
    }
    return 0;
}



两两点云获取设备自动标定

自动标定

应用RANSAC算法实现基于球形靶标的自动标定方法,具体实现流程:

  1. 坐标系C1和C2分别表示 两个深度摄像头获取的点云数据的 局部坐标。
  2. 3个在摄像头公共视场中半径不同的小圆球作为标定时待识别标靶物体
  3. 圆球圆心(Q1, Q2, Q3)空间坐标用来计算坐标系之间的变换矩阵
  4. 需要在两个深度摄像头获取的点云数据中检测并识别出各个圆球
  5. 检测识别出3个圆球后,记载两组坐标系中拥有两组对应的3个坐标点
  6. 这样可以根据变换矩阵求解方程,来估计出变换矩阵了。
  7. 实现双深度摄像头的标定

代码梳理:

  • 数据获取模块
  • 直通滤波模块(ROI切割)
  • 平面检测及删除模块(ROI切割)
  • 圆球检测模块(检测定位)
  • 刚体变换矩阵估计模块

利用支架固定双深度摄像头,从不同的方向采集包含三个小球的场景点云。

数据获取模块

利用PCL中的Grabber接口分别从双深度摄像头采集场景点云。

直通滤波模块(ROI切割)

通过深度摄像头采集得到的原始场景点云通常包含Z轴方向(垂直于摄像头镜面的方向)2m以外的点云数据,而这些点云数据通常不是我们需要的,所以为了提高后续处理的效率,应用VoxelGrid滤波器剔除2m以外的点云数据。

平面检测及删除模块(ROI切割)

获取的直通滤波后的场景有近似于平面形状的地面点云,利用SACSegmentation检测地面所在的平面点云,并将其删除。利用SACSegmentation类的setModelType函数设置模型类型平面,并利用setMethodType函数设置方法为“随机采样一致性”。

圆球检测模块(检测定位)

  • 删除场景地面点云数据后,
  • 创建SACSegmentation对象用于检测圆球
  • 并设置包括检测形状方法SACSegmentation类相关参数。

然后利用如下代码循环检测场景内的圆球点云:

  • 利用SACSegmentation类使用随机采样一致性检测到一个圆球点云
  • 记录该圆球球心后,
  • 删除该圆球点云,
  • 继续利用采样一致性算法检测
  • 直到三个圆球表面点云都检测到
  • 记录三个圆球的球心
for (i = 0; i < 3; i++)//循环次数控制球个数
{
	// Segment the largest planar component from the remaining cloud
	pcl::ModelCoefficients coefficients2;

	seg2.setInputCloud(cloud_filtered);
	seg2.segment(*tmpinliers2, coefficients2);
	coefficients_all.push_back(coefficients2);


	if (tmpinliers2->indices.size() == 0)
	{
		std::cout << "Could not estimate a sphere model for the given dataset." << std::endl;
		break;
	}

	// Extract the planar inliers from the input cloud
	pcl::ExtractIndices<pcl::PointXYZRGB> extract2;
	extract2.setInputCloud(cloud_filtered);
	extract2.setIndices(tmpinliers2);
	extract2.setNegative(false);

	// Write the planar inliers to disk
	extract2.filter(*cloud_sphere);
	viewer.showCloud(cloud_sphere);//可视化显示最后一个小球
	std::cout << "PointCloud representing the sphere component: " << cloud_sphere->points.size() << " data points." << std::endl;
	std::cout << coefficients2 << std::endl;//打印各个球的四个参数
	// Remove the planar inliers, extract the rest
	extract2.setNegative(true);
	extract2.filter(*cloud_f2);
	cloud_filtered = cloud_f2;

}

刚体变换矩阵估计模块

  • 利用双深度摄像头分别采集包含圆球的场景点云后,经过上述模块的处理,得到了在两个摄像头局部坐标系下三个小球球心的坐标。(Q1,Q2,Q3)。
  • 接下来基于三组对应点,利用TransformationFromCorrespondences类估计变换矩阵
  • 利用transformationFromCorr类的add函数添加对应点
  • 利用getTransformation函数获取变换矩阵结果,
  • 实现双深度摄像头的标定
/*
 * www.pclcn.org
 *
 *
 */

#include <fstream>
#include <iostream>
#include <pcl/console/parse.h>
#include <pcl/filters/extract_indices.h>
#include <pcl/io/pcd_io.h>
#include <pcl/sample_consensus/ransac.h>
 //#include <pcl/sample_consensus/sac_model_plane.h>
#include <pcl/sample_consensus/sac_model_sphere.h>
//#include <pcl/visualization/pcl_visualizer.h>
#include <boost/thread/thread.hpp>
#include <pcl/cuda/io/cloud_to_pcl.h>
#include <pcl/cuda/io/disparity_to_cloud.h>
#include <pcl/io/openni_grabber.h>
#include <pcl/point_cloud.h>
#include <pcl/point_types.h>
#include <pcl/cuda/time_cpu.h>
//#include <boost/shared_ptr.hpp>
#include <pcl/visualization/cloud_viewer.h>
#include <pcl/ModelCoefficients.h>
//#include <pcl/point_types.h>
#include <pcl/io/pcd_io.h>
//#include <pcl/filters/extract_indices.h>
#include <pcl/filters/voxel_grid.h>
#include <pcl/features/normal_3d.h>
#include <pcl/kdtree/kdtree.h>
#include <pcl/sample_consensus/method_types.h>
#include <pcl/sample_consensus/model_types.h>
#include <pcl/segmentation/sac_segmentation.h>
#include <pcl/segmentation/extract_clusters.h>
#include<vector>

#include <pcl/common/transforms.h>
#include <pcl/common/transformation_from_correspondences.h>


#include <pcl/filters/passthrough.h>


using pcl::cuda::PointCloudAOS;
using pcl::cuda::Device;

class KinectViewerCuda
{
public:
	KinectViewerCuda(bool downsample) : viewer("KinectGrabber"), downsample_(downsample), global_(false) {}

	void cloud_cb_(const boost::shared_ptr<openni_wrapper::Image>& image, const boost::shared_ptr<openni_wrapper::DepthImage>& depth_image, float constant)
	{
		pcl::PointCloud<pcl::PointXYZRGB>::Ptr final(new pcl::PointCloud<pcl::PointXYZRGB>);
		PointCloudAOS<Device>::Ptr data;
		{
			pcl::cuda::ScopeTimeCPU t("time:");
			d2c.compute<Device>(depth_image, image, constant, data, downsample_);
		}

		pcl::PointCloud<pcl::PointXYZRGB>::Ptr output(new pcl::PointCloud<pcl::PointXYZRGB>);
		pcl::cuda::toPCL(*data, *output);
		cloud_ = output;

		pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud_filtered0(new pcl::PointCloud<pcl::PointXYZRGB>), cloud_filteredz(new pcl::PointCloud<pcl::PointXYZRGB>), cloud_filteredx(new pcl::PointCloud<pcl::PointXYZRGB>);
		//******************************************* 滤除距离远于2两米的点云,便于后续处理效率和质量
		pcl::PassThrough<pcl::PointXYZRGB> pass;
		pass.setInputCloud(output);
		pass.setFilterFieldName("z");
		pass.setFilterLimits(0.0, 1.7);
		pass.filter(*cloud_filteredz);
		pass.setInputCloud(cloud_filteredz);
		pass.setFilterFieldName("x");
		pass.setFilterLimits(-0.3, 0.3);
		pass.filter(*cloud_filteredx);
		pass.setInputCloud(cloud_filteredx);
		pass.setFilterFieldName("y");
		pass.setFilterLimits(-0.3, 0.3);
		pass.filter(*cloud_filtered0);
		//*********************************************处理前对点云进行下采样
		pcl::VoxelGrid<pcl::PointXYZRGB> vg;
		pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud_filtered(new pcl::PointCloud<pcl::PointXYZRGB>);
		vg.setInputCloud(cloud_filtered0);
		vg.setLeafSize(0.001f, 0.001f, 0.001f);
		vg.filter(*cloud_filtered);
		std::cout << "PointCloud after filtering has: " << cloud_filtered->points.size() << " data points." << std::endl; //*
	//cloud_filtered经过下采样处理的点云
  //******************************************检测剔除掉处在平面上的点云*/
		pcl::SACSegmentation<pcl::PointXYZRGB> seg;
		pcl::PointIndices::Ptr tmpinliers(new pcl::PointIndices);
		pcl::ModelCoefficients::Ptr coefficients(new pcl::ModelCoefficients);
		pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud_plane(new pcl::PointCloud<pcl::PointXYZRGB>());

		seg.setOptimizeCoefficients(true);
		seg.setModelType(pcl::SACMODEL_PLANE);
		seg.setMethodType(pcl::SAC_RANSAC);
		seg.setMaxIterations(100);
		seg.setDistanceThreshold(0.05);
		pcl::PointCloud<pcl::PointXYZRGB>::Ptr  cloud_f(new pcl::PointCloud<pcl::PointXYZRGB>);
		int i = 0, nr_points = (int)cloud_filtered->points.size();
		while (cloud_filtered->points.size() > 0.3 * nr_points)
		{
			// Segment the largest planar component from the remaining cloud
			seg.setInputCloud(cloud_filtered);
			seg.segment(*tmpinliers, *coefficients);
			//std::cout << *coefficients << std::endl;打印平面的四个参数
			if (tmpinliers->indices.size() == 0)
			{
				std::cout << "Could not estimate a planar model for the given dataset." << std::endl;
				break;
			}

			// Extract the planar inliers from the input cloud
			pcl::ExtractIndices<pcl::PointXYZRGB> extract;
			extract.setInputCloud(cloud_filtered);
			extract.setIndices(tmpinliers);
			extract.setNegative(false);

			// Write the planar inliers to disk
			extract.filter(*cloud_plane);
			std::cout << "PointCloud representing the planar component: " << cloud_plane->points.size() << " data points." << std::endl;

			// Remove the planar inliers, extract the rest
			extract.setNegative(true);
			extract.filter(*cloud_f);
			cloud_filtered = cloud_f;
		}

		output = cloud_filtered;
		//******************************************cloud_filtered为去除点云中平面集合的点云
		//******************************************开始检测球面 
		//多个球
		pcl::SACSegmentation<pcl::PointXYZRGB> seg2;
		pcl::PointIndices::Ptr tmpinliers2(new pcl::PointIndices);
		std::vector <pcl::ModelCoefficients> coefficients_all;

		pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud_sphere(new pcl::PointCloud<pcl::PointXYZRGB>());

		seg2.setOptimizeCoefficients(true);
		seg2.setModelType(pcl::SACMODEL_SPHERE);
		seg2.setMethodType(pcl::SAC_RANSAC);
		seg2.setMaxIterations(100);
		seg2.setDistanceThreshold(0.01);
		pcl::PointCloud<pcl::PointXYZRGB>::Ptr  cloud_f2(new pcl::PointCloud<pcl::PointXYZRGB>);
		//**************************************输入全局坐标
		for (i = 0; i < 3; i++)
		{
			if (global_ == false && i == 0)
			{
				std::cout << "\n请依次输入最近的球的球心对应的全局坐标位置x y z之间用空格或回车\n" << std::endl;
				std::cin >> xyz.x;
				std::cin >> xyz.y;
				std::cin >> xyz.z;
				std::cout << "			x=" << xyz.x << "			y=" << xyz.y << "			z=" << xyz.z << std::endl;
				std::cin.clear();
			}//最近的球
			if (global_ == false && i == 1)
			{
				std::cout << "\n请依次输入较远的球的球心对应的全局坐标位置x y z之间用空格或回车\n" << std::endl;
				std::cin >> xyz.x;
				std::cin >> xyz.y;
				std::cin >> xyz.z;
				std::cout << "			x=" << xyz.x << "			y=" << xyz.y << "			z=" << xyz.z << std::endl;
				std::cin.clear();
			}//第二近的球
			if (global_ == false && i == 2)
			{
				std::cout << "\n请依次输入最远的球的球心对应的全局坐标位置x y z之间用空格或回车\n" << std::endl;
				std::cin >> xyz.x;
				std::cin >> xyz.y;
				std::cin >> xyz.z;
				std::cout << "			x=" << xyz.x << "			y=" << xyz.y << "			z=" << xyz.z << std::endl;
				std::cin.clear();
				global_ = true;
			}//最远的球
			xyz_all.push_back(xyz);
		}

		for (i = 0; i < 3; i++)//循环次数控制球个数
		{
			// Segment the largest planar component from the remaining cloud
			pcl::ModelCoefficients coefficients2;

			seg2.setInputCloud(cloud_filtered);
			seg2.segment(*tmpinliers2, coefficients2);
			coefficients_all.push_back(coefficients2);


			if (tmpinliers2->indices.size() == 0)
			{
				std::cout << "Could not estimate a sphere model for the given dataset." << std::endl;
				break;
			}

			// Extract the planar inliers from the input cloud
			pcl::ExtractIndices<pcl::PointXYZRGB> extract2;
			extract2.setInputCloud(cloud_filtered);
			extract2.setIndices(tmpinliers2);
			extract2.setNegative(false);

			// Write the planar inliers to disk
			extract2.filter(*cloud_sphere);
			viewer.showCloud(cloud_sphere);//可视化显示最后一个小球
			std::cout << "PointCloud representing the sphere component: " << cloud_sphere->points.size() << " data points." << std::endl;
			std::cout << coefficients2 << std::endl;//打印各个球的四个参数
			// Remove the planar inliers, extract the rest
			extract2.setNegative(true);
			extract2.filter(*cloud_f2);
			cloud_filtered = cloud_f2;

		}

		// 完成三个球的球心检测,其在局部坐标系内的坐标存储在coefficients_all,输入的全局坐标在xyz_all
	   //****************************************************开始进行变换矩阵估计
		Eigen::Matrix4f transformationCorrespondence;
		pcl::TransformationFromCorrespondences transformationFromCorr;
		for (i = 0; i < coefficients_all.size(); i++)
		{
			Eigen::Vector3f from(coefficients_all.at(i).values[0],
				coefficients_all.at(i).values[1],
				coefficients_all.at(i).values[2]);

			Eigen::Vector3f  to(xyz_all.at(i).x,
				xyz_all.at(i).y,
				xyz_all.at(i).z);

			transformationFromCorr.add(from, to, 1.0);//all the same weight 

		}

		transformationCorrespondence = transformationFromCorr.getTransformation().matrix();
		std::cout << "\ntransformation from corresponding points is \n" << transformationCorrespondence << std::endl;

		std::cout << "\n如果您认为标定数据正确,则输入y,则会提示保存数据,否则输入n。\n" << std::endl;
		char userin;
		std::cin.clear();

		std::cin.get(userin);
		if (userin == 'y' || userin == 'Y') {

			std::string filename;
			std::cin.clear();
			std::cout << "\n请输入文件名。\n" << std::endl;
			std::cin >> filename;
			std::ofstream myfile(filename, std::ios::binary);
			myfile.write((char*)transformationCorrespondence.data(), transformationCorrespondence.size() * sizeof(float));
			myfile.close();
		}
		else
		{
			std::cout << "\n程序继续。\n" << std::endl;
		}


	}

	void run(const std::string& device_id)
	{
		pcl::Grabber* interface = new pcl::OpenNIGrabber(device_id);

		boost::function<void(const boost::shared_ptr<openni_wrapper::Image>& image, const boost::shared_ptr<openni_wrapper::DepthImage>& depth_image, float)>
			f = boost::bind(&KinectViewerCuda::cloud_cb_, this, _1, _2, _3);

		boost::signals2::connection c = interface->registerCallback(f);

		interface->start();

		while (true)
		{
			pcl_sleep(1);
		}

		interface->stop();
	}

	pcl::cuda::DisparityToCloud d2c;
	pcl::visualization::CloudViewer viewer;
	boost::mutex mutex_;
	bool downsample_;
	pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud_;
	bool global_;
	std::vector <pcl::PointXYZ> xyz_all;
	pcl::PointXYZ xyz;
};

int main(int argc, char** argv)
{
	std::string device_id = "#1";

	int downsample = false;
	if (argc >= 2)
	{
		device_id = argv[1];
	}
	if (argc >= 3)
	{
		downsample = atoi(argv[2]);
	}
	KinectViewerCuda v(downsample);
	v.run(device_id);
	std::cout << "/n正常退出/n" << std::endl;
	return 0;
}

检测的精度与效果同圆球材质采集距离有关,碍于设备的精度,需要反复调整摄像头的角度和圆球的位置以提高获取数据的质量,方可达到良好的检测效果。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

PCL笔记九:采样一致性 的相关文章

  • 运放失真罪魁祸首之压摆率和增益带宽积

    1 什么叫压摆率 xff1f 做1pps驱动电路 1 xff0c 1PPS xff1a 秒脉冲英文全称 xff1a Pulse Per Second 1pps概念 要求上升沿 5ns xff0c FPGA输出的信号用运放跟随增强驱动后 xf
  • 电源反馈电路设计经验(PC817光耦隔离+TL431)

    身边的很多小伙伴好像都对电源的反馈电路的计算以及原理不太了解 xff0c 这里给大家系统的讲解一下 xff1a 废话不都说 xff0c 咱们直接上干货 在分析电路前需要注意的关键点 1 光耦的输入端 xff08 二极管端 xff09 的电流
  • 一分钟带你了解常用的V-I、I-V转换电路

    下面我将给大家介绍6种常用的V I I V转换电路 1 0 xff0d 5V 0 xff0d 10mA的V I变换电路 图1是由运放和阻容等元件组成的V I变换电路 xff0c 能将0 5V的直流电压信号线性地转换成0 xff0d 10mA
  • 奈奎斯特采样定理-为什么采样率需要时被测信号最高频率的两倍

    奈奎斯特采样定理 采样定理在1928年由美国电信工程师H 奈奎斯特首先提出来的 xff0c 因此称为奈奎斯特采样定理 1933年由苏联工程师科捷利尼科夫首次用公式严格地表述这一定理 xff0c 因此在苏联文献中称为科捷利尼科夫采样定理 19
  • 傅里叶变换简单解析

    这里有两个频率 一个是信号本身的频率 位1 3sec 一个下面矢量绕圆的频率 为1 0 79sec 下面这个频率是我们可以改变的 这就是所谓的窗 即我们选取多长的一段信号用来进行傅里叶变换分析 当截取的窗的周期等于信号周期1 3sec时 就
  • 组合逻辑与时序逻辑

    我前段时间对这两个逻辑的概念有点混淆 xff0c 所以就百度查看了一下 xff0c 排名第一的结果就出现了这样的解释 xff1a 数字电路根据逻辑功能的不同特点 xff0c 可以分成两大类 xff0c 一类叫组合逻辑电路 xff08 简称组
  • AD2020如何安装库

    首先快捷键op打开下面界面 然后找到库的位置安装就可以了
  • 白话Android音频系统原理(上)(标贝科技)

    白话Android音频系统原理 xff08 上 xff09 一 基本原理二 初始化准备工作三 播放流程四 录音流程 标贝科技 https ai data baker com source 61 qwer12 填写邀请码fwwqgs xff0
  • keil 工程头文件包涵及结构体重定义问题

    最近在写一个太阳能电池控制板的项目 xff0c 这两天真是收获颇丰 xff0c 把指针又从新学了一遍 xff0c 还是项目坑死人啊啊啊啊啊 下面是我在建立工程时遇到的问题 xff1a 1 头文件包涵问题 xff1a 原先写的程序都是小程序
  • Openmediavault 4.1.3 镜像下载地址

    Openmediavault 4 1 3 镜像下载地址 https nchc dl sourceforge net project openmediavault 4 1 3 openmediavault 4 1 3 amd64 iso
  • 【leetcode】【77】Combinations

    一 问题描述 Given two integers n and k return all possible combinations of k numbers out of 1 n For example If n 61 4 and k 6
  • 激光雷达介绍

    全球汽车行业正在进行自动化变革 xff0c 这将彻底改变交通运输的安全和效率水平 戴姆勒在S级豪华车型中引入L3级自动驾驶 xff08 L3 xff0c 在特定条件下自动驾驶 xff0c 人类驾驶员一旦被请求就会随时接管 xff09 是自动
  • get请求 包括username和password

    方法一 xff1a public static String wisdomNetMessagePlat String sn String encoding throws Exception String result 61 34 34 St
  • Arduino控制L298N

    一般做智能小车或者DIY开发的童鞋需要用到Arduino这狂开发板 xff0c 作为一名好奇心很强的软件开发人员 xff0c 我自然也想尝尝鲜特别是利用Arduino做一些人机交互的项目 xff0c 如果结合了Kinect AR增强现实 x
  • gitlab打tag的作用,以及如何基于tag切分支

    在 GitLab 中 xff0c Tag 是将 Git 项目中特定的提交标记为版本的一种方式 Tag 能够用于标记发布的版本 xff0c 以便于对代码的每个版本进行管理和追踪 打 Tag 的作用主要有以下几点 xff1a 标记版本发布 xf
  • 解析包含用户名密码的FTPURL

    include lt stdio h gt include lt string h gt void parse ftpurl char url char user char passwd char ipaddr uint port char
  • nvidia jetson xavier nx Deepstream Yolov3示例模型运行

    最近在玩nvidia jetson xavier nx的板子 xff0c 特在此做一些笔记 1 进入sample目录 cd opt nvidia deepstream deepstream sources objectDetector Yo
  • go-mysql-elasticsearch+mysql 同步 ElasticSearch(标贝科技)

    标贝科技 https ai data baker com source 61 qwer12 填写邀请码fwwqgs xff0c 每日免费调用量还可以翻倍 一 Elasticsearch xff1a https www elastic co
  • Intel Realsense D400系列相机自校准细节,减少踩坑!!

    自校准 xff08 对任意环境非白墙 注意距离0 4 2米之间 xff0c 深度 xff08 相机拍摄内容中物体所占空间 xff09 50 xff09 xff1a 打开Intel realSense viewer xff1a 连接设备 xf
  • QT实现NMea截取指定片段字符串——$GNGGA(小白第一次编,后期有机会完善代码,高勿)

    总体思路 xff1a 1 读取所有数据 xff08 readAll xff08 xff09 xff09 QByteArray readbuf 61 serial gt readAll 2 查找标志位 xff08 QString中使用inde

随机推荐

  • linux c&&c++关于赋值问题(char*传给另一个char*)

    仅列出四种 xff0c 欢迎补充 xff01 方法一 xff1a 直接 char buf1 61 char buf2 xff0c xff08 xff01 xff01 xff01 这里的赋值是将buf2的地址赋给了buf1 xff0c 此后b
  • B6AC使用说明

    1 接线方式 接线如下图所示 xff0c 如果电池是XT60 xff0c 可以用接鳄鱼夹子的T插夹在XT60的接口上 2 设置方法 2 1 锂电池充电 1 xff09 Batt Type gt 切换选择 LiPo BATT gt Enter
  • 整数转换为字符串

    include lt stdio h gt 反转字符串 char reverse char s char temp char p 61 s p指向s的头部 char q 61 s q指向s的尾部 while q 43 43 q q 交换移动
  • 【工具使用】Modbus Poll软件使用详解

    软件介绍 Modbus Poll是一个模拟Modbus协议主机的上位机软件 xff0c 主要用于模拟测试跟其他从机设备通信的过程 与之成套存在的另一个软件 Modbus Slave xff0c 则是模拟Modbus协议从机的上位机软件 该软
  • c++下程序的运行(第3方库的安装及安装gdal)

    C 43 43 安装第三方库 1 概览2 编译 xff0c 首先要熟悉程序编译过程 xff08 预处理 gt 编译 gt 汇编 gt 链接 xff09 2 1 编译流程4步2 1 1 1 预处理2 1 2 2 编译2 1 3 3 汇编2 1
  • 制作IEC101/104报文解析工具

    文章目录 准备 一 IEC101 104协议 二 制作解析工具步骤 1 建立协议族 2 建立协议 3 建立术语 4 建立报文块 5 建立报文解析规则 总结 准备 这里是基于 在线解析二进制报文 文章中介绍的物联网助手来快速搭建IEC101
  • PHP各版本技术特性(标贝科技)

    文章目录 PHP8JIT流程配置测试小结 match表达式 PHP7HugepageHugepage简介Hugepage配置 Opcache file cacheOpcache file cache简介Opcache file cache配
  • vue、springboot集成websocket跨域问题解决

    由于浏览器连接的是服务器上的websocket xff0c 导致出现了如下跨域错误 xff1a The value of the 39 Access Control Allow Origin 39 header in the respons
  • 动态库学习

    总结一 xff1a 动态库 前言 我们知道程序编译链接经常使用动态 xff0c 同时我们可能还知道 xff0c 动态库时程序运行时加载的 但是动态库到底有什么作用 xff0c 如何生成 如何加载等 xff0c 我们却很少关注 接下来 xff
  • Python学习第一次问题笔记:vscode 运行python代码没有输出(Code runner)

    目录 问题 xff1a 解决方案 xff1a 原因一 xff1a 原因二 xff1a 问题 xff1a 新安装Visual Studio Code后 xff0c 在其上进行第一次代码运行时 xff0c 会出现只在终端中显示代码结果 xff0
  • 标准差分进化算法matlab程序实现

    终于开始了研究生阶段的学习生活 xff0c 繁琐的入学相关事宜也处理得差不多了 在9月5号 xff0c 我们实验室召开了第一次小组会议 xff0c 导师也相继给我们指定了各自的研究方向 xff0c 不出意外 xff0c 我接下来将围绕智能优
  • 《图解深度学习》学习笔记(一)

    第一章 绪论 监督学习 xff1a 需要基于输入数据及其期望输出 xff0c 通过训练从数据中提取通用信息或特征信息 特征值 xff0c 以此得到预测模型 这里的特征值是指根据颜色和边缘等认为定义的提取方法从训练样本中提取的信息 xff08
  • 3D点云基础知识(一)-初始入门-知乎整理(一)

    本文基于如下知乎文章 xff0c 调整部分内容整理输出 xff0c 一为梳理基础知识 xff0c 二为致敬原作 作者 xff1a Mr 苍雪 链接 xff1a https zhuanlan zhihu com p 344635951 来源
  • Open3D处理点云数据(一)点云文件读取、写入、显示

    import open3d as o3d import numpy as np pcd 61 o3d geometry PointCloud pcd类型的数据 np points 61 np random rand 100 3 随机生成点云
  • Ubuntu非管理员root安装ninja:解决RuntimeError: Ninja is required to load C++ extensions错误

    服务器是共用的 xff0c 所以无法使用sudo apt get 命令快速安装 只能自己下载git上的源代码然后编译 xff0c 装在自己的私人目录下 否则 sudo apt get 安装很容易 xff0c 55555555 Ninja是一
  • Elasticsearch相似度算分TF-IDF BM25(标贝科技)

    欢迎体验标贝语音开放平台 地址 xff1a https ai data baker com source 61 qaz123 xff08 注 xff1a 填写邀请码hi25d7 xff0c 每日免费调用量还可以翻倍 xff09 控制相关度
  • 【K210】K210学习笔记五——串口通信

    K210 K210学习笔记五 串口通信 前言K210如何进行串口通信K210串口配置K210串口发送相关定义K210串口接收相关定义K210串口发送接收测试 完整源码 前言 本人大四学生 xff0c 电赛生涯已经走到尽头 xff0c 一路上
  • C++类与对象笔记十二:运算符重载三:递增运算符重载

    先看看系统自带的递增运算符 43 43 递增运算符重载 43 43 xff1b a 43 43 43 43 a int a 61 10 cout lt lt 43 43 a lt lt endl 11 先加 xff0c 后输出 cout l
  • C++类与对象笔记十四:多态六:虚析构和纯虚析构

    多态使用时 xff0c 如果子类中有属性开辟到堆区 xff0c 那么父类指针在释放时无法调用子类的析构代码 即 xff1a 父类指针无法调用子类实例对象内的析构函数 造成堆区内存泄漏 解决方案 将父类中的析构函数改为虚析构或者纯虚析构 虚析
  • PCL笔记九:采样一致性

    模型拟合 xff1f 采样一致性的目的 xff1a 用于排除错误的样本 基于采样一致性算法的应用主要是对点云进行分割 xff0c 根据不同设定的几何模型 xff0c 估计对应的几何模型的参数 xff0c 在一定允许误差范围内分割出在模型上的