前言

  这是本博客网格生成算法系列的第三篇,第一篇里面介绍了最为流行的MarchingCubes算法,第二篇中使用新三角形表来对MC算法进行了简化改进,形成了SMC算法。而这篇将介绍一种新的不同与MC算法思路的新网格生成算法,叫做Cuberille法,这种算法的思想相比MC算法要简单,更加易于实现。

体素立方体模型

  根据第一篇的介绍,我们知道MC算法的基本模型是把组成三维图像的体素都当作空间上的点而8体素组成的体元作为立方体单元。相比于MC算法,Cuberille算法是把体素都想象成立方体,而没有所谓体元的概念了。这一点其实更加符合人们对图像的最初认识,最初接触到计算机中的二维图像的时候,一般人理解像素都是将其当成一个涂了颜色的小方块。而Cuberille算法的理解正是把组成三维图像的体素也当成涂了颜色的小立方体块。例如一个三维图像中所有的实点(这里的实点和MC算法里一样指的是内容区域的体素,下文的虚点同理指背景区域的体素)组成一个猪头的形状,那么就可以认为这个猪头是很多很小的立方体方块堆砌而成的。

         
猪头        三维图像中的猪头体素方块集合

基于正方形片为边界建模

  使用小立方体堆砌组成内容区域,那么这个内容区域的边界必然就是很多正方形面片组成的Mesh,由于每个正方形面片能分成两个三角形面片,那么实际上这个表面的Mesh也是三角网格。下文的重点是研究如何找到这些组成边界面的小正方形面片。

  首先,小正方形面片总是介于实点和虚点之间,下图使用二维的情况来说明这个边界。

   
例子1   例子2   说明

  可以看出,实点和虚点的边界面总是存在一个边界,其长度等于单位长度。那么只要找到图像中所有这样的虚实点交界处的边界,将其焊接起来就能组成内容的表面模型。

  因而总结出Cbuerille算法的主题思路如下:

  1. 创建一个空的Mesh
  2. 按层、列、行三重循环遍历所有的体素V
    1. 假如V为实点
      1. 获取V的六邻域体素集合Adj6(V)
      2. 遍历Adj6(V)中的体素T
        1. 若T超出图像范围或者T为虚点
        2. 创建介于V与T之间的正方形片,并加入到Mesh

  下一步就是探讨如何来为实体素V和它的6邻接邻居虚点T(这个T也可能是超出图像范围的点,这里当其为虚点也一样的处理)中间建立方形片并加入到Mesh。为了实现高效统一的创建面片的方案,有必要对邻域和顶点进行编号。下图是一个邻域编号。显示了一个体素六个方向邻域的编号与对应坐标计算方式:

邻接体素编号 相对偏移
0 (0,1,0)
1 (0,-1,0)
2 (1,0,0)
3 (-1,0,0)
4  (0,0,1)
5 (0,0,-1) 
6邻域指示 6邻域邻接体素编号 邻接体素方位对照表

  同时为了表示立方体8个体素位置的顶点,我们也为他进行编号如下图表所示:

体素编号 体素偏移
0 (0,1,1)
1 (1,1,1)
2 (1,0,1)
3 (0,0,1)
4 (0,0,0)
5 (1,0,0)
6 (1,1,0)
7 (0,1,0)
预览图 体素方位对照表

  这样一个立方体的6个正方形面里的三角形组成就可以使用下面的图表来表示:

邻接方位编号 方位描述 三角形顶点索引
0 上面 (0,1,6)
(0,6,7)
1 下面 (3,4,5)
(3,5,2)
2 右面 (1,2,5)
(1,5,6)
3 左面 (0,7,4)
(0,4,3)
4 前面 (0,3,2)
(0,2,1)
5 后面 (4,7,6)
(4,6,5)
预览图(只标示出了前方向的正方形) 三角形顶点对照表

  这样只需要知道T是V的第几个邻居就可以创建三角片了。下面的步骤描述了这个逻辑过程。

  1. 根据T的索引 r 找出其第一个三角片的三个顶点索引。
  2. 根据三个顶点索引找出三个顶点坐标偏移量
  3. 使用V的坐标加上偏移量计算出三角形三个顶点的坐标位置
  4. 创建并添加这个三角形。
  5. 根据T的索引 r 找出其第二个三角片的三个顶点索引。
  6. 根据三个顶点索引找出三个顶点坐标偏移量
  7. 使用V的坐标加上偏移量计算出三角形三个顶点的坐标位置
  8. 创建并添加这个三角形。

  注意这里对偏移量做了一些处理,本来应该的偏移量应该是用浮点数0.5来组成的,如下图所示,焊接点都处在体素位置中点处,所以三角形顶点都应该是“X.5”形式的浮点数。而上文的过程中使用的偏移量(如顶点对应表中的偏移量)是将所有点的坐标都加了0.5之后的位置。之所以使用平移0.5之后的坐标,是希望利用整数点坐标来使用哈希表焊接。那么有必要在算法结束的时候把所有的点坐标-0.5来恢复真正的位置。不过有时由于只需要结果的形状一致,所以也不做处理。

  最后是用C#实现的Cuberille算法的代码,其中涉及到的bitmap类,哈希表类,Meshbuilder类在前几篇文章中多次提到过,就不重复粘贴代码了。

public struct Int16Triple
{
public int X;
public int Y;
public int Z;
public Int16Triple(int x, int y, int z)
{
X = x;
Y = y;
Z = z;
}
}
public struct FloatTriple
{
public float X;
public float Y;
public float Z;
public FloatTriple(float x, float y, float z)
{
X = x;
Y = y;
Z = z;
}
}
public class CuberilleProcessor
{
public static Int16Triple[][] AdjIndexToVertexIndices = new Int16Triple[6][]
{
new Int16Triple[2] { new Int16Triple(0, 1, 6), new Int16Triple(0, 6, 7) },
new Int16Triple[2] { new Int16Triple(3, 4, 5), new Int16Triple(3, 5, 2) },
new Int16Triple[2] { new Int16Triple(1, 2, 5), new Int16Triple(1, 5, 6) },
new Int16Triple[2] { new Int16Triple(0, 7, 4), new Int16Triple(0, 4, 3) },
new Int16Triple[2] { new Int16Triple(0, 3, 2), new Int16Triple(0, 2, 1) },
new Int16Triple[2] { new Int16Triple(4, 7, 6), new Int16Triple(4, 6, 5) },
};
public static Int16Triple[] VertexIndexToPositionDelta = new Int16Triple[8]
{
new Int16Triple(0, 1, 1),
new Int16Triple(1, 1, 1),
new Int16Triple(1, 0, 1),
new Int16Triple(0, 0, 1),
new Int16Triple(0, 0, 0),
new Int16Triple(1, 0, 0),
new Int16Triple(1, 1, 0),
new Int16Triple(0, 1, 0),
};
BitMap3d bmp;
public CuberilleProcessor(BitMap3d bitmap)
{
bmp = bitmap;
}
public Mesh GeneratorSurface()
{
int Width = bmp.width;
int Height = bmp.height;
int Depth = bmp.depth;
Int16Triple[] adjPoints6 = new Int16Triple[6];
MeshBuilder_IntegerVertex mb = new MeshBuilder_IntegerVertex(bmp.width, bmp.height, bmp.depth); for (int k = 0; k <= Depth - 1; k++)
{
for (int j = 0; j <= Height - 1; j++)
{
for (int i = 0; i <= Width - 1; i++)
{
if (IsInside(i,j,k))
{
Int16Triple p = new Int16Triple(i, j, k);
InitAdj6(adjPoints6,p);
for (int r = 0; r < adjPoints6.Length; r++)
{
Int16Triple t = adjPoints6[r];
if (!IsInside(t.X,t.Y,t.Z))
{
ExtractSquare(r,p,mb);
}
}
}
}
}
}
Mesh m= mb.GetMesh();
for (int i = 0; i < m.Vertices.Count; i++)
{
Point3d p = m.Vertices[i];
p.X -= 0.5f;
p.Y -= 0.5f;
p.Z -= 0.5f;
}//若需要真实位置,则都得平移回去
return m;
} private void ExtractSquare(int r,Int16Triple p, MeshBuilder_IntegerVertex mb)
{
int p0x, p0y, p0z, p1x, p1y, p1z, p2x, p2y, p2z;//
Int16Triple deltaA0 = VertexIndexToPositionDelta[AdjIndexToVertexIndices[r][0].X];
Int16Triple deltaA1 = VertexIndexToPositionDelta[AdjIndexToVertexIndices[r][0].Y];
Int16Triple deltaA2 = VertexIndexToPositionDelta[AdjIndexToVertexIndices[r][0].Z];
p0x = p.X + deltaA0.X;
p0y = p.Y + deltaA0.Y;
p0z = p.Z + deltaA0.Z;
p1x = p.X + deltaA1.X;
p1y = p.Y + deltaA1.Y;
p1z = p.Z + deltaA1.Z;
p2x = p.X + deltaA2.X;
p2y = p.Y + deltaA2.Y;
p2z = p.Z + deltaA2.Z;
mb.AddTriangle(new Int16Triple(p0x, p0y, p0z), new Int16Triple(p1x, p1y, p1z), new Int16Triple(p2x, p2y, p2z)); Int16Triple deltaB0 = VertexIndexToPositionDelta[AdjIndexToVertexIndices[r][1].X];
Int16Triple deltaB1 = VertexIndexToPositionDelta[AdjIndexToVertexIndices[r][1].Y];
Int16Triple deltaB2 = VertexIndexToPositionDelta[AdjIndexToVertexIndices[r][1].Z]; p0x = p.X + deltaB0.X;
p0y = p.Y + deltaB0.Y;
p0z = p.Z + deltaB0.Z;
p1x = p.X + deltaB1.X;
p1y = p.Y + deltaB1.Y;
p1z = p.Z + deltaB1.Z;
p2x = p.X + deltaB2.X;
p2y = p.Y + deltaB2.Y;
p2z = p.Z + deltaB2.Z;
mb.AddTriangle(new Int16Triple(p0x, p0y, p0z), new Int16Triple(p1x, p1y, p1z), new Int16Triple(p2x, p2y, p2z));
}
public virtual bool IsInside(int x, int y, int z)
{
if (x <= 0 || y <= 0 || z <= 0 || x > bmp.width || y > bmp.height || z > bmp.depth)
return false;
else
{
return bmp.GetPixel(x, y, z) == BitMap3d.WHITE;
}
}//judge if a voxel is inside the surface public static void InitAdj6(Int16Triple[] adjPoints6,Int16Triple p)
{
adjPoints6[0].X = p.X;
adjPoints6[0].Y = p.Y + 1;
adjPoints6[0].Z = p.Z; adjPoints6[1].X = p.X;
adjPoints6[1].Y = p.Y - 1;
adjPoints6[1].Z = p.Z; adjPoints6[2].X = p.X + 1;
adjPoints6[2].Y = p.Y;
adjPoints6[2].Z = p.Z; adjPoints6[3].X = p.X - 1;
adjPoints6[3].Y = p.Y;
adjPoints6[3].Z = p.Z; adjPoints6[4].X = p.X;
adjPoints6[4].Y = p.Y;
adjPoints6[4].Z = p.Z + 1; adjPoints6[5].X = p.X;
adjPoints6[5].Y = p.Y;
adjPoints6[5].Z = p.Z - 1;
}//initialize poistions of the 6-adjacency points
}

算法结果

  算法使用Engine.raw数据,生成的Cuberille表面放大后的效果图如下,可以看出确实是由方块组成的模型:

  同时使用SMC算法对Engine数据生成表面,放在一起进行对比呈现,同时比较他们的网格规模的图表如下:

- SMC算法 Cuberille算法
预览图
顶点数 216147 311263
三角形数 432370 622638

  可以看出Cuberille的显示效果显然不如SMC算法平滑(自然也不会比经典MC算法平滑),同时相比SMC算法输出规模更大。这些都是Cuberille算法的缺点,所以现在实际应用场合中较少使用这个算法,不过在一些特殊场合,还是会见到这个算法的身影。

  本文的代码可见本人的github:https://github.com/chnhideyoshi/SeededGrow2d/tree/master/CubicSurface

  爬网的太疯狂了,转载本文要注明出处啊:http://www.cnblogs.com/chnhideyoshi/

图像数据到网格数据-3——实现Cuberille算法的更多相关文章

  1. 图像数据到网格数据-1——MarchingCubes算法

    原文:http://blog.csdn.net/u013339596/article/details/19167907 概述 之前的博文已经完整的介绍了三维图像数据和三角形网格数据.在实际应用中,利用 ...

  2. 图像数据到网格数据-2——改进的SMC算法的实现

    概要 本篇接上一篇继续介绍网格生成算法,同时不少内容继承自上篇.上篇介绍了经典的三维图像网格生成算法MarchingCubes,并且基于其思想和三角形表实现了对样例数据的网格构建.本篇继续探讨网格生成 ...

  3. 图像数据到网格数据-1——Marching Cubes算法的一种实现

    概述 之前的博文已经完整的介绍了三维图像数据和三角形网格数据.在实际应用中,利用遥感硬件或者各种探测仪器,可以获得表征现实世界中物体的三维图像.比如利用CT机扫描人体得到人体断层扫描图像,就是一个表征 ...

  4. CUDA 实现JPEG图像解码为RGB数据

    了解JPEG数据格式的人应该easy想到.其对图像以8*8像素块大小进行切割压缩的方法非常好用并行处理的思想来实现.而其实英伟达的CUDA自v5.5開始也提供了JPEG编解码的演示样例.该演示样例存储 ...

  5. KubeCon 2021|使用 eBPF 代替 iptables 优化服务网格数据面性能

    作者 刘旭,腾讯云高级工程师,专注容器云原生领域,有多年大规模 Kubernetes 集群管理及微服务治理经验,现负责腾讯云服务网格 TCM 数据面产品架构设计和研发工作. 引言 目前以 Istio[ ...

  6. 北京市行政村边界shp数据/北京市乡镇边界/北京市土地利用分类数据/北京市气象数据/降雨量分布数据/太阳辐射数据

     数据下载链接:数据下载链接​ 北京是一座有着三千多年历史的古都,在不同的朝代有着不同的称谓,大致算起来有二十多个别称.北京地势西北高.东南低.西部.北部和东北部三面环山,东南部是一片缓缓向渤海倾斜的 ...

  7. 广西省行政村边界shp数据/广西省乡镇边界/广西省土地利用分类数据/广西省气象数据/降雨量分布数据/太阳辐射数据

    ​  数据下载链接:数据下载链接 广西壮族自治区,地处中国南部,北回归线横贯中部,属亚热带季风气候区.南北以贺州--东兰一线为界,此界以北属中亚热带季风气候区,以南属南亚热带季风气候区. 数据范围:全 ...

  8. 第二篇:R语言数据可视化之数据塑形技术

    前言 绘制统计图形时,半数以上的时间会花在调用绘图命令之前的数据塑型操作上.因为在把数据送进绘图函数前,还得将数据框转换为适当格式才行. 本文将给出使用R语言进行数据塑型的一些基本的技巧,更多技术细节 ...

  9. 数据库之mysql篇(3)—— mysql创建/修改数据表/操作表数据

    创建数据表:create table 数据表名 1.创建表规范 create table 表名( 列名   数据类型    是否为空   自动排序/默认值  主键/外键/唯一键, 列名   数据类型 ...

随机推荐

  1. "delete this" in C++

    Ideally delete operator should not be used for this pointer. However, if used, then following points ...

  2. BigDecimal 计算注意事项

    BigDecimal 在进行除法运算(divide)时一定要注意:如果被除数为变量,一定要指定精度 和 舍入模式,否则会报:Non-terminating decimal expansion; no ...

  3. 【Linux】【Services】【Docker】网络

    容器的网络模型: closed container: 仅有一个接口:loopback 不参与网络通信,仅适用于无须网络通信的应用场景,例如备份.程序调试等: --net none bridged co ...

  4. Docker常用image

    MySQL Start a mysql server instance Starting a MySQL instance is simple: docker run -itd --name mysq ...

  5. 【Java多线程】CompletionService

    什么是CompletionService? 当我们使用ExecutorService启动多个Callable时,每个Callable返回一个Future,而当我们执行Future的get方法获取结果时 ...

  6. 记一次ssh连接慢

    2020-03-28日机房搬迁完后,发现有一台60服务器ssh连接特别慢,但是其他服务器正常; 下面是解决过程: vim /etc/ssh/sshd_config       (编辑配置文件) 查找F ...

  7. 【MySQL】查询不在表中的数据

    1.方法一(仅适用单个字段):使用 not in ,比较容易理解,缺点是效率低 如:select A.ID from A where A.ID not in (select ID from B): 2 ...

  8. ExecutorService 线程池详解

    1.什么是ExecutorService,为什么要使用线程池? 许多服务器应用程序都面向处理来自某些远程来源的大量短小的任务,每当一个请求到达就创建一个新线程,然后在新线程中为请求服务,但是频繁创建新 ...

  9. Gitlab-CICD实践篇

    一.背景 随着公司项目使用gitlab越来越多,业务发布的次数越来越频繁,对于发布效率提出了更高的要求.从2012开始,Gitlab官方开始集成了Continuous Integration (CI) ...

  10. Nginx安全检查

    1.检查是否配置Nginx账号锁定策略 描述 1.执行系统命令passwd -S nginx来查看锁定状态 出现Password locked证明锁定成功 如:nginx LK ..... (Pass ...