PCL拟合并绘制平面(二)

news2024/9/28 15:29:00

使用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、效果图

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

浏览器工作原理与实践--调用栈:为什么JavaScript代码会出现栈溢出

在上篇文章中&#xff0c;我们讲到了&#xff0c;当一段代码被执行时&#xff0c;JavaScript引擎先会对其进行编译&#xff0c;并创建执行上下文。但是并没有明确说明到底什么样的代码才算符合规范。 那么接下来我们就来明确下&#xff0c;哪些情况下代码才算是“一段”代码&am…

TheMoon 恶意软件短时间感染 6,000 台华硕路由器以获取代理服务

文章目录 针对华硕路由器Faceless代理服务预防措施 一种名为"TheMoon"的新变种恶意软件僵尸网络已经被发现正在侵入全球88个国家数千台过时的小型办公室与家庭办公室(SOHO)路由器以及物联网设备。 "TheMoon"与“Faceless”代理服务有关联&#xff0c;该服务…

46秒AI生成真人视频爆火,遭在线打假「换口型、声音」

ChatGPT狂飙160天&#xff0c;世界已经不是之前的样子。 新建了人工智能中文站 每天给大家更新可用的国内可用chatGPT资源 更多资源欢迎关注 是炒作还是真正的 AI 视频能力进化&#xff1f; AI 生成视频已经发展到这个程度了吗&#xff1f; 前段时间&#xff0c;英国王室凯特…

每天能提醒自己做事的app有哪个?

在忙碌的日常生活和工作中&#xff0c;我们时常面临各种任务和琐事。一旦处理不及时&#xff0c;很容易导致遗忘&#xff0c;进而给自己带来不必要的麻烦和损失。因此&#xff0c;拥有一款能够高效提醒我们做事的提醒app显得尤为重要。 敬业签就是这样一款实用的提醒软件。它不…

零基础10 天入门 Web3之第1天

10 天入门 Web3 Web3 是互联网的下一代&#xff0c;它将使人们拥有自己的数据并控制自己的在线体验。Web3 基于区块链技术&#xff0c;该技术为安全、透明和可信的交易提供支持。我准备做一个 10 天的学习计划&#xff0c;可帮助大家入门 Web3&#xff1a; 想要一起探讨学习的…

【氮化镓】位错对氮化镓(GaN)电子能量损失谱(EEL)的影响

本文献《Influence of dislocations on electron energy-loss spectra in gallium nitride》由C. J. Fall等人撰写&#xff0c;发表于2002年。研究团队通过第一性原理计算&#xff0c;探讨了位错对氮化镓&#xff08;GaN&#xff09;电子能量损失谱&#xff08;EEL&#xff09;…

python——修改注册表

如图&#xff1a;想要修改Public的值为2.2.1.1 import winreg# 定义要修改的键和新值 key_path rSOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList key_name Public new_name 2.2.1.1# 打开指定的键 # 注意此处是注册表路径拼接&#xff0c;此处是HKEY_LOCAL_MACH…

docker部署ubuntu

仓库&#xff1a; https://hub.docker.com/search?qUbuntu 拉一个Ubuntu镜像 docker pull ubuntu:18.04 查看本地镜像&#xff1a; docker images 运行容器 docker run -itd --name ubuntu-18-001 ubuntu:18.04 通过ps命令可以查看正在运行的容器信息 docker ps 进入容器 最…

Obsidian插件:增加目录栏 flating toc

一、插件介绍 增加目录栏 插件市场搜索 flating toc安装即可 二、使用 写文档时候可以看到左边默认出现目录 可以自己配置一些相关设置 最后也可以安装一下插件样式设置插件&#xff0c;自己按照自己喜好调整

leetcode —— 5.最长回文子串

题目&#xff1a; 给你一个字符串 s&#xff0c;找到 s 中最长的回文子串。 如果字符串的反序与原始字符串相同&#xff0c;则该字符串称为回文字符串。 示例 1&#xff1a; 输入&#xff1a;s "babad" 输出&#xff1a;"bab" 解释&#xff1a;"…

基础数据结构(蓝桥杯Python组)

链表 基础概念 链表可以快速插入、删除元素&#xff0c;用于存储数据。 每个节点维护两个部分:数据域和指针域数据域就是节点所存储的数据&#xff0c;指针其实就是下标&#xff08;next&#xff0c;节点的下一个节点在哪&#xff09;链表维护head和tail&#xff0c;每次从ta…

学浪视频提取

经过调查,学浪这个学习平台越来越多人使用了,但是学浪视频官方没有提供下载按钮,为了让这些人能够随时随地的观看视频,于是我钻研学浪视频的下载,终于研究出来了并且做成软件批量版 下面是学浪视频提取的软件,有需要的自己下载一下 链接&#xff1a;https://pan.baidu.com/s/…

由浅到深认识Java语言(36):I/O流

该文章Github地址&#xff1a;https://github.com/AntonyCheng/java-notes 在此介绍一下作者开源的SpringBoot项目初始化模板&#xff08;Github仓库地址&#xff1a;https://github.com/AntonyCheng/spring-boot-init-template & CSDN文章地址&#xff1a;https://blog.c…

C++中 eigen(一)建造向量

快速上手&#xff1a; https://eigen.tuxfamily.org/dox/group__QuickRefPage.html 文档&#xff1a; https://eigen.tuxfamily.org/dox/index.html Constructors 建造 向量 Vectors 向量 例如&#xff0c;方便的 typedef Vector3f 是一个包含 3 个浮点数的&#xff08;列&a…

Spring用到了哪些设计模式?

目录 Spring 框架中⽤到了哪些设计模式&#xff1f;工厂模式单例模式1.饿汉式&#xff0c;线程安全2.懒汉式&#xff0c;线程不安全3.懒汉式&#xff0c;线程安全4.双重检查锁&#xff08;DCL&#xff0c; 即 double-checked locking&#xff09;5.静态内部类6.枚举单例 代理模…

15K star!一款功能强悍的手机电脑同屏工具,开源无需root!

在日常工作、生活场景中&#xff0c;经常会遇到需将手机与电脑屏幕进行共享。 今天就给大家推荐一款Android实时投屏神器&#xff1a;QtScrcpy。 它可以通过 USB / 网络连接Android设备&#xff0c;并进行显示和控制&#xff0c;且无需root权限。 1、简介 QtScrcpy是一款功…

nginx代理解决跨域问题

文章目录 一、什么是跨域、跨域问题产生的原因二、注意事项三、nginx代理解决总结 一、什么是跨域、跨域问题产生的原因 跨域&#xff08;Cross-Origin&#xff09;是指在 Web 开发中&#xff0c;一个网页的运行脚本试图访问另一个网页的资源时&#xff0c;这两个网页的域名、…

微信开发者工具接入短剧播放器插件

接入短剧播放插线 申请添加插件基础接入app.jsonapp.jsplayerManager.js数据加密跳转到播放器页面运行出错示例小程序页面页面使用的方法小程序输入框绑定申请添加插件 添加插件:登录微信开发者平台 ——> 设置 ——> 第三方设置 ——> 插件管理 ——> 搜索“短剧…

springboot多模块

这里springboot使用idea中的 Spring Initializr 来快速创建。 一、demo 1、创建父项目 首先使用 Spring Initializr 来快速创建好一个父Maven工程。然后删除无关的文件&#xff0c;只需保留pom.xml 文件。 &#xff08;1&#xff09;new Project -> spring initializr快…

FPGA时钟资源详解(1)——时钟Buffer的选择

FPGA时钟系列文章总览&#xff1a;FPGA原理与结构&#xff08;14&#xff09;——时钟资源https://ztzhang.blog.csdn.net/article/details/132307564 目录 一、概述 二、时钟Buffer的选择 2.1 BUFG 2.2 BUFR 和 BUFIO 2.2.1 源同步接口的支持 2.2.2 扩展时钟域…