【三维几何学习】网格简化-ModelNet10

引言

计算机算力有限,特别是在深度学习领域,撇开网格的输入特征计算,现有条件很难直接训练测试高分辨的网格。故需要对网格进行简化:

常规网格简化算法简介

一、网格的简化

数据集可参考:三角网格(Triangular Mesh)分类数据集 - ModelNet及其水密版

分别使用open3DMeshlabblender对bathtub_0141.obj进行简化,未调参

1.1 水密网格的简化可视化

在这里插入图片描述
对水密网格进行简化,可以保持其大致形状,但是细节较模糊,也许加入平滑会更好
在这里插入图片描述

1.2 非水密网格的简化可视化

在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述
对非水密的离散网格进行简化,可能会使其扭曲,如上图所示
结果文件如下:
请添加图片描述

1.3 核心代码

import os
import subprocess
import open3d
import time
import numpy as np
from blender_process import Process
# https://github.com/ranahanocka/MeshCNN/blob/master/scripts/dataprep/blender_process.py


def load_obj(file):
    f = open(file)
    vs, faces = [], []
    for line in f:
        line = line.strip()
        splitted_line = line.split()
        if not splitted_line:
            continue
        elif splitted_line[0] == 'v':
            vs.append([float(v) for v in splitted_line[1:4]])
        elif splitted_line[0] == 'f':
            face_vertex_ids = [int(c.split('/')[0]) for c in splitted_line[1:]]
            assert len(face_vertex_ids) == 3
            face_vertex_ids = [(ind - 1) if (ind >= 0) else (len(vs) + ind)
                               for ind in face_vertex_ids]
            faces.append(face_vertex_ids)
    f.close()
    return np.array(vs), np.array(faces, dtype=int)


if __name__=='__main__':
    # 0.简化网格面个数设定
    face_num = 500

    # 1.网格路径 读取网格
    load_dir = './datasets/bathtub_0141.obj'
    save_dir = './datasets/bathtub_0141_' + str(face_num)

    # 2.1 open3D简化并保存
    start_time = time.time()
    vs, faces = load_obj(load_dir)
    m = open3d.geometry.TriangleMesh()
    m.vertices = open3d.utility.Vector3dVector(vs)
    m.triangles = open3d.utility.Vector3iVector(faces)
    m = m.simplify_quadric_decimation(face_num)
    open3d.io.write_triangle_mesh(save_dir + '_open3d.obj', m)
    t = (time.time() - start_time)
    print('open3D简化并保存:', t)

    # 2.2 Meshlab简化并保存
    start_time = time.time()
    temp = subprocess.run(["D:/software/MeshLab/meshlabserver.exe",
                           "-i", os.path.abspath(load_dir),
                           "-o", os.path.abspath(save_dir + '_meshlab.obj'),
                           "-s", "D:/vm/QECD_500.mlx"])
    t = (time.time() - start_time)
    print('Meshlab简化并保存:', t)

    # 2.3 blender简化并保存
    start_time = time.time()
    blender = Process(os.path.abspath(load_dir),
                      face_num,
                      os.path.abspath(save_dir + '_blender.obj'))
    t = (time.time() - start_time)
    print('blender简化并保存:', t)

# bathtub_0141.obj 单位秒
# open3D简化并保存: 0.769096851348877     0.9059770107269287
# Meshlab简化并保存: 0.7407355308532715   1.652160406112671
# blender简化并保存: 0.5777468681335449   0.7607262134552002

QECD_500.mlx

<!DOCTYPE FilterScript>
<FilterScript>
 <filter name="Simplification: Quadric Edge Collapse Decimation">
  <Param name="TargetFaceNum" type="RichInt" tooltip="The desired final number of faces." description="Target number of faces" value="500"/>
  <Param name="TargetPerc" type="RichFloat" tooltip="If non zero, this parameter specifies the desired final size of the mesh as a percentage of the initial size." description="Percentage reduction (0..1)" value="0"/>
  <Param name="QualityThr" type="RichFloat" tooltip="Quality threshold for penalizing bad shaped faces.&lt;br>The value is in the range [0..1]&#xa; 0 accept any kind of face (no penalties),&#xa; 0.5  penalize faces with quality &lt; 0.5, proportionally to their shape&#xa;" description="Quality threshold" value="0.3"/>
  <Param name="PreserveBoundary" type="RichBool" tooltip="The simplification process tries to do not affect mesh boundaries during simplification" description="Preserve Boundary of the mesh" value="false"/>
  <Param name="BoundaryWeight" type="RichFloat" tooltip="The importance of the boundary during simplification. Default (1.0) means that the boundary has the same importance of the rest. Values greater than 1.0 raise boundary importance and has the effect of removing less vertices on the border. Admitted range of values (0,+inf). " description="Boundary Preserving Weight" value="1"/>
  <Param name="PreserveNormal" type="RichBool" tooltip="Try to avoid face flipping effects and try to preserve the original orientation of the surface" description="Preserve Normal" value="false"/>
  <Param name="PreserveTopology" type="RichBool" tooltip="Avoid all the collapses that should cause a topology change in the mesh (like closing holes, squeezing handles, etc). If checked the genus of the mesh should stay unchanged." description="Preserve Topology" value="false"/>
  <Param name="OptimalPlacement" type="RichBool" tooltip="Each collapsed vertex is placed in the position minimizing the quadric error.&#xa; It can fail (creating bad spikes) in case of very flat areas. &#xa;If disabled edges are collapsed onto one of the two original vertices and the final mesh is composed by a subset of the original vertices. " description="Optimal position of simplified vertices" value="false"/>
  <Param name="PlanarQuadric" type="RichBool" tooltip="Add additional simplification constraints that improves the quality of the simplification of the planar portion of the mesh." description="Planar Simplification" value="false"/>
  <Param name="QualityWeight" type="RichBool" tooltip="Use the Per-Vertex quality as a weighting factor for the simplification. The weight is used as a error amplification value, so a vertex with a high quality value will not be simplified and a portion of the mesh with low quality values will be aggressively simplified." description="Weighted Simplification" value="false"/>
  <Param name="AutoClean" type="RichBool" tooltip="After the simplification an additional set of steps is performed to clean the mesh (unreferenced vertices, bad faces, etc)" description="Post-simplification cleaning" value="true"/>
  <Param name="Selected" type="RichBool" tooltip="The simplification is applied only to the selected set of faces.&#xa; Take care of the target number of faces!" description="Simplify only selected faces" value="false"/>
 </filter>
</FilterScript>

参考:

Meshlab:用PyMeshLab执行从MeshLab中导出的过滤器脚本(.mlx)功能
Open3D:Open3D Mesh 网格
blender:blender_process.py

二、ModelNet10数据集简化

生成简化版本的数据集

import glob, os, shutil, sys, json
from pathlib import Path
import pylab as plt
import trimesh
import open3d
import time
import numpy as np
from tqdm import tqdm


def load_off(file):
    file = open(file, 'r')
    if 'OFF' != file.readline().strip():
        raise ('Not a valid OFF header')

    n_verts, n_faces, n_dontknow = tuple([int(s) for s in file.readline().strip().split(' ')])
    verts = [[float(s) for s in file.readline().strip().split(' ')] for i_vert in range(n_verts)]
    faces = [[int(s) for s in file.readline().strip().split(' ')][1:] for i_face in range(n_faces)]
    file.close()
    return np.array(verts), np.array(faces, dtype=int)


MESH_EXTENSIONS = [
    '.off',
]
def is_mesh_file(filename):
    return any(filename.endswith(extension) for extension in MESH_EXTENSIONS)
def make_dataset_by_class(dir, class_to_idx, phase):
    meshes = [[] for _ in class_to_idx]
    dir = os.path.expanduser(dir)
    id = -1
    for target in sorted(os.listdir(dir)):
        d = os.path.join(dir, target)  # 获取每一类的 路径
        if not os.path.isdir(d):
            continue  # 不是文件夹就跳过了
        d = os.path.join(d, phase)
        id = id + 1
        #print(os.listdir(d))
        for root, _, fnames in sorted(os.walk(d)):
            for fname in sorted(fnames):
                if is_mesh_file(fname):  # 是mesh文件 并且 是相关类型 train or test
                    path = os.path.join(root, fname)
                    item = (path, class_to_idx[target],fname,root)  # 路径 , 类别
                    meshes[id].append(item)
    return meshes


if __name__=='__main__':
    # 0.简化网格面个数设定
    face_num = 500
    # 1.得到所有文件的路径
    dir_ = '../../../datasets/ModelNet10'
    classes = [d for d in os.listdir(dir_) if os.path.isdir(os.path.join(dir_, d))]  # 只返回文件夹
    class_to_idx = {classes[i]: i for i in range(len(classes))}                      # 类别
    train = make_dataset_by_class(os.path.join(dir_), class_to_idx,'train')
    test = make_dataset_by_class(os.path.join(dir_), class_to_idx,'test')

    # 2.遍历 test
    cnt = 0
    for test_ in test:  # 所有类
        start_time = time.time()
        cnt = cnt + 1
        print(cnt)
        for mesh in test_:  # 每一类的所有obj
            # 2.1 读取
            vs, faces = load_off(mesh[0])
            m = open3d.geometry.TriangleMesh()
            m.vertices = open3d.utility.Vector3dVector(vs)
            m.triangles = open3d.utility.Vector3iVector(faces)
            # 2.2 路径
            # save_dir = '../../../datasets/ModelNet10_' + str(face_num) + '/' + mesh[3]
            save_dir = mesh[3].replace('ModelNet10', 'ModelNet10_' + str(face_num))
            if not os.path.isdir(save_dir):
                os.makedirs(save_dir, exist_ok=True)
            # 2.3 简化并保存
            while len(m.triangles) < face_num:   # 面的个数
                m = m.subdivide_midpoint(number_of_iterations=1)
            m = m.simplify_quadric_decimation(face_num)
            file_name = os.path.splitext(mesh[2])
            open3d.io.write_triangle_mesh(save_dir + '/' + file_name[0] + '.obj', m)
        t = (time.time() - start_time)
        print('t:', t)

    # 2.遍历 train
    cnt = 0
    for train_ in train:   # 所有类
        start_time = time.time()
        cnt = cnt + 1
        print(cnt)
        for mesh in train_:   # 每一类的所有obj
            # 2.1 读取
            vs, faces = load_off(mesh[0])
            m = open3d.geometry.TriangleMesh()
            m.vertices = open3d.utility.Vector3dVector(vs)
            m.triangles = open3d.utility.Vector3iVector(faces)
            # 2.2 路径
            save_dir = mesh[3].replace('ModelNet10', 'ModelNet10_' + str(face_num))
            if not os.path.isdir(save_dir):
                os.makedirs(save_dir, exist_ok=True)
            # 2.3 简化并保存
            while len(m.triangles) < face_num:
                m = m.subdivide_midpoint(number_of_iterations=1)
            m = m.simplify_quadric_decimation(face_num)
            file_name = os.path.splitext(mesh[2])
            open3d.io.write_triangle_mesh(save_dir + '/' + file_name[0] + '.obj', m)
        t = (time.time() - start_time)
        print('t:', t)

三、展望

  1. 保护边缘的网格简化
  2. 先计算完网格元素特征,再进行降采样
  3. 先水密化后简化,最好加入平滑等预处理
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
### 回答1: 视觉几何三维重建是指利用摄像机拍摄的多个视角下的图像,通过计算机图形学算法获得三维模型的过程。其中,openmvs是一种基于MVS(多视角几何重建)实现的三维重建工具。 OpenMVS是一个开源的三维重建工具,基于MVS算法,可以实现从多个图像中生成高精度的三维模型。OpenMVS的几何重建算法主要是采用光束法,通过对图像进行矩阵重建来计算相机位置和三角形点云。OpenMVS的几何重建方法相对于其他算法具有较高的稳定性和精度。 在OpenMVS的源码分析中,主要包括三个部分:几何重建、点云和网格处理。几何重建是基于多视角几何的,通过将多个图像的视角转化到同一个坐标系中,可以计算出三角形的点云。点云处理主要包括点云优化和稠密重建。网格处理则是在点云的基础上生成三角形网格模型。 OpenMVS的优势在于能够充分利用多视图几何的信息,提高三维重建的精度和效率。而且该工具具有良好的可扩展性和适应性,可以在不同场景下应用。同时,OpenMVS的开源代码也为研究者提供了一个可靠的研究平台,进行更深入的算法研究和开发。 总之,视觉几何三维重建是一项非常复杂的任务,而OpenMVS作为一个优秀的三维重建工具,通过独特的几何重建算法和优秀的可扩展性,加速了三维重建的研究和应用。 ### 回答2: 首先,视觉几何三维重建是一项重要的计算机视觉技术,其主要目的是利用多视角图像或视频序列来恢复场景的三维结构。在该过程中,重建算法必须解决诸多技术难题,如图像匹配、相机姿态估计、点云配准、三维重建等。 而OpenMVS则是一款优秀的三维重建软件,其核心算法基于多视图几何,能够高效、精确地处理大规模三维数据。具体来说,OpenMVS采用稀疏点云表示法(Sparse Point Cloud)和密集点云表示法(Dense Point Cloud)来表示场景中的点云信息,其中稀疏点云用于初始匹配,密集点云用于表面重建。 在实现中,OpenMVS采用先进的图像流水线(Image Pipeline)来处理输入的图像序列,包括预处理(Pre-processing)、特征提取(Feature Extraction)、特征匹配(Feature Matching)等多个步骤。在此基础上,OpenMVS还提供了多种优化方法,如基于非线性优化的相机姿态估计、自适应曲率滤波等,以进一步提高重建效果。 值得指出的是,OpenMVS作为一款开源软件,其源代码也是完全开放的。此外,OpenMVS还具有友好的用户界面和丰富的文档,能够帮助用户快速上手并实现高质量的三维重建。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大米粥哥哥

感谢认可!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值