PCL拟合并绘制平面(二)

使用RANSAC拟合点云平面

  • 1、C++实现
  • 2、效果图

普通的点云平面拟合方式在一般情况下可以得到较好的平面拟合效果,但是容易出现平面拟合错误或是拟合的平面不是最优的情况。此时就需要根据自己的实际使用情况,调整平面拟合的迭代次数以及收敛条件。

使用RANSAC迭代的方式,获取所有迭代过程中的最优平面,虽然速度上不如普通的平面拟合方式,但是准确度有一定的提升。下面是具体实现的方式:

1、C++实现

随机处理函数:

	//随机处理
	static bool m_already_seeded = false;
	inline void SeedRand(int seed)
	{
		srand(seed);
	}
	inline void SeedRandOnce(int seed)
	{
		//if (!m_already_seeded)
		//{
		SeedRand(seed);
		m_already_seeded = true;
		//}
	}
	inline int RandomInt(int min, int max)
	{
		int d = max - min + 1;
		return int(((double)rand() / ((double)RAND_MAX + 1.0)) * d) + min;
	}

主要函数实现部分:

	//部分用到的参数的初始值 mParams.VoxelSize=3.0 mParams.MaxModelFitIterations=2000
	//mParams.SegDistanceThreshold=0.3
	//点云的单位是mm
	void PlaneFittingRansac(PointCloudT::Ptr cloudsource, PointCloudT::Ptr cloudfiltered, PointCloudT::Ptr cloudseg, pcl::ModelCoefficients::Ptr coefficients)
	{
		//点云下采样
		PointCloudT::Ptr cloudDownSampling;
		cloudDownSampling.reset(new(PointCloudT));
		if (cloudsource->points.size() > 0 && cloudsource->points.size() < 20000)
		{
			cloudDownSampling = cloudsource;
		}
		else if (cloudsource->points.size() > 20000 && cloudsource->points.size() < 60000)
		{
			mParams.VoxelSize = 1.0;
			PointCloudVoxelGrid(cloudsource, cloudDownSampling, mParams.VoxelSize);
		}
		else if (cloudsource->points.size() > 60000 && cloudsource->points.size() < 100000)
		{
			mParams.VoxelSize = 2.0;
			PointCloudVoxelGrid(cloudsource, cloudDownSampling, mParams.VoxelSize);
		}
		else
		{
			PointCloudVoxelGrid(cloudsource, cloudDownSampling, mParams.VoxelSize);
		}

		int PointsNum = 6;
		std::vector<std::vector<size_t>> mvSets = std::vector<std::vector<size_t>>(mParams.MaxModelFitIterations, std::vector<size_t>(PointsNum, 0));
		//点的对数
		const int N = cloudDownSampling->points.size();
		//新建一个容器vAllIndices存储点云索引,并预分配空间
		std::vector<size_t> vAllIndices;
		vAllIndices.reserve(N);
		//在RANSAC的某次迭代中,还可以被抽取来作为数据样本的索引,所以这里起的名字叫做可用的索引
		std::vector<size_t> vAvailableIndices;
		//初始化所有特征点对的索引,索引值0到N-1
		for (int i = 0; i < N; i++)
			vAllIndices.push_back(i);

		//用于进行随机数据样本采样,设置随机数种子
		SeedRandOnce(0);
		//开始每一次的迭代 
		for (int it = 0; it < mParams.MaxModelFitIterations; it++)
		{
			//迭代开始的时候,所有的点都是可用的
			vAvailableIndices = vAllIndices;
			//选择最小的数据样本集
			for (size_t j = 0; j < PointsNum; j++)
			{
				// 随机产生一对点的id,范围从0到N-1
				int randi = RandomInt(0, vAvailableIndices.size() - 1);
				// idx表示哪一个索引对应的点对被选中
				int idx = vAvailableIndices[randi];
				//将本次迭代这个选中的第j个特征点对的索引添加到mvSets中
				mvSets[it][j] = idx;
				// 由于这对点在本次迭代中已经被使用了,所以我们为了避免再次抽到这个点,就在"点的可选列表"中,
				// 将这个点原来所在的位置用vector最后一个元素的信息覆盖,并且删除尾部的元素
				// 这样就相当于将这个点的信息从"点的可用列表"中直接删除了
				vAvailableIndices[randi] = vAvailableIndices.back();
				vAvailableIndices.pop_back();
			}//依次提取出6个点
		}//迭代mMaxIterations次,选取各自迭代时需要用到的最小数据集

		//某次迭代中,点云的坐标
		PointCloudT::Ptr cloudIn;
		cloudIn.reset(new(PointCloudT));
		//保存最优的平面
		double fError = 0.0;
		double plane[4] = { 0 };
		coefficients->values.resize(4);
		//最优的匹配
		int BestMatch = 0;
		pcl::PointIndices::Ptr inliers;
		inliers.reset(new pcl::PointIndices());
		//下面进行每次的RANSAC迭代
		for (int it = 0; it < mParams.MaxModelFitIterations; it++)
		{
			//选择6个点对进行迭代
			for (size_t j = 0; j < PointsNum; j++)
			{
				//从mvSets中获取当前次迭代的某个特征点对的索引信息
				int idx = mvSets[it][j];
				pcl::PointXYZ pointcloud = cloudDownSampling->points[idx];
				if (!isFinite(pointcloud)) //不是有效点
					continue;
				cloudIn->push_back(pointcloud);
			}

			if (!isSampleGood(cloudIn)) //不是好的平面点(构成直线了)
				continue;

			PlaneFitting(cloudIn, plane, fError);
			//获得内点的数量和索引
			std::vector<int> TempInlier;
			for (int i = 0; i < cloudDownSampling->points.size(); i++)
			{
				pcl::PointXYZ pointcloud = cloudDownSampling->points[i];
				if (!isFinite(pointcloud)) //不是有效点
					continue;
				//计算误差
				double det = sqrt(plane[0] * plane[0] + plane[1] * plane[1] + plane[2] * plane[2]);
				double err = abs(plane[0] * cloudDownSampling->points[i].x + plane[1] * cloudDownSampling->points[i].y + plane[2] * cloudDownSampling->points[i].z + plane[3]) / det;
				if (err < mParams.SegDistanceThreshold)
					TempInlier.push_back(i);
			}
			//更新最优方程
			if (TempInlier.size() > BestMatch)
			{
				BestMatch = TempInlier.size();
				inliers->indices = TempInlier;
				coefficients->values[0] = plane[0];
				coefficients->values[1] = plane[1];
				coefficients->values[2] = plane[2];
				coefficients->values[3] = plane[3];
			}
			cloudIn.reset(new(PointCloudT));
		}
		cloudIn.reset();
		//创建点云提取对象
		pcl::ExtractIndices<pcl::PointXYZ> extract;
		extract.setInputCloud(cloudDownSampling);
		extract.setIndices(inliers);
		//提取内点
		extract.setNegative(false);
		extract.filter(*cloudseg);
		//提取外点
		extract.setNegative(true);
		extract.filter(*cloudfiltered);
		inliers.reset();

		//不优化
		if (mParams.RefinePlane == 0)
			return;
		//平面系数优化(这一步需要ceres库,如果没有,直接屏蔽就好)
		plane[0] = coefficients->values[0];
		plane[1] = coefficients->values[1];
		plane[2] = coefficients->values[2];
		plane[3] = coefficients->values[3];
		std::vector<cv::Point3f> v3dpointsPlane;
		for (int i = 0; i < cloudseg->points.size(); i++)
		{
			pcl::PointXYZ pointcloud = cloudseg->points[i];
			if (!isFinite(pointcloud)) //不是有效点
				continue;
			v3dpointsPlane.push_back(cv::Point3f(pointcloud.x, pointcloud.y, pointcloud.z));
		}
		optimizer.BundleAdjustmentPlane(v3dpointsPlane, plane);
		//归一化存储
		double det = sqrt(plane[0] * plane[0] + plane[1] * plane[1] + plane[2] * plane[2]);
		coefficients->values[0] = plane[0] / det;
		coefficients->values[1] = plane[1] / det;
		coefficients->values[2] = plane[2] / det;
		coefficients->values[3] = plane[3] / det;
	}

体素滤波:

	void PointCloudVoxelGrid(PointCloudT::Ptr cloudsource, PointCloudT::Ptr cloudfiltered, float voxelsize)
	{
		pcl::VoxelGrid<PointT> sor;							  //创建滤波对象
		sor.setInputCloud(cloudsource);                       //设置需要过滤的点云给滤波对象
		sor.setLeafSize(voxelsize, voxelsize, voxelsize);     //设置滤波时创建的体素体积为1cm的立方体
		sor.filter(*cloudfiltered);                           //执行滤波处理,存储输出
	}

判断是否前3个点共线:

	bool isSampleGood(PointCloudT::Ptr cloudsource)
	{
		// Need an extra check in case the sample selection is empty
		if (cloudsource->points.size() < 3)
			return (false);
		// Get the values at the two points
		pcl::Array4fMap p0 = cloudsource->points[0].getArray4fMap();
		pcl::Array4fMap p1 = cloudsource->points[1].getArray4fMap();
		pcl::Array4fMap p2 = cloudsource->points[2].getArray4fMap();

		Eigen::Array4f dy1dy2 = (p1 - p0) / (p2 - p0);

		return ((dy1dy2[0] != dy1dy2[1]) || (dy1dy2[2] != dy1dy2[1]));
	}

平面拟合:

	void PlaneFitting(PointCloudT::Ptr cloudIn, double* plane, double& fError)
	{
		CvMat* points = cvCreateMat(cloudIn->points.size(), 3, CV_32FC1);
		for (int i = 0; i < cloudIn->points.size(); ++i)
		{
			cvmSet(points, i, 0, cloudIn->points[i].x);
			cvmSet(points, i, 1, cloudIn->points[i].y);
			cvmSet(points, i, 2, cloudIn->points[i].z);
		}

		int nrows = points->rows;
		int ncols = points->cols;
		int type = points->type;
		CvMat* centroid = cvCreateMat(1, ncols, type);
		cvSet(centroid, cvScalar(0));
		for (int c = 0; c < ncols; c++)
		{
			for (int r = 0; r < nrows; r++)
			{
				centroid->data.fl[c] += points->data.fl[ncols*r + c];
			}
			centroid->data.fl[c] /= nrows;
		}

		// Subtract geometric centroid from each point.  
		CvMat* points2 = cvCreateMat(nrows, ncols, type);
		for (int r = 0; r < nrows; r++)
			for (int c = 0; c < ncols; c++)
				points2->data.fl[ncols*r + c] = points->data.fl[ncols*r + c] - centroid->data.fl[c];

		// Evaluate SVD of covariance matrix.  
		CvMat* A = cvCreateMat(ncols, ncols, type);
		CvMat* W = cvCreateMat(ncols, ncols, type);
		CvMat* V = cvCreateMat(ncols, ncols, type);
		cvGEMM(points2, points, 1, NULL, 0, A, CV_GEMM_A_T);
		cvSVD(A, W, NULL, V, CV_SVD_V_T);

		// Assign plane coefficients by singular vector corresponding to smallest singular value.  
		plane[ncols] = 0;
		for (int c = 0; c < ncols; c++)
		{
			plane[c] = V->data.fl[ncols*(ncols - 1) + c];
			plane[ncols] += plane[c] * centroid->data.fl[c];
		}

		//计算拟合误差
		//Ax+By+Cz=D A=plane[0],B=plane[1],C=plane[2],D=plane[3]
		double sum_error = 0;
		double det = sqrt(plane[0] * plane[0] + plane[1] * plane[1] + plane[2] * plane[2]);
		for (int i = 0; i < cloudIn->points.size(); ++i)
		{
			double  a = cloudIn->points[i].x;
			double  b = cloudIn->points[i].y;
			double  c = cloudIn->points[i].z;
			double err = abs(plane[0] * a + plane[1] * b + plane[2] * c - plane[3]) / det;
			sum_error = sum_error + err;
		}
		fError = sum_error / cloudIn->points.size();

		//归一化平面向量,并将方程修改为Ax+By+Cz+D=0
		plane[0] = plane[0] / det;
		plane[1] = plane[1] / det;
		plane[2] = plane[2] / det;
		plane[3] = -plane[3] / det;

		cvReleaseMat(&points);
		cvReleaseMat(&points2);
		cvReleaseMat(&A);
		cvReleaseMat(&W);
		cvReleaseMat(&V);
	}

平面拟合的另一种方式:OpenCV最小二乘法拟合空间平面

2、效果图

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/492925.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

PHPCMS v9城市分站插件

PHPCMS自带的有多站点功能&#xff0c;但是用过的朋友都知道&#xff0c;自带的多站点功能有很多的不方便之处&#xff0c;例如站点栏目没法公用&#xff0c;每个站点都需要创建模型、每个站点都需要单独添加内容&#xff0c;还有站点必须静态化。如果你内容很多这些功能当然无…

智慧公厕,运用大数据提升公共厕所管理水平

在现代社会&#xff0c;科技的发展给我们带来了诸多便利&#xff0c;而智慧公厕就是其中之一。智慧公厕运用数据和技术&#xff0c;提升公共厕所的管理水平&#xff0c;为社会生活服务。本文将以智慧公厕源头实力厂家广州中期科技有限公司&#xff0c;遍布全国的众多标杆性案例…

数据结构-----栈、顺序栈、链栈

在软件应用中&#xff0c;栈这种后进先出数据结构的应用是非常普遍的。比如用浏览器上网时&#xff0c;不管什么浏览器都有一个“后退”键&#xff0c;你点击后可以按访问顺序的逆序加载浏览过的网页。即使从一个网页开始&#xff0c;连续点了几十个链接跳转&#xff0c;你点“…

Vtk裁剪功能之平面裁剪vtkClipClosedSurface(vtk小记)

1.原理分析 对你的三维图形&#xff0c;使用一个平面切下去&#xff0c;然后保留一半。 确定一个平面&#xff1a;使用法向量和一个三维坐标点可以确定一个平面 原始图像 切一刀 切两刀&#xff0c;又一半 切三刀&#xff0c;又一半 源代码 #include <vtkActor.h> #i…

医院同步时钟系统的耐用性与可靠性

医院同步时钟系统的耐用性与可靠性是医疗机构管理中至关重要的一环。随着医疗技术的不断发展和医院服务水平的提升&#xff0c;同步时钟系统在医院中的作用也变得愈发重要。在医院环境中&#xff0c;时间的准确性对于医疗过程中的各个环节都至关重要&#xff0c;从手术安排到用…

Python基础教程:轻松入门

基础 注释 #这是单行注释 这是多行注释&#xff0c;使用单引号。 这是多行注释&#xff0c;使用单引号。 这是多行注释&#xff0c;使用单引号。 """ 这是多行注释&#xff0c;使用双引号。 这是多行注释&#xff0c;使用双引号。 这是多行注释&#xff0c;使…

手把手教你 - JMeter压力测试

前言 压力测试是每一个Web应用程序上线之前都需要做的一个测试&#xff0c;他可以帮助我们发现系统中的瓶颈问题&#xff0c;减少发布到生产环境后出问题的几率&#xff1b;预估系统的承载能力&#xff0c;使我们能根据其做出一些应对措施。所以压力测试是一个非常重要的步骤&…

YOLOv9改进策略:注意力机制 | 动态稀疏注意力的双层路由方法BiLevelRoutingAttention | CVPR2023

&#x1f4a1;&#x1f4a1;&#x1f4a1;本文改进内容&#xff1a; CVPR2023 动态稀疏注意力的双层路由方法BiLevelRoutingAttention&#xff0c;强烈推荐&#xff0c;涨点很不错&#xff0c;同时被各个领域的魔改次数甚多&#xff0c;侧面验证了性能。 &#x1f4a1;&#x1…

html2canvas 请求阿里云oss图片跨域问题解决

1. html2canvas设置 useCORS:true html2canvas(this.$refs.qrcode, {useCORS:true,}).then(canvas > {// 转成图片&#xff0c;生成图片地址let oImg new Image()oImg canvas.toDataURL(image/png) // 导出图片var oA document.createElement("a")oA.download…

大模型落地实战指南:从选择到训练,深度解析显卡选型、模型训练技、模型选择巧及AI未来展望—打造AI应用新篇章

0.前言大模型发展史 早期阶段&#xff08;1950s~1980s&#xff09; 在1950年代初期&#xff0c;人们开始尝试使用计算机处理自然语言文本。然而&#xff0c;由于当时的计算机处理能力非常有限&#xff0c;很难处理自然语言中的复杂语法和语义。随着技术的发展&#xff0c;自然…

Codeup_1132:问题 A: 最长公共子序列

目录 Problem DescriptionInputOutputSample InputSample Output原题链接解题思路代码实现&#xff08;C&#xff09; Problem Description 给你一个序列X和另一个序列Z&#xff0c;当Z中的所有元素都在X中存在&#xff0c;并且在X中的下标顺序是严格递增的&#xff0c;那么就…

2024游泳耳机哪个牌子好?分析测评四大热门游泳耳机

随着科技的不断发展&#xff0c;游泳耳机已经成为游泳爱好者们在水中畅游时的最佳伴侣。近年来游泳耳机市场涌现出了众多品牌和产品&#xff0c;让人眼花缭乱。为了帮助大家挑选到最适合自己的游泳耳机&#xff0c;我们特意对市面上四大热门游泳耳机进行了详细的分析测评&#…

Linux 系统Centos7.0记录安装Docker和安装jdk环境完整教程(建议收藏备用)

Linux 系统Centos7.0记录安装Docker和安装jdk环境完整教程&#xff08;建议收藏备用&#xff09; 一、安装前准备工作 1.1 查看服务器系统版本以及内核版本 cat /etc/redhat-release1.2 查看服务器内核版本 uname -r这里我们使用的是CentOS 7.9 系统&#xff0c;内核版本为…

短信系统开发注意事项|网页版短信后台

在开发短信系统时&#xff0c;有一些重要的注意事项需要考虑&#xff0c;以确保系统的稳定性、安全性和功能完整性。以下是一些开发短信系统时需要注意的事项&#xff1a; 合规性和法律要求&#xff1a;确保短信系统的开发符合当地法律法规和通信行业规定&#xff0c;包括用户隐…

【STM32嵌入式系统设计与开发】——12IWDG(独立看门狗应用)

这里写目录标题 一、任务描述二、任务实施1、ActiveBeep工程文件夹创建2、函数编辑&#xff08;1&#xff09;主函数编辑&#xff08;2&#xff09;USART1初始化函数(usart1_init())&#xff08;3&#xff09;USART数据发送函数&#xff08; USART1_Send_Data&#xff08;&…

【漏洞复现】WordPress Automatic Plugin 任意文件下载漏洞(CVE-2024-27954)

0x01 产品简介 WordPress Automatic Plugin是一款功能强大的WordPress插件&#xff0c;旨在帮助用户自动采集并发布内容。这款插件支持从各种来源自动采集内容&#xff0c;包括但不限于RSS源、网站、YouTube、eBay等。用户可以根据自己的需求设置采集规则&#xff0c;选择要采…

大数据Hadoop入门04 ——【HDFS shell操作】

一、HDSF shell命令行解释说明 1、介绍 命令行界面&#xff08;英语: command-line interface&#xff0c;缩写: CLl)&#xff0c;是指用户通过键盘输入指令&#xff0c;计算机接收到指令后&#xff0c;予以执行一种人际交互方式。Hadoop提供了文件系统的shell命令行客户端:…

【收集】好玩的技术

文章目录 IP-Adapter-FaceID IP-Adapter-FaceID https://huggingface.co/h94/IP-Adapter-FaceID

http模块—http请求练习

题目要求&#xff1a;搭建如下http服务&#xff1a; 1.当浏览器向我们的服务器发送请求时&#xff0c;当请求类型是get请求&#xff0c;请求的url路径地址是/login。响应体结果是登录页面 2.当浏览器向我们的服务器发送请求时&#xff0c;当请求类型是get请求&#xff0c;请求…

【物联网开源平台】tingsboard安装与编译

别看这篇了&#xff0c;这篇就当我的一个记录&#xff0c;我有空我再写过一篇&#xff0c;编译的时候出现了一个错误&#xff0c;然后我针对那一个错误执行了一个命令&#xff0c;出现了绿色的succes,我就以为整个tingsboard项目编译成功了&#xff0c;后面发现的时候&#xff…