fastlio中ikdtree代码解析

引言

本文结合代码对ikdtree进行介绍,ikdtree是fastlio中用来管理地图的数据结构。kdtree是一种常用的树形结构。ikdtree在kdtree的基础上增加了较为快速的添加删除及重构树的操作,以便于对地图的动态管理。以下会结合fastlio中ikd_Tree.h和ikd_Tree.cpp两个文件进行介绍。

基础

下面对基础的数据结构和算法进行介绍,是后续了解ikdtree的基础

队列(queue)

在ikd_Tree.h中,定义了ikdtree类,ikdtree类中,定义了MANUAL_Q结构,代码如下。

这是一个比较标准的队列应用,队列中存储的元素是Operation_Logger_Type,实际上是针对树的一些操作,这些操作如果因为多线程阻塞的原因没有被执行,就会保存到队列中,等多线程释放后,再对新的树进行对应的操作。

    class MANUAL_Q{private:int head = 0, tail = 0, counter = 0;Operation_Logger_Type q[Q_LEN];         // 一个长度较大的数组,用来存放对ikdtree的操作bool is_empty;public:void pop()                              // 扔出一个元素,注意这里只是改变了指针的位置,没有清理元素本身{if (counter == 0)                   // 先判断元素个数return;head++;head %= Q_LEN;                      // 对长度取模,可以看做一个循环队列counter--;if (counter == 0)is_empty = true;return;}Operation_Logger_Type front()           // 拿到当前队列的第一个元素{return q[head];}Operation_Logger_Type back()            // 拿到当前队列最后一个元素{return q[tail];}void clear()                            // 清空队列,实际就是指向位置的初始化{head = 0;tail = 0;counter = 0;is_empty = true;return;}void push(Operation_Logger_Type op)     // 队列尾部添加一个元素,也是循环添加{q[tail] = op;counter++;if (is_empty)is_empty = false;tail++;tail %= Q_LEN;}bool empty()                            // 判断是否为空{return is_empty;}int size()                              // 返回元素个数{return counter;}};

堆(heap)

代码中采用了max-heap。其中比较难理解的是插入和删除操作。具体可以参考

https://www.geeksforgeeks.org/insertion-and-deletion-in-heaps/

这个链接中的实现方式和ikdtree中实现方式略有不同,但整体思路是一致的。

以下是经过注释的代码

    class MANUAL_HEAP{public:MANUAL_HEAP(int max_capacity = 100){cap = max_capacity;heap = new PointType_CMP[max_capacity];     // 最大容量heap_size = 0;}~MANUAL_HEAP(){delete[] heap;}void pop(){if (heap_size == 0)return;heap[0] = heap[heap_size - 1];              // 弹出最大值,然后把队列最后的值放上来heap_size--;MoveDown(0);                                // 将放上来的新值下沉,找到合适的位置return;}PointType_CMP top()                             // 拿到最大值{return heap[0];}void push(PointType_CMP point){if (heap_size >= cap)   // 太多了新的就不放了return;heap[heap_size] = point;                       // 新插入的点放到最后FloatUp(heap_size);                             // 然后将最后一个点上浮到合适为止heap_size++;return;}int size(){return heap_size;}void clear(){heap_size = 0;return;}private:PointType_CMP *heap;void MoveDown(int heap_index)                       // 下沉过程{int l = heap_index * 2 + 1;                     // 找到左子节点的indexPointType_CMP tmp = heap[heap_index];           // 复制出一个当前值while (l < heap_size)                           // 没有循环超过数组大小,如果heap_size是0,那么直接退出while,放入第一个点{                                               // 如果heap_size是1,也是直接退出。if (l + 1 < heap_size && heap[l] < heap[l + 1])     // 从左子节点和右子节点中选出一个大的来l++;if (tmp < heap[l])                          // 将当前值和这个大的值比较,如果小,就交换,保证大的值在上面{heap[heap_index] = heap[l];heap_index = l;l = heap_index * 2 + 1;                 // 然后继续循环}elsebreak;}heap[heap_index] = tmp;return;}   void FloatUp(int heap_index)                            // 上浮过程{int ancestor = (heap_index - 1) / 2;                // 找到父节点PointType_CMP tmp = heap[heap_index];while (heap_index > 0)                              // 保证在范围内{if (heap[ancestor] < tmp)                       // 如果父节点小{heap[heap_index] = heap[ancestor];          // 就进行交换heap_index = ancestor;ancestor = (heap_index - 1) / 2;            // 直到循环完成}elsebreak;}heap[heap_index] = tmp;return;}int heap_size = 0;int cap = 0;};

kdtree

关于kdtree本身的知识,主要参考Wikipedia:https://en.wikipedia.org/wiki/K-d_tree

这里主要讲kdtree的构建、添加点、删除点和最近邻搜索

kdtree的构建

构建函数的伪代码如下。

主要步骤包括:1. 选择分割轴;2. 沿分割轴排序,并找到中值;3. 构造节点,同时用中值将所有元素分成两个部分,分别是左右子树,进行递归构建。

function kdtree (list of points pointList, int depth)
{// Select axis based on depth so that axis cycles through all valid values// 选择一个分割轴,可以按照最大方差选取,也可以按顺序直接分var int axis := depth mod k;// Sort point list and choose median as pivot element// 将所有点按分割轴排序,并找到其中的中值select median by axis from pointList;// Create node and construct subtree// 构造节点,然后对左右子树进行递归循环构建node.location := median;node.leftChild := kdtree(points in pointList before median, depth+1);node.rightChild := kdtree(points in pointList after median, depth+1);return node;
}

添加元素

添加元素伪代码:

function insert_kdtree (node, point, depth)
{// Base case: If the node is null, create a new node here// 处理递归结束情况,如果是空节点,就新建一个if (node is null){node := create new node;node.location := point;node.leftChild := null;node.rightChild := null;return node;}// Calculate current dimension (axis) based on depth// 选择分割轴var int axis := depth mod k;// Compare the new point with the current node's point along the current axis看看是在当前节点,沿着分割轴的左侧还是右侧if (point[axis] < node.location[axis]){// Recur on the left subtree// 如果是左侧就接着递归左侧insert_kdtree(node.leftChild, point, depth+1);}else{// Recur on the right subtree// 如果是右侧就接着递归右侧insert_kdtree(node.rightChild, point, depth+1);}
}

删除元素

删除kdtree中的元素的方法在Wikipedia中给了两个:

  1. 将剩下的点直接重建
  2. 找个点替代这个点,比如从左子树中找沿着当前轴最大的点,或从右子树中找沿着当前轴最小的点。

下面给出方法2的一个伪代码。

function delete_kdtree(node, point, depth)
{// Base case: If node is null, the point is not found// 处理空节点if (node is null)return null;// Calculate current dimension (axis) based on depth// 找到分割轴var int axis := depth mod k;// If the point to be deleted is found at the current node// 如果当前点就是要删除的点if (node.location == point){// Case 1: Node is a leaf node// 如果是叶节点,就直接删if (node.leftChild is null and node.rightChild is null)return null; // Simply remove the leaf node// Case 2: Node has a right child// 如果不是叶节点,从右子树中找一个最小值if (node.rightChild is not null){// Find the minimum point in the right subtree along the current axisvar PointType minPoint := find_min(node.rightChild, axis, depth + 1);// Replace current node with the minimum point foundnode.location := minPoint;// Recursively delete the minimum point from the right subtree// 同时还要递归删除刚刚找到的那个点node.rightChild := delete_kdtree(node.rightChild, minPoint, depth + 1);}// Case 3: Node only has a left child// 或者从左子树中找一个最大值,else if (node.leftChild is not null){// Find the maximum point in the left subtree along the current axisvar PointType maxPoint := find_max(node.leftChild, axis, depth + 1);// Replace current node with the maximum point foundnode.location := maxPoint;// Recursively delete the maximum point from the left subtree// 同时还要递归删除刚刚找到的那个点node.leftChild := delete_kdtree(node.leftChild, maxPoint, depth + 1);}return node;}// Recursively search for the point in the left or right subtree// 如果不是当前点,就接着递归寻找if (point[axis] < node.location[axis]){// Go to left subtreenode.leftChild := delete_kdtree(node.leftChild, point, depth + 1);}else{// Go to right subtreenode.rightChild := delete_kdtree(node.rightChild, point, depth + 1);}return node;
}function find_min(node, axis, depth)
{if (node is null)return null;// Calculate current axisvar int current_axis := depth mod k;// If current axis matches the target axisif (current_axis == axis){// The minimum is in the left subtreeif (node.leftChild is null)return node.location;return find_min(node.leftChild, axis, depth + 1);}// Otherwise, find the minimum across both subtrees// 递归遍历找最小值var PointType leftMin := find_min(node.leftChild, axis, depth + 1);var PointType rightMin := find_min(node.rightChild, axis, depth + 1);// Return the smallest among node, leftMin, and rightMinreturn min(node.location, leftMin, rightMin);
}function find_max(node, axis, depth)
{if (node is null)return null;// Calculate current axisvar int current_axis := depth mod k;// If current axis matches the target axisif (current_axis == axis){// The maximum is in the right subtreeif (node.rightChild is null)return node.location;return find_max(node.rightChild, axis, depth + 1);}// Otherwise, find the maximum across both subtreesvar PointType leftMax := find_max(node.leftChild, axis, depth + 1);var PointType rightMax := find_max(node.rightChild, axis, depth + 1);// Return the largest among node, leftMax, and rightMaxreturn max(node.location, leftMax, rightMax);
}

最近邻搜索

Visit(root):d= distance(target, root)	// 计算和当前点的距离if d < r:					// 比最近的距离还近r= d					// 记录这个距离closest= root			// 记录这个点if root.left and root.x - target.x < r:	// x在这里就是对应root的分割轴,Visit(root.left)if root.right and target.x - root.x < r:Visit(root.right)

多线程锁

pthread_mutex_init:初始化一个互斥锁。

pthread_mutex_lock:以阻塞方式加锁互斥锁。

pthread_mutex_trylock:以非阻塞方式尝试加锁互斥锁。

pthread_mutex_unlock:解锁互斥锁。

pthread_mutex_destroy:销毁一个互斥锁。

ikdtree

接口API

以下从Fastlio的laserMapping.cpp中,选出用到的ikdtree的api。从这里可以看出ikdtree的用法,进而可以推断出ikdtree本身实现所需要包含的功能。

ikdtree是进行点云地图管理的,由于点云数量庞大,并且fastlio仅维护当前位置附近的局部地图,因此,ikdtree需要能快速的完成插入点删除点的动作。

同时,为了完成点云匹配计算,需要ikdtree能完成最近的K个点的查询功能。具体如下:

  1. 定义:
KD_TREE<PointType> ikdtree;
  1. 初始化构造:
            if(ikdtree.Root_Node == nullptr)								// 如果没有根节点,说明没构造{if(feats_down_size > 5){ikdtree.set_downsample_param(filter_size_map_min);feats_down_world->resize(feats_down_size);for(int i = 0; i < feats_down_size; i++){pointBodyToWorld(&(feats_down_body->points[i]), &(feats_down_world->points[i]));}ikdtree.Build(feats_down_world->points);				// 给它一串点,构造初始的树}continue;}int featsFromMapNum = ikdtree.validnum();						// 读取一些树的特性kdtree_size_st = ikdtree.size();								// 读取树中节点的个数
  1. 删除点:

随着车辆的移动,需要删除某些区域的点,这样不至于地图过大,只保留激光雷达附近的局部地图即可

if(cub_needrm.size() > 0) kdtree_delete_counter = ikdtree.Delete_Point_Boxes(cub_needrm);
  1. 添加点:

随着车辆移动,需要新加入点

    add_point_size = ikdtree.Add_Points(PointToAdd, true);ikdtree.Add_Points(PointNoNeedDownsample, false); 
  1. 查询top-K最近点

在进行点云匹配时,会选择最近的K个点,组成平面,进行点面匹配计算

ikdtree.Nearest_Search(point_world, NUM_MATCH_POINTS, points_near, pointSearchSqDis);

主要功能

构造

主要构造过程函数

树的构造过程主要是看BuildTree函数。在rebuild功能中,也会调用这个函数完成数的创建。

/*** 树的构造过程函数,大概分成4部分* 1. 递归结束条件* 2. 根据x,y,z三个轴上的分布情况,选定分割轴* 3. 创建当前点,并且对左右子树递归循环创建* 4. 更新当前节点属性参数
*/
template <typename PointType>
void KD_TREE<PointType>::BuildTree(KD_TREE_NODE **root, int l, int r, PointVector &Storage)
{if (l > r)                  // 终止条件return;*root = new KD_TREE_NODE;   // 分配一块新的node内存出来,给root所指向的地址InitTreeNode(*root);int mid = (l + r) >> 1;     // 等价于 (l + r) / 2int div_axis = 0;int i;// Find the best division Axisfloat min_value[3] = {INFINITY, INFINITY, INFINITY};float max_value[3] = {-INFINITY, -INFINITY, -INFINITY};float dim_range[3] = {0, 0, 0};for (i = l; i <= r; i++)                                // 对所有点循环遍历,找到当前这组点中“范围最大的”一个维度作为分割轴{min_value[0] = min(min_value[0], Storage[i].x);min_value[1] = min(min_value[1], Storage[i].y);min_value[2] = min(min_value[2], Storage[i].z);max_value[0] = max(max_value[0], Storage[i].x);max_value[1] = max(max_value[1], Storage[i].y);max_value[2] = max(max_value[2], Storage[i].z);}// Select the longest dimension as division axisfor (i = 0; i < 3; i++)dim_range[i] = max_value[i] - min_value[i];for (i = 1; i < 3; i++)if (dim_range[i] > dim_range[div_axis])div_axis = i;// Divide by the division axis and recursively build.(*root)->division_axis = div_axis;switch (div_axis)                   // 这是在进行排序并找到中位数的点{case 0:     // nth_element 是c++标准库中的一个函数nth_element(begin(Storage) + l, begin(Storage) + mid, begin(Storage) + r + 1, point_cmp_x);break;case 1:nth_element(begin(Storage) + l, begin(Storage) + mid, begin(Storage) + r + 1, point_cmp_y);break;case 2:nth_element(begin(Storage) + l, begin(Storage) + mid, begin(Storage) + r + 1, point_cmp_z);break;default:nth_element(begin(Storage) + l, begin(Storage) + mid, begin(Storage) + r + 1, point_cmp_x);break;}(*root)->point = Storage[mid];      // 将中位数的点记录到当前node中KD_TREE_NODE *left_son = nullptr, *right_son = nullptr;BuildTree(&left_son, l, mid - 1, Storage);      // 然后继续遍历左右子节点BuildTree(&right_son, mid + 1, r, Storage);(*root)->left_son_ptr = left_son;               // 记录新的左右子节点(*root)->right_son_ptr = right_son;Update((*root));                    //更新当前节点的一些属性return;
}
节点属性参数更新函数
/*** Update 函数在 KD-Tree 中的作用是更新每个节点的统计信息和元数据。* 具体来说,它会更新节点的大小(TreeSize)、删除标记(tree_deleted)、空间范围(node_range_x, node_range_y, node_range_z),* 以及父子节点之间的关系。这个函数在构建树时被调用,以确保每个节点的相关信息都是最新的,尤其是在递归创建子树之后。*/
template <typename PointType>
void KD_TREE<PointType>::Update(KD_TREE_NODE *root)
{KD_TREE_NODE *left_son_ptr = root->left_son_ptr;KD_TREE_NODE *right_son_ptr = root->right_son_ptr;float tmp_range_x[2] = {INFINITY, -INFINITY};       //用于存储当前节点的空间范围(即该节点的最小值和最大值)float tmp_range_y[2] = {INFINITY, -INFINITY};float tmp_range_z[2] = {INFINITY, -INFINITY};// Update Tree Sizeif (left_son_ptr != nullptr && right_son_ptr != nullptr)    // 左右子节点都存在的情况{root->TreeSize = left_son_ptr->TreeSize + right_son_ptr->TreeSize + 1;          // 更新当前节点属性,利用子节点的属性root->invalid_point_num = left_son_ptr->invalid_point_num + right_son_ptr->invalid_point_num + (root->point_deleted ? 1 : 0);root->down_del_num = left_son_ptr->down_del_num + right_son_ptr->down_del_num + (root->point_downsample_deleted ? 1 : 0);root->tree_downsample_deleted = left_son_ptr->tree_downsample_deleted & right_son_ptr->tree_downsample_deleted & root->point_downsample_deleted;root->tree_deleted = left_son_ptr->tree_deleted && right_son_ptr->tree_deleted && root->point_deleted;if (root->tree_deleted || (!left_son_ptr->tree_deleted && !right_son_ptr->tree_deleted && !root->point_deleted))    // 根据是否要删除树进行分类{tmp_range_x[0] = min(min(left_son_ptr->node_range_x[0], right_son_ptr->node_range_x[0]), root->point.x);tmp_range_x[1] = max(max(left_son_ptr->node_range_x[1], right_son_ptr->node_range_x[1]), root->point.x);        // 计算范围tmp_range_y[0] = min(min(left_son_ptr->node_range_y[0], right_son_ptr->node_range_y[0]), root->point.y);tmp_range_y[1] = max(max(left_son_ptr->node_range_y[1], right_son_ptr->node_range_y[1]), root->point.y);tmp_range_z[0] = min(min(left_son_ptr->node_range_z[0], right_son_ptr->node_range_z[0]), root->point.z);tmp_range_z[1] = max(max(left_son_ptr->node_range_z[1], right_son_ptr->node_range_z[1]), root->point.z);}else{if (!left_son_ptr->tree_deleted)        // 如果左子树不删,就和左子树计算一次边界{tmp_range_x[0] = min(tmp_range_x[0], left_son_ptr->node_range_x[0]);tmp_range_x[1] = max(tmp_range_x[1], left_son_ptr->node_range_x[1]);tmp_range_y[0] = min(tmp_range_y[0], left_son_ptr->node_range_y[0]);    // 计算范围tmp_range_y[1] = max(tmp_range_y[1], left_son_ptr->node_range_y[1]);tmp_range_z[0] = min(tmp_range_z[0], left_son_ptr->node_range_z[0]);tmp_range_z[1] = max(tmp_range_z[1], left_son_ptr->node_range_z[1]);}if (!right_son_ptr->tree_deleted)       // 如果右子树不删,再和右子树计算一次边界{tmp_range_x[0] = min(tmp_range_x[0], right_son_ptr->node_range_x[0]);tmp_range_x[1] = max(tmp_range_x[1], right_son_ptr->node_range_x[1]);tmp_range_y[0] = min(tmp_range_y[0], right_son_ptr->node_range_y[0]);tmp_range_y[1] = max(tmp_range_y[1], right_son_ptr->node_range_y[1]);tmp_range_z[0] = min(tmp_range_z[0], right_son_ptr->node_range_z[0]);tmp_range_z[1] = max(tmp_range_z[1], right_son_ptr->node_range_z[1]);}if (!root->point_deleted)               // 和自身位置计算一次边界{tmp_range_x[0] = min(tmp_range_x[0], root->point.x);tmp_range_x[1] = max(tmp_range_x[1], root->point.x);tmp_range_y[0] = min(tmp_range_y[0], root->point.y);tmp_range_y[1] = max(tmp_range_y[1], root->point.y);tmp_range_z[0] = min(tmp_range_z[0], root->point.z);tmp_range_z[1] = max(tmp_range_z[1], root->point.z);}}}else if (left_son_ptr != nullptr)       // 同样道理处理左子树非空,但是右子树空的情况{root->TreeSize = left_son_ptr->TreeSize + 1;root->invalid_point_num = left_son_ptr->invalid_point_num + (root->point_deleted ? 1 : 0);root->down_del_num = left_son_ptr->down_del_num + (root->point_downsample_deleted ? 1 : 0);root->tree_downsample_deleted = left_son_ptr->tree_downsample_deleted & root->point_downsample_deleted;root->tree_deleted = left_son_ptr->tree_deleted && root->point_deleted;if (root->tree_deleted || (!left_son_ptr->tree_deleted && !root->point_deleted)){tmp_range_x[0] = min(left_son_ptr->node_range_x[0], root->point.x);tmp_range_x[1] = max(left_son_ptr->node_range_x[1], root->point.x);tmp_range_y[0] = min(left_son_ptr->node_range_y[0], root->point.y);tmp_range_y[1] = max(left_son_ptr->node_range_y[1], root->point.y);tmp_range_z[0] = min(left_son_ptr->node_range_z[0], root->point.z);tmp_range_z[1] = max(left_son_ptr->node_range_z[1], root->point.z);}else{if (!left_son_ptr->tree_deleted){tmp_range_x[0] = min(tmp_range_x[0], left_son_ptr->node_range_x[0]);tmp_range_x[1] = max(tmp_range_x[1], left_son_ptr->node_range_x[1]);tmp_range_y[0] = min(tmp_range_y[0], left_son_ptr->node_range_y[0]);tmp_range_y[1] = max(tmp_range_y[1], left_son_ptr->node_range_y[1]);tmp_range_z[0] = min(tmp_range_z[0], left_son_ptr->node_range_z[0]);tmp_range_z[1] = max(tmp_range_z[1], left_son_ptr->node_range_z[1]);}if (!root->point_deleted){tmp_range_x[0] = min(tmp_range_x[0], root->point.x);tmp_range_x[1] = max(tmp_range_x[1], root->point.x);tmp_range_y[0] = min(tmp_range_y[0], root->point.y);tmp_range_y[1] = max(tmp_range_y[1], root->point.y);tmp_range_z[0] = min(tmp_range_z[0], root->point.z);tmp_range_z[1] = max(tmp_range_z[1], root->point.z);}}}else if (right_son_ptr != nullptr)      // 同样道理处理右子树非空,左子树空的情况{root->TreeSize = right_son_ptr->TreeSize + 1;root->invalid_point_num = right_son_ptr->invalid_point_num + (root->point_deleted ? 1 : 0);root->down_del_num = right_son_ptr->down_del_num + (root->point_downsample_deleted ? 1 : 0);root->tree_downsample_deleted = right_son_ptr->tree_downsample_deleted & root->point_downsample_deleted;root->tree_deleted = right_son_ptr->tree_deleted && root->point_deleted;if (root->tree_deleted || (!right_son_ptr->tree_deleted && !root->point_deleted)){tmp_range_x[0] = min(right_son_ptr->node_range_x[0], root->point.x);tmp_range_x[1] = max(right_son_ptr->node_range_x[1], root->point.x);tmp_range_y[0] = min(right_son_ptr->node_range_y[0], root->point.y);tmp_range_y[1] = max(right_son_ptr->node_range_y[1], root->point.y);tmp_range_z[0] = min(right_son_ptr->node_range_z[0], root->point.z);tmp_range_z[1] = max(right_son_ptr->node_range_z[1], root->point.z);}else{if (!right_son_ptr->tree_deleted){tmp_range_x[0] = min(tmp_range_x[0], right_son_ptr->node_range_x[0]);tmp_range_x[1] = max(tmp_range_x[1], right_son_ptr->node_range_x[1]);tmp_range_y[0] = min(tmp_range_y[0], right_son_ptr->node_range_y[0]);tmp_range_y[1] = max(tmp_range_y[1], right_son_ptr->node_range_y[1]);tmp_range_z[0] = min(tmp_range_z[0], right_son_ptr->node_range_z[0]);tmp_range_z[1] = max(tmp_range_z[1], right_son_ptr->node_range_z[1]);}if (!root->point_deleted){tmp_range_x[0] = min(tmp_range_x[0], root->point.x);tmp_range_x[1] = max(tmp_range_x[1], root->point.x);tmp_range_y[0] = min(tmp_range_y[0], root->point.y);tmp_range_y[1] = max(tmp_range_y[1], root->point.y);tmp_range_z[0] = min(tmp_range_z[0], root->point.z);tmp_range_z[1] = max(tmp_range_z[1], root->point.z);}}}else{root->TreeSize = 1;root->invalid_point_num = (root->point_deleted ? 1 : 0);root->down_del_num = (root->point_downsample_deleted ? 1 : 0);root->tree_downsample_deleted = root->point_downsample_deleted;root->tree_deleted = root->point_deleted;tmp_range_x[0] = root->point.x;tmp_range_x[1] = root->point.x;tmp_range_y[0] = root->point.y;tmp_range_y[1] = root->point.y;tmp_range_z[0] = root->point.z;tmp_range_z[1] = root->point.z;}memcpy(root->node_range_x, tmp_range_x, sizeof(tmp_range_x));       // 把计算出来的包络范围赋值给当前节点memcpy(root->node_range_y, tmp_range_y, sizeof(tmp_range_y));memcpy(root->node_range_z, tmp_range_z, sizeof(tmp_range_z));float x_L = (root->node_range_x[1] - root->node_range_x[0]) * 0.5;float y_L = (root->node_range_y[1] - root->node_range_y[0]) * 0.5;float z_L = (root->node_range_z[1] - root->node_range_z[0]) * 0.5;root->radius_sq = x_L*x_L + y_L * y_L + z_L * z_L;          // 大概计算一个半径的平方if (left_son_ptr != nullptr)left_son_ptr->father_ptr = root;            // 赋值父节点if (right_son_ptr != nullptr)right_son_ptr->father_ptr = root;if (root == Root_Node && root->TreeSize > 3){KD_TREE_NODE *son_ptr = root->left_son_ptr;if (son_ptr == nullptr)son_ptr = root->right_son_ptr;float tmp_bal = float(son_ptr->TreeSize) / (root->TreeSize - 1);        // 判断树平衡的一些条件,root->alpha_del = float(root->invalid_point_num) / root->TreeSize;      // 只在根节点进行计算root->alpha_bal = (tmp_bal >= 0.5 - EPSS) ? tmp_bal : 1 - tmp_bal;}return;
}

在构造树的过程中,生成完每个节点的坐标点,以及所有递归子树后,会进行update更新,此时更新的属性都是默认值,都是“不需要删除树”之类的值。此时的树非常“干净”没有什么特别的属性(删除相关属性)。

添加功能

Add_Points函数

里面调用了Add_by_point 和 Delete_by_range函数

template <typename PointType>
int KD_TREE<PointType>::Add_Points(PointVector &PointToAdd, bool downsample_on)
{int NewPointSize = PointToAdd.size();               // 要添加的点的个数int tree_size = size();BoxPointType Box_of_Point;                          // 定义一些临时变量PointType downsample_result, mid_point;bool downsample_switch = downsample_on && DOWNSAMPLE_SWITCH;    // 是否降采样float min_dist, tmp_dist;                           // 临时变量int tmp_counter = 0;for (int i = 0; i < PointToAdd.size(); i++)         // 对每个要添加的点都进行循环{if (downsample_switch)                          // 判断是否进行降采样{                                               // 如果要进行Box_of_Point.vertex_min[0] = floor(PointToAdd[i].x / downsample_size) * downsample_size;    // 生成一个立方体边框的长宽高Box_of_Point.vertex_max[0] = Box_of_Point.vertex_min[0] + downsample_size;Box_of_Point.vertex_min[1] = floor(PointToAdd[i].y / downsample_size) * downsample_size;Box_of_Point.vertex_max[1] = Box_of_Point.vertex_min[1] + downsample_size;Box_of_Point.vertex_min[2] = floor(PointToAdd[i].z / downsample_size) * downsample_size;Box_of_Point.vertex_max[2] = Box_of_Point.vertex_min[2] + downsample_size;mid_point.x = Box_of_Point.vertex_min[0] + (Box_of_Point.vertex_max[0] - Box_of_Point.vertex_min[0]) / 2.0;     // 立方体边框的中心点mid_point.y = Box_of_Point.vertex_min[1] + (Box_of_Point.vertex_max[1] - Box_of_Point.vertex_min[1]) / 2.0;mid_point.z = Box_of_Point.vertex_min[2] + (Box_of_Point.vertex_max[2] - Box_of_Point.vertex_min[2]) / 2.0;PointVector().swap(Downsample_Storage);Search_by_range(Root_Node, Box_of_Point, Downsample_Storage);   // 看看都有多少点在边框内部min_dist = calc_dist(PointToAdd[i], mid_point);                 // 计算要添加的点到中心店的距离downsample_result = PointToAdd[i];for (int index = 0; index < Downsample_Storage.size(); index++) // 对每个方框内的点进行循环 {tmp_dist = calc_dist(Downsample_Storage[index], mid_point); // 计算方框内的每个点到中心线的距离if (tmp_dist < min_dist)                                    // 找到最小的那个点{min_dist = tmp_dist;downsample_result = Downsample_Storage[index];}}if (Rebuild_Ptr == nullptr || *Rebuild_Ptr != Root_Node)        // 如果没有在rebuild{if (Downsample_Storage.size() > 1 || same_point(PointToAdd[i], downsample_result)){if (Downsample_Storage.size() > 0)Delete_by_range(&Root_Node, Box_of_Point, true, true);  // 删除原先方框内的点Add_by_point(&Root_Node, downsample_result, true, Root_Node->division_axis);    // 新增那个距离中心最近的点tmp_counter++;}}else                                                            // 如果在rebuild{if (Downsample_Storage.size() > 1 || same_point(PointToAdd[i], downsample_result)){Operation_Logger_Type operation_delete, operation;operation_delete.boxpoint = Box_of_Point;operation_delete.op = DOWNSAMPLE_DELETE;operation.point = downsample_result;operation.op = ADD_POINT;pthread_mutex_lock(&working_flag_mutex);if (Downsample_Storage.size() > 0)Delete_by_range(&Root_Node, Box_of_Point, false, true);Add_by_point(&Root_Node, downsample_result, false, Root_Node->division_axis);tmp_counter++;if (rebuild_flag){pthread_mutex_lock(&rebuild_logger_mutex_lock);if (Downsample_Storage.size() > 0)Rebuild_Logger.push(operation_delete);          // 缓存删除以及添加操作Rebuild_Logger.push(operation);pthread_mutex_unlock(&rebuild_logger_mutex_lock);}pthread_mutex_unlock(&working_flag_mutex);};}}else        // 如果不进行下采样{if (Rebuild_Ptr == nullptr || *Rebuild_Ptr != Root_Node){Add_by_point(&Root_Node, PointToAdd[i], true, Root_Node->division_axis);    // 就直接添加}else{Operation_Logger_Type operation;operation.point = PointToAdd[i];operation.op = ADD_POINT;pthread_mutex_lock(&working_flag_mutex);Add_by_point(&Root_Node, PointToAdd[i], false, Root_Node->division_axis);if (rebuild_flag){pthread_mutex_lock(&rebuild_logger_mutex_lock);Rebuild_Logger.push(operation);                                     // 或者缓存后添加pthread_mutex_unlock(&rebuild_logger_mutex_lock);}pthread_mutex_unlock(&working_flag_mutex);}}}return tmp_counter;
}
Add_by_point函数

/*** 这个函数是一个点一个点的添加,通过递归的方式完成遍历,找到点需要在的位置,然后添加* 添加完成后要不断update来更新信息
*/
template <typename PointType>
void KD_TREE<PointType>::Add_by_point(KD_TREE_NODE **root, PointType point, bool allow_rebuild, int father_axis)
{if (*root == nullptr)       // 迭代遍历终止条件{*root = new KD_TREE_NODE;InitTreeNode(*root);(*root)->point = point;(*root)->division_axis = (father_axis + 1) % 3; Update(*root);          // 添加完点后要反向遍历一下return;}(*root)->working_flag = true;Operation_Logger_Type add_log;struct timespec Timeout;add_log.op = ADD_POINT;add_log.point = point;Push_Down(*root);if (((*root)->division_axis == 0 && point.x < (*root)->point.x) || ((*root)->division_axis == 1 && point.y < (*root)->point.y) || ((*root)->division_axis == 2 && point.z < (*root)->point.z)){           // 需要添加在左子树的情况if ((Rebuild_Ptr == nullptr) || (*root)->left_son_ptr != *Rebuild_Ptr){Add_by_point(&(*root)->left_son_ptr, point, allow_rebuild, (*root)->division_axis);}else{pthread_mutex_lock(&working_flag_mutex);Add_by_point(&(*root)->left_son_ptr, point, false, (*root)->division_axis);if (rebuild_flag){pthread_mutex_lock(&rebuild_logger_mutex_lock);Rebuild_Logger.push(add_log);pthread_mutex_unlock(&rebuild_logger_mutex_lock);}pthread_mutex_unlock(&working_flag_mutex);}}else{           // 需要添加在右子树的情况if ((Rebuild_Ptr == nullptr) || (*root)->right_son_ptr != *Rebuild_Ptr){Add_by_point(&(*root)->right_son_ptr, point, allow_rebuild, (*root)->division_axis);}else{pthread_mutex_lock(&working_flag_mutex);Add_by_point(&(*root)->right_son_ptr, point, false, (*root)->division_axis);if (rebuild_flag){pthread_mutex_lock(&rebuild_logger_mutex_lock);Rebuild_Logger.push(add_log);pthread_mutex_unlock(&rebuild_logger_mutex_lock);}pthread_mutex_unlock(&working_flag_mutex);}}Update(*root);      // 每个循环遍历退出之后,都进行一下标志位自下而上的更新if (Rebuild_Ptr != nullptr && *Rebuild_Ptr == *root && (*root)->TreeSize < Multi_Thread_Rebuild_Point_Num)Rebuild_Ptr = nullptr;bool need_rebuild = allow_rebuild & Criterion_Check((*root));   // 同时根据平衡条件判断要不要进行树的重建if (need_rebuild)Rebuild(root);      // 重建,重建以当前节点为root的树,不是从头都重建if ((*root) != nullptr)(*root)->working_flag = false;return;
}

删除功能

Delete_Point_Boxes函数
/*** 对外部的删除接口,给入一串方框,要求把方框中的点都删除
*/
template <typename PointType>
int KD_TREE<PointType>::Delete_Point_Boxes(vector<BoxPointType> &BoxPoints)
{int tmp_counter = 0;for (int i = 0; i < BoxPoints.size(); i++)          // 对方框进行循环{if (Rebuild_Ptr == nullptr || *Rebuild_Ptr != Root_Node)    // 如果没有在进行rebuild,或者只是局部rebuild{tmp_counter += Delete_by_range(&Root_Node, BoxPoints[i], true, false);  // 调用Delete_by_range函数进行删除,}                                                                           // 由于此时没有rebuild 任务,所以allow rebuild= trueelse                                            // 如果正在进行rebuild,说明树的结果会发生变化{Operation_Logger_Type operation;operation.boxpoint = BoxPoints[i];operation.op = DELETE_BOX;pthread_mutex_lock(&working_flag_mutex);tmp_counter += Delete_by_range(&Root_Node, BoxPoints[i], false, false); // 但是这里依然调用了Delete_by_range,由于// 此时有rebuild任务,所以allow rebuild = falseif (rebuild_flag)                           // 如果正在进行多线程rebuild{pthread_mutex_lock(&rebuild_logger_mutex_lock);Rebuild_Logger.push(operation);         // 那就先把删除操作缓存一下pthread_mutex_unlock(&rebuild_logger_mutex_lock);}pthread_mutex_unlock(&working_flag_mutex);}}return tmp_counter;
}
Delete_by_range函数
/*** 这个函数执行具体的delete操作,具体的执行过程就是反复递归,然后对节点的是否删除属性进行赋值,然后* 根据重建标准,判定是否重建,也就是这里是lazy 删除,不是当场就删除,而是要等待合适的时机,通过* 重建树来执行彻底的删除操作
*/
template <typename PointType>
int KD_TREE<PointType>::Delete_by_range(KD_TREE_NODE **root, BoxPointType boxpoint, bool allow_rebuild, bool is_downsample)
{if ((*root) == nullptr || (*root)->tree_deleted)return 0;(*root)->working_flag = true;Push_Down(*root);           // 将当前节点状态传递给子节点int tmp_counter = 0;if (boxpoint.vertex_max[0] <= (*root)->node_range_x[0] || boxpoint.vertex_min[0] > (*root)->node_range_x[1])    // 确定是否在包络上有相交return 0;                                                                                                   // 没有相交,说明不用删除直接返回if (boxpoint.vertex_max[1] <= (*root)->node_range_y[0] || boxpoint.vertex_min[1] > (*root)->node_range_y[1])return 0;if (boxpoint.vertex_max[2] <= (*root)->node_range_z[0] || boxpoint.vertex_min[2] > (*root)->node_range_z[1])return 0;if (boxpoint.vertex_min[0] <= (*root)->node_range_x[0] && boxpoint.vertex_max[0] > (*root)->node_range_x[1] && boxpoint.vertex_min[1] <= (*root)->node_range_y[0] && boxpoint.vertex_max[1] > (*root)->node_range_y[1] && boxpoint.vertex_min[2] <= (*root)->node_range_z[0] && boxpoint.vertex_max[2] > (*root)->node_range_z[1]){   // 如果有相交就要删除,这个if不仅是相交,是包含,如果时包含,就直接把这个点下所有的点都删掉(*root)->tree_deleted = true;       // 所以这里要删除整个树(*root)->point_deleted = true;(*root)->need_push_down_to_left = true;     // 需要把这个删除状态传递给子节点(*root)->need_push_down_to_right = true;    // 那么什么时候把这个信息向下pushdown呢?tmp_counter = (*root)->TreeSize - (*root)->invalid_point_num;(*root)->invalid_point_num = (*root)->TreeSize;if (is_downsample){(*root)->tree_downsample_deleted = true;(*root)->point_downsample_deleted = true;(*root)->down_del_num = (*root)->TreeSize;}return tmp_counter;}if (!(*root)->point_deleted && boxpoint.vertex_min[0] <= (*root)->point.x && boxpoint.vertex_max[0] > (*root)->point.x && boxpoint.vertex_min[1] <= (*root)->point.y && boxpoint.vertex_max[1] > (*root)->point.y && boxpoint.vertex_min[2] <= (*root)->point.z && boxpoint.vertex_max[2] > (*root)->point.z){   // 如果是相交,就要先把当前点删了,然后再去下面遍历其他的子树(*root)->point_deleted = true;tmp_counter += 1;if (is_downsample)(*root)->point_downsample_deleted = true;}Operation_Logger_Type delete_box_log;struct timespec Timeout;if (is_downsample)delete_box_log.op = DOWNSAMPLE_DELETE;elsedelete_box_log.op = DELETE_BOX;         // 构造一个操作,然后看看是不是需要根据rebuild状态进行操作缓存。delete_box_log.boxpoint = boxpoint;if ((Rebuild_Ptr == nullptr) || (*root)->left_son_ptr != *Rebuild_Ptr)      // 这里先接着遍历左树{tmp_counter += Delete_by_range(&((*root)->left_son_ptr), boxpoint, allow_rebuild, is_downsample);}else{pthread_mutex_lock(&working_flag_mutex);tmp_counter += Delete_by_range(&((*root)->left_son_ptr), boxpoint, false, is_downsample);   // 这个意思是,我现在原来的树里标注删除if (rebuild_flag)                                                                           // 然后等新树建好了,同样的动作再执行一遍{pthread_mutex_lock(&rebuild_logger_mutex_lock);Rebuild_Logger.push(delete_box_log);pthread_mutex_unlock(&rebuild_logger_mutex_lock);}pthread_mutex_unlock(&working_flag_mutex);}if ((Rebuild_Ptr == nullptr) || (*root)->right_son_ptr != *Rebuild_Ptr)     // 同样遍历右子树删除{tmp_counter += Delete_by_range(&((*root)->right_son_ptr), boxpoint, allow_rebuild, is_downsample);}else{pthread_mutex_lock(&working_flag_mutex);tmp_counter += Delete_by_range(&((*root)->right_son_ptr), boxpoint, false, is_downsample);if (rebuild_flag){pthread_mutex_lock(&rebuild_logger_mutex_lock);Rebuild_Logger.push(delete_box_log);pthread_mutex_unlock(&rebuild_logger_mutex_lock);}pthread_mutex_unlock(&working_flag_mutex);}Update(*root);          // 前面是pushdown,根据父节点确定子节点,现在update,根据子节点再反馈给父节点 保持一致。if (Rebuild_Ptr != nullptr && *Rebuild_Ptr == *root && (*root)->TreeSize < Multi_Thread_Rebuild_Point_Num)Rebuild_Ptr = nullptr;bool need_rebuild = allow_rebuild & Criterion_Check((*root));if (need_rebuild)Rebuild(root);      // 判断一下,是不是需要重新构造树if ((*root) != nullptr)(*root)->working_flag = false;return tmp_counter;
}

查找功能

Nearest_Search函数

这个是对外的接口函数,内部会调用Search函数

/*** Top-K个最近点的搜索* 
*/
template <typename PointType>
void KD_TREE<PointType>::Nearest_Search(PointType point, int k_nearest, PointVector &Nearest_Points, vector<float> &Point_Distance, float max_dist)
{MANUAL_HEAP q(2 * k_nearest);                               // 首先构造一个堆,q.clear();                                                  // 通常利用树来完成最近邻搜索都是配合一个堆来进行vector<float>().swap(Point_Distance);if (Rebuild_Ptr == nullptr || *Rebuild_Ptr != Root_Node)    // 如果没有重建{Search(Root_Node, k_nearest, point, q, max_dist);       // 就直接search}else                                                        // 如果正在重建{pthread_mutex_lock(&search_flag_mutex);while (search_mutex_counter == -1)                      // 就循环等待{pthread_mutex_unlock(&search_flag_mutex);usleep(1);pthread_mutex_lock(&search_flag_mutex);}search_mutex_counter += 1;pthread_mutex_unlock(&search_flag_mutex);Search(Root_Node, k_nearest, point, q, max_dist);       // 等待结束直接searchpthread_mutex_lock(&search_flag_mutex);search_mutex_counter -= 1;pthread_mutex_unlock(&search_flag_mutex);}int k_found = min(k_nearest, int(q.size()));PointVector().swap(Nearest_Points);vector<float>().swap(Point_Distance);for (int i = 0; i < k_found; i++)                           // 把找到的结果赋给输出变量{Nearest_Points.insert(Nearest_Points.begin(), q.top().point);Point_Distance.insert(Point_Distance.begin(), q.top().dist);q.pop();}return;
}

/*** 该函数是具体的search过程
*/
template <typename PointType>
void KD_TREE<PointType>::Search(KD_TREE_NODE *root, int k_nearest, PointType point, MANUAL_HEAP &q, float max_dist)
{if (root == nullptr || root->tree_deleted)return;float cur_dist = calc_box_dist(root, point);float max_dist_sqr = max_dist * max_dist;if (cur_dist > max_dist_sqr)return;int retval;if (root->need_push_down_to_left || root->need_push_down_to_right){retval = pthread_mutex_trylock(&(root->push_down_mutex_lock));if (retval == 0){Push_Down(root);                                        // 先进性一下属性向下传递pthread_mutex_unlock(&(root->push_down_mutex_lock));    // 保证比如,已经删了点不要search等}else{pthread_mutex_lock(&(root->push_down_mutex_lock));pthread_mutex_unlock(&(root->push_down_mutex_lock));}}if (!root->point_deleted){float dist = calc_dist(point, root->point);                 // 判断当前节点和查询点之间的距离,if (dist <= max_dist_sqr && (q.size() < k_nearest || dist < q.top().dist))  // 和堆上的第一个点进行比较,{if (q.size() >= k_nearest)                              // 如果小,就说明是top-k,就加到堆里q.pop();PointType_CMP current_point{root->point, dist};q.push(current_point);}}int cur_search_counter;float dist_left_node = calc_box_dist(root->left_son_ptr, point);        // 计算和左子节点的距离float dist_right_node = calc_box_dist(root->right_son_ptr, point);      // 计算和有子节点if (q.size() < k_nearest || dist_left_node < q.top().dist && dist_right_node < q.top().dist)    // 如果heap没满肯定要搜索{                                                                                               // 如果左右距离都比较小,肯定也要接着搜索if (dist_left_node <= dist_right_node)                              // 如果左侧距离小,肯定就先找左侧{if (Rebuild_Ptr == nullptr || *Rebuild_Ptr != root->left_son_ptr)   // 处理多线程rebuild情况{Search(root->left_son_ptr, k_nearest, point, q, max_dist);  // 递归找左侧}else{pthread_mutex_lock(&search_flag_mutex);while (search_mutex_counter == -1)                          // 如果正在重建就阻塞{pthread_mutex_unlock(&search_flag_mutex);usleep(1);pthread_mutex_lock(&search_flag_mutex);}search_mutex_counter += 1;pthread_mutex_unlock(&search_flag_mutex);Search(root->left_son_ptr, k_nearest, point, q, max_dist);  // 还是找左边pthread_mutex_lock(&search_flag_mutex);search_mutex_counter -= 1;pthread_mutex_unlock(&search_flag_mutex);}if (q.size() < k_nearest || dist_right_node < q.top().dist)     // 如果还没找满,或者右侧有更小的{if (Rebuild_Ptr == nullptr || *Rebuild_Ptr != root->right_son_ptr){Search(root->right_son_ptr, k_nearest, point, q, max_dist); // 就去找右边}else{pthread_mutex_lock(&search_flag_mutex);                 // 同样处理多线程rebuild的情况while (search_mutex_counter == -1){pthread_mutex_unlock(&search_flag_mutex);usleep(1);pthread_mutex_lock(&search_flag_mutex);}search_mutex_counter += 1;pthread_mutex_unlock(&search_flag_mutex);Search(root->right_son_ptr, k_nearest, point, q, max_dist);pthread_mutex_lock(&search_flag_mutex);search_mutex_counter -= 1;pthread_mutex_unlock(&search_flag_mutex);}}}else                    //这个是右子树距离小的情况,此时先找右子节点,找不满或左侧有更小的再看左侧{if (Rebuild_Ptr == nullptr || *Rebuild_Ptr != root->right_son_ptr){Search(root->right_son_ptr, k_nearest, point, q, max_dist);}else{pthread_mutex_lock(&search_flag_mutex);while (search_mutex_counter == -1){pthread_mutex_unlock(&search_flag_mutex);usleep(1);pthread_mutex_lock(&search_flag_mutex);}search_mutex_counter += 1;pthread_mutex_unlock(&search_flag_mutex);Search(root->right_son_ptr, k_nearest, point, q, max_dist);pthread_mutex_lock(&search_flag_mutex);search_mutex_counter -= 1;pthread_mutex_unlock(&search_flag_mutex);}if (q.size() < k_nearest || dist_left_node < q.top().dist){if (Rebuild_Ptr == nullptr || *Rebuild_Ptr != root->left_son_ptr){Search(root->left_son_ptr, k_nearest, point, q, max_dist);}else{pthread_mutex_lock(&search_flag_mutex);while (search_mutex_counter == -1){pthread_mutex_unlock(&search_flag_mutex);usleep(1);pthread_mutex_lock(&search_flag_mutex);}search_mutex_counter += 1;pthread_mutex_unlock(&search_flag_mutex);Search(root->left_son_ptr, k_nearest, point, q, max_dist);pthread_mutex_lock(&search_flag_mutex);search_mutex_counter -= 1;pthread_mutex_unlock(&search_flag_mutex);}}}}else        // 如果heap找满了,并且查询点到左右都有点远,或者右一个优点远{if (dist_left_node < q.top().dist)  // 对小的那一侧进行搜索{if (Rebuild_Ptr == nullptr || *Rebuild_Ptr != root->left_son_ptr){Search(root->left_son_ptr, k_nearest, point, q, max_dist);}else{pthread_mutex_lock(&search_flag_mutex);while (search_mutex_counter == -1){pthread_mutex_unlock(&search_flag_mutex);usleep(1);pthread_mutex_lock(&search_flag_mutex);}search_mutex_counter += 1;pthread_mutex_unlock(&search_flag_mutex);Search(root->left_son_ptr, k_nearest, point, q, max_dist);pthread_mutex_lock(&search_flag_mutex);search_mutex_counter -= 1;pthread_mutex_unlock(&search_flag_mutex);}}if (dist_right_node < q.top().dist){if (Rebuild_Ptr == nullptr || *Rebuild_Ptr != root->right_son_ptr){Search(root->right_son_ptr, k_nearest, point, q, max_dist);}else{pthread_mutex_lock(&search_flag_mutex);while (search_mutex_counter == -1){pthread_mutex_unlock(&search_flag_mutex);usleep(1);pthread_mutex_lock(&search_flag_mutex);}search_mutex_counter += 1;pthread_mutex_unlock(&search_flag_mutex);Search(root->right_son_ptr, k_nearest, point, q, max_dist);pthread_mutex_lock(&search_flag_mutex);search_mutex_counter -= 1;pthread_mutex_unlock(&search_flag_mutex);}}}return;
}

rebuild功能


/*** 重建树的线程执行函数
*/
template <typename PointType>
void KD_TREE<PointType>::multi_thread_rebuild()
{bool terminated = false;KD_TREE_NODE *father_ptr, **new_node_ptr;pthread_mutex_lock(&termination_flag_mutex_lock);terminated = termination_flag;pthread_mutex_unlock(&termination_flag_mutex_lock);while (!terminated)                                         // 是否收到了外部的停止指令,收到就停止{pthread_mutex_lock(&rebuild_ptr_mutex_lock);pthread_mutex_lock(&working_flag_mutex);if (Rebuild_Ptr != nullptr)                 // 重建指针不是空的{/* Traverse and copy */if (!Rebuild_Logger.empty())            // 这个不能是空的?{printf("\n\n\n\n\n\n\n\n\n\n\n ERROR!!! \n\n\n\n\n\n\n\n\n");}rebuild_flag = true;                    // 表明正在重建if (*Rebuild_Ptr == Root_Node)          // 看看是不是从根节点重建{Treesize_tmp = Root_Node->TreeSize; // 一些参数赋值Validnum_tmp = Root_Node->TreeSize - Root_Node->invalid_point_num;alpha_bal_tmp = Root_Node->alpha_bal;alpha_del_tmp = Root_Node->alpha_del;}KD_TREE_NODE *old_root_node = (*Rebuild_Ptr);   // 局部变量,记录一下要重建的节点地址father_ptr = (*Rebuild_Ptr)->father_ptr;        // 记录一下父节点PointVector().swap(Rebuild_PCL_Storage);        // 初始化,swap 是为了高效的清理内存数据// Lock Searchpthread_mutex_lock(&search_flag_mutex);         // 这个while的设计是等待其他线程中的条件满足,再向下进行while (search_mutex_counter != 0)               // 如果不满足,就会一直在这里阻塞{pthread_mutex_unlock(&search_flag_mutex);usleep(1);pthread_mutex_lock(&search_flag_mutex);}search_mutex_counter = -1;pthread_mutex_unlock(&search_flag_mutex);// Lock deleted points cachepthread_mutex_lock(&points_deleted_rebuild_mutex_lock);flatten(*Rebuild_Ptr, Rebuild_PCL_Storage, MULTI_THREAD_REC);   // 保存所有有效点,同时记录要删除的点// Unlock deleted points cachepthread_mutex_unlock(&points_deleted_rebuild_mutex_lock);// Unlock Searchpthread_mutex_lock(&search_flag_mutex);search_mutex_counter = 0;                       // 重新置位?pthread_mutex_unlock(&search_flag_mutex);pthread_mutex_unlock(&working_flag_mutex);/* Rebuild and update missed operations*/Operation_Logger_Type Operation;KD_TREE_NODE *new_root_node = nullptr;if (int(Rebuild_PCL_Storage.size()) > 0)        // 有效点数大于0{BuildTree(&new_root_node, 0, Rebuild_PCL_Storage.size() - 1, Rebuild_PCL_Storage);      // 重新构造树// Rebuild has been done. Updates the blocked operations into the new treepthread_mutex_lock(&working_flag_mutex);pthread_mutex_lock(&rebuild_logger_mutex_lock);int tmp_counter = 0;while (!Rebuild_Logger.empty()){Operation = Rebuild_Logger.front();                             // 取出缓存的操作max_queue_size = max(max_queue_size, Rebuild_Logger.size());Rebuild_Logger.pop();pthread_mutex_unlock(&rebuild_logger_mutex_lock);pthread_mutex_unlock(&working_flag_mutex);run_operation(&new_root_node, Operation);                       // 对树执行缓存操作tmp_counter++;if (tmp_counter % 10 == 0)usleep(1);pthread_mutex_lock(&working_flag_mutex);pthread_mutex_lock(&rebuild_logger_mutex_lock);}pthread_mutex_unlock(&rebuild_logger_mutex_lock);}/* Replace to original tree*/// pthread_mutex_lock(&working_flag_mutex);pthread_mutex_lock(&search_flag_mutex);while (search_mutex_counter != 0){pthread_mutex_unlock(&search_flag_mutex);usleep(1);pthread_mutex_lock(&search_flag_mutex);}search_mutex_counter = -1;pthread_mutex_unlock(&search_flag_mutex);if (father_ptr->left_son_ptr == *Rebuild_Ptr)   // 将这个新的子树,放到原先的整个树中{father_ptr->left_son_ptr = new_root_node;}else if (father_ptr->right_son_ptr == *Rebuild_Ptr){father_ptr->right_son_ptr = new_root_node;}else{throw "Error: Father ptr incompatible with current node\n";}if (new_root_node != nullptr)new_root_node->father_ptr = father_ptr;(*Rebuild_Ptr) = new_root_node;int valid_old = old_root_node->TreeSize - old_root_node->invalid_point_num;int valid_new = new_root_node->TreeSize - new_root_node->invalid_point_num;if (father_ptr == STATIC_ROOT_NODE)Root_Node = STATIC_ROOT_NODE->left_son_ptr;KD_TREE_NODE *update_root = *Rebuild_Ptr;while (update_root != nullptr && update_root != Root_Node){update_root = update_root->father_ptr;if (update_root->working_flag)break;if (update_root == update_root->father_ptr->left_son_ptr && update_root->father_ptr->need_push_down_to_left)break;if (update_root == update_root->father_ptr->right_son_ptr && update_root->father_ptr->need_push_down_to_right)break;Update(update_root);            // 由于子树发生了更新,要把更新结果传递回原先的树中}pthread_mutex_lock(&search_flag_mutex);search_mutex_counter = 0;pthread_mutex_unlock(&search_flag_mutex);Rebuild_Ptr = nullptr;pthread_mutex_unlock(&working_flag_mutex);rebuild_flag = false;/* Delete discarded tree nodes */delete_tree_nodes(&old_root_node);}else{pthread_mutex_unlock(&working_flag_mutex);}pthread_mutex_unlock(&rebuild_ptr_mutex_lock);pthread_mutex_lock(&termination_flag_mutex_lock);terminated = termination_flag;pthread_mutex_unlock(&termination_flag_mutex_lock);usleep(100);}printf("Rebuild thread terminated normally\n");
}

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

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

相关文章

故障诊断 | 基于双路神经网络的滚动轴承故障诊断

故障诊断 | 基于双路神经网络的滚动轴承故障诊断 目录 故障诊断 | 基于双路神经网络的滚动轴承故障诊断效果一览基本介绍程序设计参考资料效果一览 基本介绍 基于双路神经网络的滚动轴承故障诊断 融合了原始振动信号 和 二维信号时频图像的多输入(多通道)故障诊断方法 单路和双…

快速生成应用:AI大模型与低代码平台如何无缝结合提升效率?

引言&#xff1a;数字化时代的开发挑战 在数字化转型的浪潮中&#xff0c;快速响应市场需求已成为企业的核心竞争力。AI大模型与低代码平台的结合&#xff0c;为应用开发提供了一条更加智能、快速的路径。通过自动代码生成、智能推荐和持续优化&#xff0c;这一无缝结合大幅提升…

计算机前沿技术-人工智能算法-大语言模型-最新论文阅读-2024-09-19

计算机前沿技术-人工智能算法-大语言模型-最新论文阅读-2024-09-19 1. SAM4MLLM: Enhance Multi-Modal Large Language Model for Referring Expression Segmentation Authors: Yi-Chia Chen, Wei-Hua Li, Cheng Sun, Yu-Chiang Frank Wang, Chu-Song Chen SAM4MLLM: 增强多模…

21 基于51单片机的隧道车辆检测系统

目录 一、主要功能 二、硬件资源 三、程序编程 四、实现现象 一、主要功能 以AT89C51单片机为控制核心&#xff0c;实现对隧道环境的监测。采用模块化设计&#xff0c; 共分以下几个功能模块&#xff1a; 单片机最小系统模块、电源模块、气体传感模块、和显示模块等。 通过…

在电脑中增加一个新盘

找到此电脑右击找到管理点进去 找到磁盘管理点进去 找到D盘&#xff0c;点击D盘然后右击找到压缩卷点击&#xff0c;之后按照自己的意愿分盘容量 然后就一直点下一页 返回去就能看到新加卷F盘了 在此电脑中也可以查看 完成

ToB项目身份认证(一):基于目录的用户管理、LDAP和Active Directory简述

在ToB的项目里&#xff0c;公司部门之间是树状的关系&#xff0c;成员结构也类似。由于windows的使用范围很广&#xff0c;尤其是在企业里&#xff0c;所以它集成的Active Directory域服务往往企业应用需要兼容的。 什么是基于目录的用户管理&#xff1f; 基于目录的用户管理…

双十一快来了!什么值得买?分享五款高品质好物~

双十一大促再次拉开帷幕&#xff0c;面对众多优惠是否感到选择困难&#xff1f;为此&#xff0c;我们精心筛选了一系列适合数字生活的好物&#xff0c;旨在帮助每一位朋友都能轻松找到心仪之选。这份推荐清单&#xff0c;不仅实用而且性价比高&#xff0c;是您双十一购物的不二…

cc2530使用(SmartRF Flash Programmer)烧录hex固件

1图标 2IAR生成HEX文件 2-1勾选他 2-2生成对应的文件&#xff08;勾选他并改后缀&#xff09; &#xff08;选择他&#xff09; 点击OK即可 3&#xff08;选择文件&#xff0c;插入板子&#xff08;会显示对应的板子&#xff09;&#xff0c;烧录即可&#xff09;

LeetcodeTop100 刷题总结(二)

LeetCode 热题 100&#xff1a;https://leetcode.cn/studyplan/top-100-liked/ 文章目录 八、二叉树94. 二叉树的中序遍历&#xff08;递归与非递归&#xff09;补充&#xff1a;144. 二叉树的前序遍历&#xff08;递归与非递归&#xff09;补充&#xff1a;145. 二叉树的后序遍…

注册商标的有关流程

注册商标的有关流程 在商业活动中&#xff0c;商标作为企业品牌的重要组成部分&#xff0c;不仅代表着企业的形象和信誉&#xff0c;更是企业资产的重要部分。因此&#xff0c;了解并遵循注册商标的流程&#xff0c;对于保护企业的合法权益至关重要。 一、确定商标注册范围并进…

大模型学习方向不知道的,看完这篇学习思路好清晰!!

入门大模型并没有想象中复杂&#xff0c;尤其对于普通程序员&#xff0c;建议采用从外到内的学习路径。下面我们通过几个步骤来探索如何系统学习大模型&#xff1a; 1️⃣初步理解应用场景与人才需求 大模型的核心应用涵盖了智能体&#xff08;AI Agent&#xff09;、微调&…

【TPAMI 2024】告别误差,OPAL算法如何让光场视差估计变得轻而易举?

题目&#xff1a;OPAL: Occlusion Pattern Aware Loss for Unsupervised Light Field Disparity Estimation OPAL&#xff1a;面向无监督光场视差估计的遮挡模式感知损失 作者&#xff1a;Peng Li; Jiayin Zhao; Jingyao Wu; Chao Deng; Yuqi Han; Haoqian Wang; Tao Yu 摘要…

一个永久的.NET渗透工具和知识仓库

01前言 为了更好地应对基于.NET技术栈的风险识别和未知威胁&#xff0c;.NET安全攻防帮会从创建以来一直聚焦于.NET领域的安全攻防技术&#xff0c;定位于高质量安全攻防社区&#xff0c;也得到了许多师傅们的支持和信任&#xff0c;通过帮会深度连接入圈的师傅们&#xff0c;…

计算机毕业设计推荐-基于PHP的律所预约服务管理系统

精彩专栏推荐订阅&#xff1a;在下方主页&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f496;&#x1f525;作者主页&#xff1a;计算机毕设木哥&#x1f525; &#x1f496; 文章目录 一、基于PHP的律…

61.【C语言】数据在内存中的存储

1.前置知识 整数在内存中以补码形式存储 有符号整数三种码均有符号位,数值位 正整数:原码反码补码 负整数:原码≠反码≠补码 2.解释 int arr[] {1,2,3,4,5}; VSx86Debug环境下,内存窗口输入&arr VSx64Debug环境下,内存窗口输入&arr 存放的顺序都一样,均是小端序…

路由基础--路由引入

路由引入的主要作用是实现路由信息在不同路由协议之间的传递和学习。在大型企业网络中&#xff0c;多种路由协议共存是常态&#xff0c;为了实现全网互通&#xff0c;需要通过路由引入来传递路由信息。此外&#xff0c;执行路由引入时还可以部署路由控制&#xff0c;从而实现对…

Leetcode 2464. 有效分割中的最少子数组数目

1.题目基本信息 1.1.题目描述 给定一个整数数组 nums。 如果要将整数数组 nums 拆分为 子数组 后是 有效的&#xff0c;则必须满足: 每个子数组的第一个和最后一个元素的最大公约数 大于 1&#xff0c;且 nums 的每个元素只属于一个子数组。 返回 nums 的 有效 子数组拆分中…

【数据结构】Java的HashMap 和 HashSet 大全笔记,写算法用到的时候翻一下,百度都省了!(实践篇)

本篇会加入个人的所谓鱼式疯言 ❤️❤️❤️鱼式疯言:❤️❤️❤️此疯言非彼疯言 而是理解过并总结出来通俗易懂的大白话, 小编会尽可能的在每个概念后插入鱼式疯言,帮助大家理解的. &#x1f92d;&#x1f92d;&#x1f92d;可能说的不是那么严谨.但小编初心是能让更多人…

ESP32Cam人工智能教学22

ESP32Cam人工智能教学22 在线车牌识别装置 在第十六课《tencent-OCR》中&#xff0c;已经学会了使用腾讯在线识别车牌&#xff0c;但是用的是电脑中的Python程序&#xff0c;读取一张车牌图片内容&#xff0c;然后发送给腾讯服务器进行识别&#xff0c;并获取返回的识别结果。…

基于yolov5滑块识别破解(一)

由于内容较长&#xff0c;将分为两个部分来说明&#xff0c;本文讲解yolov5的部署与训练。 1.YOLOv5部署 云端部署&#xff08;训练&#xff09; 服务器创建 如果自己的显卡算力不是很好的&#xff0c;或者是核显电脑&#xff0c;可以租用算力&#xff0c;价格还行一块钱左右就…