三维体数据分割算法
本文基于分裂合并分割算法,提出了两种新的分割算法:基于八叉树的分裂合并算法和基于自适应包围盒的分裂合并算法。下面将对这两种算法进行描述。
1 分裂合并法分析
区域生长法的原理是根据种子像素点(体数据中为体素点)向其周围扩散,对区域周围的每一个像素/体素进行分析。区域生长法需要用额外的内存区域来保存待生长的像素/体素点,并且算法的时间复杂度较大。区域生长的过程中,需要以像素/体素为单位一圈一圈地向外扩张。对于体数据来说,区域生长法花费较多的运算时间。
分裂合并的基本思想是:先将整幅图像或体数据依据某种规则分裂出很多个形状规则的子区域,子区域内的像素或体素特征具有一致性,然后合并特征相似的子区域,从而实现分割的目的[2]。体数据分裂后的各子区域是一个立方体的体素集合,集合内所有体素属于同一区域组织。分裂的同时需要对子区域生成连通图,即将体数据中分裂区域之间的邻接关系以图的形式表达出来。合并的工作与区域生长法相似,先选择一个区域生长的种子点,确定该种子点所在的正方体分裂区域;然后以该区域为中心,对其所有相邻的子区域进行判断;如果相邻区域与该区域是同一组织则进行合并。该算法实际是以立方体节点为最小单位对种子点进行生长,因此其运算效率要优于区域生长法。在区域分裂时需要生成区域邻接图,通过邻接图表示各子区域间的相邻关系。在区域合并时,需要通过邻接图来查找等待合并的子区域。
分裂的子区域结构需要包含以下几种数据:
(1)空间包围盒。用来记录子区域的大小和位置;
(2)内部数据特征值。可以记录所包含体素的灰度特征,分布情况等;
(3)相邻节点。每个非边界节点至少有6个相邻节点(每个立方体面一个)。在区域合并时需要判断该节点的所有相邻节点。
分裂子区域结构为如下代码中的结构体BoxNode。该结构中保存着其数据区域、特征值、与之相邻的子区域。
struct BoxNode // 节点包围盒结构体,每一个节点表示一个分裂后的子区域 { Point ptStart; // 节点的开始位置 Point ptEnd; // 节点的结束位置 WORD voxelValue; // 体素值 long nMergeRagion; // 该节点的合并区域,未合并时值为-1 std::set<BoxNode*> setNeighbours; // 相邻节点指针集合 void AddNeighbour(BoxNode* nodePtr) // 添加相邻节点 {setNeighbours.insert(nodePtr); } void RemoveNeighbour(BoxNode* nodePtr) // 移除相邻节点 {setNeighbours.erase(nodePtr);} }; |
2 基于八叉树的分裂算法
对于二维图像来说,可以采用四叉树的方式对其进行分裂:首先选取区域一致性准则(如像素灰度值),然后根据这一准则将图像等分成四个区域,并分别判断这些区域是否满足一致性准则;如果不满足一致性,则继续分裂[24]。本课题所研究的是对三维体数据的分割,因此对其分裂时将基于二维的四叉树方法扩展为针对三维的八叉树。
八叉树是每个非叶节点有且仅有8个子节点的一种树形结构体,它是表现一个被立方体封装的三维物体的理想结构[5]。对于有着物体密集的数据集合,八叉树能够快速的进行数据管理、可视化裁剪、光线跟踪等三维空间操作。八叉树的根节点包含一个立方体,它封闭着全部体数据。每个节点的子节点是8个相同大小的立方体,它们将父节点等分为八份。如图5-6所示。
a.初始节点 b.第一次分裂 c.第二次分裂
图5- 6八叉树模型
用Volume表示整个体数据区域,P表示区域特征一致性测度的谓词逻辑,从最高层开始,将Volume分裂成8个相同的正方体子区域Volumei,对于任一个区域Volumei如果P(Volumei)为false就将继续对Volumei进行8等分,直至P(Volumei)为true或Volumei为单个体素为止。对于一个2n 2n 2n的体数据,最多可以分解至n层,第n层数据区域为单个体素。对于本文实验所采用的医学图像来说,图像分辨率为512 512。在对XOY平面上进行分裂时最多可以分解至第8层。假如断层数目为M,那么处理时将Z轴方向上的断层个数设为2k,使得 2k<= M< 2(k+1)。在八叉树分裂的同时需要生成区域邻接图,即每次分裂后,新生成的8个节点是上下左右相邻的,然后判断每个新生成节点与原相邻节点是否相邻。
图5- 7基于八叉树的分裂算法流程图 |
基于八叉树分裂算法的步骤如下所述:
a.生成一个包含整个体数据的节点,该节点的开始位置为原点:
ptStart(0, 0, 0);结束位置为体数据的最大值ptEnd(xmax, ymax, zmax);
b. 对步骤a生成的节点进行特征一致性检测,如果不一致则该节点需要进行分裂处理,跳到步骤c,否则,跳到结束分裂;
c.将节点进行八叉树分裂处理,即由该节点分裂出八个新的子节点;
d.建立新生成的八个子节点的相邻关系,并更新新节点与旧节点的相邻关系;
e.对新生成的八个子节点分别做特征一致性检测,如果不一致,则对该节点进行步骤c的操作;
f.分裂完成。
基于八叉树分裂算法采用分治递归的策略,能够快速有效的对体数据进行分裂处理。由于该算法将一个问题拆分成8个子问题,所以可以采用并行的多CPU运算对其进行优化。但基于八叉树分裂算法会产生过度分裂的情况,假如体数据中一个特征一致性区域正好位于某个待分裂节点的中心位置,那么使用八叉树会将该区域分裂成八份。
3 基于自适应包围盒的分裂算法
为了解决区域的过度分裂的问题,本文提出一种基于自适应包围盒的分裂算法,能有效解决这一过度分裂问题。该算法的基本思想是一次性的遍历体数据中每个体素,找出所有具有特征值一致性的立方体区域,然后生成这些立方体区域的邻接图。
图5- 8基于自适应包围盒的分裂算法流程图
自适应包围盒的分裂算法的实现步骤如下所述:
a. 首先创建一个分裂区域子节点集合setNodes,和一个与体数据相同大小的数据区域NodeArray。NodeArray用于存放每个体素所在子节点的地址;还需要将NodeArray的数据内容设置为空;
b.依次遍历体数据中的每一个体素点Voxel(xi, yi,zi);当完成遍历后,跳转到步骤g。
c.判断Voxel(xi,yi,zi)是否已经被扩展过;判断方法是看其对应NodeArray(xi,yi,zi)中的值是否为空;如果是扩展过的,则返回步骤b;
d.创建一个子区域节点Nodej,该节点的起始位置为Voxel(xi,yi,zi);
e.分别对X、Y、Z三个坐标轴的正方向进行扩展,并判断新扩展的体素与Voxel(xi,yi,zi)是否具有特征一致性;当某一方向出现不一致的体素时,则停止该方向的扩展;
f.步骤e完成后会得到节点Nodej的结束位置Voxel(xi+m, yi+n,zi+k);先将Nodej所包含的体素Voxel(xs,ys,zs)所对应NodeArray(xs,ys,zs)的值设置为Nodej的地址,再将其添加到子节点集合setNodes中;
g.在遍历完体数据中所有体素后,通过遍历NodeArray中的数据,生成子区域节点的相邻关系。
自适应包围盒的分裂算法能够只通过两次遍历体数据,就可以对体数据进行分裂;并且分裂后的区域不会有像八叉树那种过度分裂的情况。
4 节点合并算法
该阶段与区域生长算法很相似:首先选择一个种子点,获得该种子点所在的子区域Vmerge。根据区域邻接图,对任意与Vmerge相邻的子区域Vi,若P(Vmerge U Vi)==true,则将其合并并将Vi的相邻子区域设为Vmerge相邻的子区域。直至没有满足合并条件的相邻区域为止。
a.通过种子点的坐标位置找到种子点所在的子区域节点;然后创建一个待合并的队列,并将刚才找到的子区域节点放到该队列中;再将该子区域节点放到合并区域集合中;
b.从待合并队列中取出一个子区域节点;如果待合并队列为空则表示合并完成,跳转到步骤d;
c.依次对步骤b中取出的节点的相邻节点做一致性检测,如果检测通过,则将该相邻节点分别放到待合并队列和合并区域集合中;
d.完成合并操作。
代码实现:
1 /****************************************************************
2 File name : VolumeDataSplitMerge.CPP
3 Author : 叶峰
4 Version : 1.0a
5 Create Date : 2011/12/01
6 Description :
7 Others :
8 *****************************************************************/
9
10 // --------------------------------------------------------------------------------------
11
12 #include <Windows.h>