1. 为什么你的3D模型看起来“脏兮兮”?网格滤波来救场
你是否可能经历过如下所述的一种状态, 即当你极其辛勤地通过操作将某一个三维模型从一台三维扫描仪的设备内部提取并导出出来时, 或者, 你在某个互联网平台上面下载到了一个外观十分精致且完美的三维空间资源作品之后, 一旦你将其放置到当前的某个软件或环境界面里面去直接打开进行检查与观察, 却发现其物体表面区域呈现出了一种崎岖不平、杂乱无章的状态, 并且表面上散布着数量众多的所谓噪点干扰信号, 使得整体视觉效果看起来就像是被一层灰色的污渍所覆盖和遮蔽了一样沉重。
又或者出现这样的现象情况是, 在你的三维模型最终进行图像渲染处理生成的过程中, 模型的轮廓边缘部分突然出现了不自然的锯齿状断裂痕迹和尖锐的毛刺突起物, 导致无论你怎么尝试去调整和分析光照光源的角度以及强度参数设置, 始终都无法让光影效果变得合理和顺畅。
那么, 造成这一切问题的根本原因核心所在, 往往直接指向了作为基础结构的网格数据模型在自身质量层面上的缺陷和不足这一事实问题本身。
在三维这个领域里, 一个完整的模型, 通常是靠着成千上万个特别细微的小三角形拼凑起来的, 这些三角形的顶点坐标数据, 直接决定了整个模型长什么样子, 可是, 当进行数据采集, 或者进行数据传输, 甚至还是对数据进行处理的这么一些过程中, 这些顶点的位置信息, 非常容易就混入一些不必要的噪声, 这样的话, 就会导致模型的表面不再平滑, 看上去疙疙瘩瘩的, 面对这种情况, 我们就需要拿出网格滤波与优化这一套工具组合来帮忙了。
简单来说, 网格滤波这件事, 它就像是给那个三维模型去做了一次美容和清洁工作似的, 它能平滑掉表面的那些毛刺和噪声, 让模型看起来更干净, 也更专业一点;而网格优化, 这事情更像是塑形和减负, 它可以一边保持住模型的大体形状, 一边减少一些不必要的三角形数量, 这样就能提升后续处理或者渲染的性能了。
这篇文章就是为了你而精心准备的实战指南。不管你是刚接触3D处理领域的在校学生, 还是在具体的项目中需要处理设备扫描数据的工程师。你都能够按照我的步骤, 一步一步地来进行操作。我会借助这个性能非常强大的类库。通过手把手的方式带着你去掌握均值滤波。通过手把手的方式带着你去掌握拉普拉斯滤波。
还有其他的各种基础的滤波手段。这些全部都属于最为核心的算法类型。我会向大家说明它们各自适用于什么样的业务场景。我会向大家解释参数该如何进行调整才会最好。我还会分享一些我自己在实际应用过程中所遇到并踩过的坑。我们不是只讲那些个原理, 我们更加注重实实在在的实战操作, 保证你能够看完之后马上就能用起来。
2. 动手之前:准备你的“手术台”
在开始对模型进行优化调整之前, 我们需要先把开发环境搭建好,并且还要准备一个用来做示例的预训练模型作为测试对象。请不用担心这个问题, 因为这个操作的整个流程其实是非常简单的。
2.1 安装与导入
首先的时候, 你需要确保说你自己的那个环境已经是处于一个已经准备好的状态了, 你去打开你自己电脑里面那个终端也好, 或者是命令提示符那个东西也行, 你就用pip这个安装工具去进行安装操作, 我个人是非常强烈建议你要使用虚拟环境这个东西来对你的那些依赖情况进行管理, 这样的话就可以避免发生各种包之间的冲突情况。
pip install open3d在完成安装这一步操作之后, 你需要在你的脚本的最开始地方, 把我们所需要的那些库给一一导入进来。
除此之外, 我们往往还会用到名为NumPy的那个东西, 主要是拿来做一些数值上的计算工作的, 同时, 我们也会使用copy这个模块, 专门用来对对象进行深度复制这样的操作,目的就是当你在处理原始数据的相关工作的时候, 不小心把原始数据给改动坏掉这种情况, 就不会发生了。
import open3d as o3dimport numpy as np
import copy
2.2 加载一个示例网格模型
他们十分贴心地, 给我们准备了一些内置的示例数据, 这些数据非常适合拿来学习和测试。同时, 你也是可以加载你自己的PLY文件、OBJ文件或STL文件的。
在这里我们会使用一个经典的“斯坦福兔子”模型, 这个模型在图形学那个领域里, 它的地位就像所谓的“Hello World”一样, 是一个非常基础且常见的存在。
# 方法一:使用Open3D自带的测试数据(需要互联网下载一次)bunny_mesh = o3d.data.BunnyMesh().path
mesh = o3d.io.read_triangle_mesh(bunny_mesh)
# 方法二:如果你有自己的文件,比如一个叫‘model.ply’的文件
# mesh = o3d.io.read_triangle_mesh(‘model.ply’)
# 快速检查一下模型基本信息
print(mesh)
print(f”顶点数:{len(mesh.vertices)}“)
print(f”三角形面数:{len(mesh.triangles)}“)
在加载全部完成以后, 我们可以先对它的原始样式进行可视化展示。请你一定要记得去计算顶点法线, 因为如果不这样做的话, 3D模型会呈现出灰蒙蒙的状态, 完全无法展现出应有的立体视觉感受。
mesh.compute_vertex_normals() # 计算法线用于着色o3d.visualization.draw_geometries([mesh], window_name=”原始兔子模型“)
现在, 你的手术台已经准备就绪, 而那个待处理的对象也安排妥当。当你看到它的时候, 可能发现其表面存在一种略微粗糙的状态, 这就是我们后续需要进行具体操作的主要目标。
在具体的实际操作过程里, 这种粗糙的情况往往会被不同程度地放大并表现得更加显著, 举个例子来说, 如果涉及到利用激光扫描技术来获取建筑物点的集合数据, 并将其重建为网格模型的话, 那么其中所蕴含的噪声干扰将会非常突出和明显。
3. 基础清洁工:均值滤波(Mean )
你不妨想象一下, 使用一块质地比较柔软的布料去轻轻地擦拭一个物体的表面, 其目的就是把那些凸起来的灰尘颗粒给抹平或者说抹匀了, 这个具体的动作其实也就是均值滤波最为直观的一种比喻形式。它属于所有那种基于网格进行的滤波算法里面的最简单类型之一, 而且也是相对最直接的。
3.1 算法原理:邻居的平均值
均值滤波的原理非常简单:对于网格中的每一个顶点,找到它所有相邻的顶点,然后将这个顶点的新位置设置为这些邻居顶点位置的平均值。用公式表达就是:
v_new = (v1 + v2 + … + vk) / k
在此之中, v_new指的是位于顶点处的新坐标位置, 而v1到vk表示与该顶点相连接的所有邻接顶点的集合, 至于k, 则代表着这些邻居的总数量。整个计算机制会对网格内部的所有顶点进行同步处理, 这一完整过程被称作一次迭代操作。
你能够将此项操作持续地重复使用, 随着迭代次数的逐渐增加, 所呈现出的平滑效果会越来越显著, 然而必须注意到的是, 模型本身也很有可能随之出现更加严重的收缩情况。
3.2 实战:用代码平滑一个噪声模型
我们应当先去制造出来一个存在着噪音问题的模型, 随后再使用均值滤波的方法对它进行修复处理, 如此以来你便更加清晰地看到其中的具体效果如何了。
# 1. 复制原始模型,避免污染原数据mesh_noisy = copy.deepcopy(mesh)
# 2. 手动添加随机噪声到每个顶点
vertices = np.asarray(mesh_noisy.vertices)
noise_strength = 0.005 # 噪声强度,根据你的模型尺寸调整
noise = np.random.randn(*vertices.shape) * noise_strength
vertices += noise
mesh_noisy.vertices = o3d.utility.Vector3dVector(vertices)
mesh