关注公众号,发现CV技术之美
最近在学习open3d的相关应用,然后遇到了一个很有趣的问题。给定多个mesh,我们可能会需要把他们全部合并到一个文件并使用。但是这并不好实现,因为open3d自己不支持这样的操作。相比之下,其他一些集成度非常高的软件,是可以实现这样的操作的,例如meshlab通过交互栏中的“flatten visible layer”指令来实现。
唯一的缺点是,你每次都需要手动操作才行,这对于需要高度自动化的使用场景,就不是很合适了。因此,如何可以实现一个自动化的脚本,支持直接合并多个可染色的mesh,并输出带有纹理的最终结果,是一个非常重要的功能。遗憾的是度娘和谷歌目前没有相关的教程。因此本文带大家了解一下,如何重头写一个ply文件并且合并输出所有需要合并的m
esh。
▍如何存储一个带纹理的obj格式的mesh
这里我们首先介绍一下,怎么去存储一个mesh。为了方便,我们使用这篇文章中的代码 (https://zhuanlan.zhihu.com/p/569974846),先自己生成若干个mesh(有些带有纹理,有些没有),然后进行存储。单模型存储在open3d中是很简单的,open3d提供了一个接口来直接存储对应的mesh,接口是o3d.io.write_triangle_mesh。
但是要注意的是,如果要存纹理信息,这个命令需要使用obj格式,因为另外一种常见的ply格式,则无法存储纹理信息。因此,作为合并的第一步,我们手动输出全部mesh为obj格式以支持纹理信息,并且分开存储。
以下代码把场景内的全部mesh文件输出为obj格式。
if not os.path.exists("save_mesh"):
os.makedirs("save_mesh", exist_ok=False)
o3d.io.write_triangle_mesh("save_mesh/obj_back.obj", back_obj)
o3d.io.write_triangle_mesh("save_mesh/obj_left.obj", left_obj)
o3d.io.write_triangle_mesh("save_mesh/obj_front.obj", front_obj)
o3d.io.write_triangle_mesh("save_mesh/obj_right.obj", right_obj)
o3d.io.write_triangle_mesh("save_mesh/box_back.obj", back_box)
o3d.io.write_triangle_mesh("save_mesh/box_left.obj", left_box)
o3d.io.write_triangle_mesh("save_mesh/box_front.obj", front_box)
o3d.io.write_triangle_mesh("save_mesh/box_right.obj", right_box)
▍如何存储一个带纹理的ply格式的mesh
存储为obj格式之后,我们通过meshlab自带的命令行格式,把所有带有纹理的mesh全部转化为ply文件。这里要注意的是,如果你的mesh模型本身是不带有色彩的,那么这一步可以直接加载mesh模型然后转为ply文件,上一步输出为obj格式则是可以跳过的。
下面我们依次加载obj文件并转存为ply文件。代码如下:
if not os.path.exists("save_mesh_ply"):
os.makedirs("save_mesh_ply", exist_ok=False)
for obj in ["save_mesh/obj_back.obj", "save_mesh/obj_left.obj", "save_mesh/obj_front.obj",
"save_mesh/obj_right.obj", "save_mesh/box_back.obj", "save_mesh/box_left.obj",
"save_mesh/box_front.obj", “save_mesh/box_right.obj"]: #简单粗暴的列出来所有mesh
ms = pymeshlab.MeshSet()
ms.load_new_mesh(obj)
ms.save_current_mesh(os.path.join(obj.split("/")[0]+"_ply", obj.split("/")[1].replace("obj", “ply")))
最终存储的mesh,重新使用meshlab可视化结果如下:
注意右侧红框,此时存在8个不同的层(layers)。我们的最终目的是把他们全部合并为一层并且统一存储。
▍ply文件格式介绍
下面我们来介绍一下ply文件格式的组成。ply文件有两个重要组成部分。第一组是头部(header),之后是对应于头部定义的数值组。
首先是头部(header)的定义,对于无纹理mesh文件来说,直接套用ply头部固定模板即可。
ply
format ascii 1.0
comment VCGLIB generated
element vertex vertex_count
property float x
property float y
property float z
property uchar red
property uchar green
property uchar blue
property uchar alpha
element face face_count
property list uchar int vertex_indices
end_header
关于无纹理文件的头部的定义,大部分情况下可以直接照抄,无需修改,除了点、面对应的数量(红色变量对应位置替换即可)。这里进一步解释一下关键字:header中的comment是注释的意思,property详细定义了所需要的数据结构。最后使用end_header标注定义结束。另外ply文件格式的编码,我强烈推荐使用ascii格式,否则使用文本编辑工具打开是乱码,不利于分析问题。
头部的定义具体包含了顶点与面的定义。对于不带纹理的ply文件,其对应顶点的定义需要如下关键参数