Octree based Point Cloud Downsample

本算法基于哈希八叉树,并可进行按比例抽稀。

基本思路是迭代执行如下步骤直到目标数量ReserveNum等于0:

1)抽取不大于目标数量ReserveNum的前N层点云,并实时调整八叉树,类似从二叉树中pop元素;

2)更新目标数量ReserveNum为原值减去已去除的点云数量;


Notice: this code is a demo. No optimization tech has been used.


#include "tgHashedOctree.h"

int main(int argc, char* argv[])
{
	//
	std::vector<double> l_lstX;
	std::vector<double> l_lstY;
	std::vector<double> l_lstZ;
	//
	// put point cloud into l_lstX, l_lstY, l_lstZ
	// 
	tg::mesh::HashedOctree l_oHashedOctree;
	for (int i = 0; i < l_lstX.size(); ++i)
		l_oHashedOctree.AddPoint(l_lstX[i], l_lstY[i], l_lstY[i], i);

	std::vector<size_t> l_lstIndex;
	l_oHashedOctree.DownSample(l_dRadio, l_lstIndex);

	return 0;
}

Header File

/*
Hashed Octree

Copyright (C) by Tony Gauss (gaoxing.mes@gmail.com) in 2015

License : MIT
http://opensource.org/licenses/MIT

Permission is hereby granted, free of charge, to any person obtaining a copy of this software
and associated documentation files (the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

*/

#ifndef ___tgHashedOctree___
#define ___tgHashedOctree___

#include <unordered_map>

namespace tg
{

/*

HashedOctree
this class defines a hashed octree, only the indices of the input points will be stored in 
the octree, a downsample method is developed based on this octree
Notic: downsample will clear the octree

*/
class HashedOctree
{
public:
	///
	// constructors and destructor

	HashedOctree(const int&	i_nMaxDepth = 20);

	~HashedOctree();

	///
	// public member functions

	// Add a normalized point into the Octree, 
	void AddPoint(	const double	i_dX	,	// Range [-0.5, 0.5]
					const double	i_dY	,	// Range [-0.5, 0.5]
					const double	i_dZ	,	// Range [-0.5, 0.5]
					const size_t	i_sIndex);	// index of the point in the original point cloud

	// Downsample the point cloud based on Octree
	void DownSample(	const double				i_dRatio	,	// reserve ratio
								std::vector<size_t>&		i_lstIndex	);	// indices of points that are reserverd

	// show how many depth of the Octree have been filled
	int GetFilledDepth() const;

	// show how many points the Octree contains
	size_t Size() const;

private:
	///
	// private member functions

	// Get the key of the child no deeper than a threshold
	size_t GetChildNoDeeperThan(size_t			i_sKey		,	// input key
								const int&		i_nDepthThre);	// threshold depth

	// Remove all points above a threshold depth
	void RemoveNodesAboveDepth(	size_t			i_sKey		,	// initial key, should be 1
								int				i_nDepthCur	,	// initial Depth, should be 1
								const int&		i_nDepthThre);	// threshold depth

	// Get all points below a threshold depth
	void GetPointsBelowDepth(	std::vector<size_t>&		i_lstIndex	,	// indices list to contain the result
								size_t						i_sKey		,	// initial key, should be 1
								int							i_nDepthCur	,	// initial Depth, should be 1
								const int&					i_nDepthThre);	// threshold depth

	///
	// private variables

	int m_nMaxDepth		;		// max depth of the octree
	int m_nFilledDepth	;		// filled depth of the octree
	size_t m_sReserveNum;		// number of points that shall be reserved

	std::vector<size_t> m_lstDepthPointNum;		// number of points in each depth

	std::unordered_map<size_t, std::vector<size_t> > m_oHashedOctree;	// hash map to store the octree
	
};

};

#endif

Source File


/*
Hashed Octree

Copyright (C) by Tony Gauss (gaoxing.mes@gmail.com) in 2015

License : MIT
http://opensource.org/licenses/MIT

Permission is hereby granted, free of charge, to any person obtaining a copy of this software
and associated documentation files (the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

*/

#include "tgHashedOctree.h"

namespace tg
{

HashedOctree::HashedOctree(const int&	i_nMaxDepth)
{
	this->m_nMaxDepth = i_nMaxDepth;
	this->m_lstDepthPointNum.resize(this->m_nMaxDepth + 1, 0);
	this->m_nFilledDepth = 0;
}

HashedOctree::~HashedOctree()
{

}

size_t HashedOctree::Size() const
{
	size_t l_sCurNum = 0;
	for (int i = 0; i <= this->m_nMaxDepth; ++i)
	{
		l_sCurNum += this->m_lstDepthPointNum[i];
	}
	return l_sCurNum;
}

int HashedOctree::GetFilledDepth() const
{
	return this->m_nFilledDepth;
}

void HashedOctree::AddPoint(const double	i_dX		,
							const double	i_dY		,
							const double	i_dZ		,
							const size_t	i_sIndex	)
{
	//assert(i_dX >= -0.5 && i_dX <= 0.5);
	//assert(i_dY >= -0.5 && i_dY <= 0.5);
	//assert(i_dZ >= -0.5 && i_dZ <= 0.5);

	double l_dCentroid[3] = { 0 };
	size_t l_sLabelAll = 1;
	if (this->m_oHashedOctree.find(l_sLabelAll) == this->m_oHashedOctree.end())
	{
		this->m_oHashedOctree[l_sLabelAll].push_back(i_sIndex);
		this->m_nFilledDepth = 1;
		this->m_lstDepthPointNum[0]++;
		return;
	}

	int i;
	const size_t l_s1 = 1;
	for (i = 1; i < this->m_nMaxDepth; ++i)
	{
		size_t l_sLabel = 0;
		double l_dStep = 0.5 / (l_s1 << i);
		if (i_dX > l_dCentroid[0])
		{
			l_sLabel += 1;
			l_dCentroid[0] += l_dStep;
		}
		else
		{
			l_dCentroid[0] -= l_dStep;
		}
		if (i_dY > l_dCentroid[1])
		{
			l_sLabel += 2;
			l_dCentroid[1] += l_dStep;
		}
		else
		{
			l_dCentroid[1] -= l_dStep;
		}
		if (i_dZ > l_dCentroid[2])
		{
			l_sLabel += 4;
			l_dCentroid[2] += l_dStep;
		}
		else
		{
			l_dCentroid[2] -= l_dStep;
		}
		l_sLabelAll = (l_sLabelAll << 3) + l_sLabel;

		if (this->m_oHashedOctree.find(l_sLabelAll) == this->m_oHashedOctree.end())
		{
			++i;
			break;
		}
	}
	this->m_nFilledDepth = this->m_nFilledDepth > i ? this->m_nFilledDepth: i;
	this->m_lstDepthPointNum[i-1]++;

	this->m_oHashedOctree[l_sLabelAll].push_back(i_sIndex);
}

void HashedOctree::DownSample(	const double			i_dRatio	,
								std::vector<size_t>&	i_lstIndex	)
{
	assert(i_dRatio > 0);
	assert(i_dRatio <= 1.0);

	this->m_sReserveNum = this->Size() * i_dRatio;
	if(this->m_sReserveNum == 0)
		return;

	i_lstIndex.reserve(this->m_sReserveNum);
	while (i_lstIndex.size() < this->m_sReserveNum)
	{
		/
		// Find depth threshold
		size_t l_sCurNum = i_lstIndex.size();
		int l_nDepthThre;
		for (l_nDepthThre = 0; l_nDepthThre <= this->m_nMaxDepth; ++l_nDepthThre)
		{
			l_sCurNum += this->m_lstDepthPointNum[l_nDepthThre];
			if (l_sCurNum > this->m_sReserveNum)
				break;
		}

		/
		// Get points in depth below l_nDepthThre
		this->GetPointsBelowDepth(i_lstIndex, 1, 0, l_nDepthThre);

		printf("[%I64u/%I64u], Threshold: %d\n", i_lstIndex.size(), this->m_sReserveNum, l_nDepthThre);
	}
	this->m_oHashedOctree.clear();
}

void HashedOctree::GetPointsBelowDepth(	std::vector<size_t>&	i_lstIndex	,
										size_t					i_sKey		,
										int						i_nDepthCur	,
										const int&				i_nDepthThre)
{
	// return if threshold is reached
	if (i_nDepthCur >= i_nDepthThre)
		return;

	// return if this node does Not exits, which implies it has No child
	auto iter = this->m_oHashedOctree.find(i_sKey);
	if (iter == this->m_oHashedOctree.end())
		return;

	// Get a point from this node
	i_lstIndex.push_back(iter->second[0]);
	// Find the deepest (no deeper than the threshold) child of this node
	size_t l_sKeyTmp = GetChildNoDeeperThan(i_sKey, i_nDepthThre);
	// if the child's depth is equal to the threshold, move one point from child to this node 
	if ((l_sKeyTmp >> (3 * i_nDepthThre)) > 0)
	{
		auto iterTmp = this->m_oHashedOctree.find(l_sKeyTmp);
		iter->second[0] = iterTmp->second.back();
		iterTmp->second.pop_back();
		if (!iterTmp->second.size())
			this->m_oHashedOctree.erase(iterTmp);

		--this->m_lstDepthPointNum[i_nDepthThre];
	}
	// otherwise remove this node
	else
	{
		this->m_oHashedOctree.erase(iter);
		--this->m_lstDepthPointNum[i_nDepthCur];
	}
	// recursively ... until the threshold depth is reached
	i_sKey = (i_sKey << 3);
	for (size_t i = 0; i < 8; ++i)
	{
		this->GetPointsBelowDepth(i_lstIndex, i_sKey + i, i_nDepthCur + 1, i_nDepthThre);
	}
}

size_t HashedOctree::GetChildNoDeeperThan(	size_t		i_sKey		,
											const int&	i_nDepthThre)
{
	size_t l_sKeyMax = i_sKey;
	i_sKey = (i_sKey << 3);
	for (size_t i = 0; i < 8; ++i)
	{
		auto iter = this->m_oHashedOctree.find(i_sKey + i);
		if (iter != this->m_oHashedOctree.end())
		{
			if (((i_sKey + i) >> (3 * i_nDepthThre)) > 0)
				return i_sKey + i;

			size_t l_sKeyTmp = GetChildNoDeeperThan(i_sKey + i, i_nDepthThre);
			l_sKeyMax = l_sKeyMax > l_sKeyTmp ? l_sKeyMax : l_sKeyTmp;
		}
	}
	return l_sKeyMax;
}

void HashedOctree::RemoveNodesAboveDepth(	size_t		i_sKey		,	// initial key, should be 1
											int			i_nDepthCur	,	// initial Depth, should be 1
											const int&	i_nDepthThre)	// threshold depth
{
	// return if reached max depth
	if (i_nDepthCur > this->m_nMaxDepth)
		return;

	// return if this node does Not exits, which implies it has No child
	auto iter = this->m_oHashedOctree.find(i_sKey);
	if (iter == this->m_oHashedOctree.end())
		return;

	// remove this node if its depth is larger than threshold
	if (i_nDepthCur > i_nDepthThre)
	{
		this->m_oHashedOctree.erase(iter);
		--this->m_lstDepthPointNum[i_nDepthCur];
	}

	// recursively ... 
	i_sKey = (i_sKey << 3);
	for (size_t i = 0; i < 8; ++i)
	{
		this->RemoveNodesAboveDepth(i_sKey + i, i_nDepthCur + 1, i_nDepthThre);
	}
}

}




  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
叉树Octree是一种用于空间分割的数据结构,常用于计算机图形学、物理模拟等领域。其原理是将空间递归地分割成八个子空间,每个子空间又可以继续递归地分割成八个子空间,直到最小空间达到一定的大小。 C++中可以通过定义节点类和递归函数来实现叉树Octree。以下是一个简单的实现: ```c++ #include <iostream> #include <vector> #include <cmath> using namespace std; class Node { public: double x, y, z; // 三维坐标 double size; // 此节点所代表的空间大小 vector<Node*> children; // 子节点指针数组 Node(double x_, double y_, double z_, double size_) { x = x_; y = y_; z = z_; size = size_; children.resize(8, nullptr); } }; void insert(Node* node, double x, double y, double z, double size) { if (size <= node->size) { // 如果此节点的大小大于等于当前空间大小,则将此空间添加到此节点的子节点中 if (node->children[0] == nullptr) { // 如果当前节点还没有子节点,则创建 node->children[0] = new Node(node->x - node->size / 4, node->y - node->size / 4, node->z - node->size / 4, node->size / 2); node->children[1] = new Node(node->x + node->size / 4, node->y - node->size / 4, node->z - node->size / 4, node->size / 2); node->children[2] = new Node(node->x - node->size / 4, node->y + node->size / 4, node->z - node->size / 4, node->size / 2); node->children[3] = new Node(node->x + node->size / 4, node->y + node->size / 4, node->z - node->size / 4, node->size / 2); node->children[4] = new Node(node->x - node->size / 4, node->y - node->size / 4, node->z + node->size / 4, node->size / 2); node->children[5] = new Node(node->x + node->size / 4, node->y - node->size / 4, node->z + node->size / 4, node->size / 2); node->children[6] = new Node(node->x - node->size / 4, node->y + node->size / 4, node->z + node->size / 4, node->size / 2); node->children[7] = new Node(node->x + node->size / 4, node->y + node->size / 4, node->z + node->size / 4, node->size / 2); } for (int i = 0; i < 8; i++) { // 递归插入 insert(node->children[i], x, y, z, size / 2); } } } int main() { Node* root = new Node(0, 0, 0, 1); // 创建根节点 insert(root, 0.5, 0.5, 0.5, 0.1); // 插入一个大小为0.1的空间,其坐标为(0.5,0.5,0.5) return 0; } ``` 在这个简单的实现中,我们定义了一个节点类Node,包含了三维坐标和空间大小等属性,同时还有一个指向子节点的数组。我们通过递归函数insert来递归地插入新的空间,如果当前节点的大小大于等于要插入的空间大小,则将此空间插入到当前节点的子节点中,否则递归进入子节点。在main函数中,我们创建了一个根节点,并插入了一个大小为0.1的空间。 需要注意的是,此实现仅是一个简单的示例,实际应用中可能需要更复杂的节点类和插入函数。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值