如何将 .OBJ 3D 模型自动裁剪为边界框?

Posted

技术标签:

【中文标题】如何将 .OBJ 3D 模型自动裁剪为边界框?【英文标题】:How to automatically crop an .OBJ 3D model to a bounding box? 【发布时间】:2020-03-20 07:09:55 【问题描述】:

在现已过时的 Autodesk ReCap API 中,可以在要从图像生成的场景周围指定一个“边界框”

在生成的模型中,边界框之外的任何顶点都被丢弃,超出边界框的任何体积都被截断,以在框边界处具有面。

我现在使用 Autodesk 的 Forge Reality Capture API,它取代了 ReCap。显然,这个新 API 不允许用户指定边界框。

所以我现在正在寻找一个程序,它将一个 .OBJ 文件和一个指定的边界框作为输入,并输出一个仅包含该边界框内的顶点和面的文件。

【问题讨论】:

【参考方案1】:

鉴于无法在 Reality Capture API 中指定边界框,我创建了这个 python 程序。它很粗糙,因为它只丢弃顶点在边界框之外的面。它实际上确实会无损地丢弃,只是在输出 OBJ 文件中将它们注释掉。这允许您取消注释它们,然后使用不同的边界框。

如果您确实想要删除边界框外的所有相关 v、vn、vt、vp 和 f 行,这可能不是您需要的,因为 OBJ 文件大小几乎保持不变。但是对于我的特殊需要,最好保留所有记录并仅使用 cmets。

# obj3Dcrop.py
# (c) Scott L. McGregor, Dec 2019
#  License: free for all non commercial uses.  Contact author for any other uses.
#  Changes and Enhancements must be shared with author, and be subject to same use terms

# TL;DR:  This program uses a bounding box, and "crops" faces and vertices from a
# Wavefront .OBJ format file, created by Autodesk Forge Reality Capture API
# if one of the vertices in a face is not within the bounds of the box.
#
# METHOD
# 1) All lines other than "v" vertex definitions and "f" faces definitions
#    are copied UNCHANGED from the input .OBJ file to an output .OBJ file.
# 2) All "v" vertex definition lines have their (x, y, z) positions tested to see if:
#    minX < x < maxX and minY < y < maxY and minZ < z < maxZ ?
#    If TRUE, we want to keep this vertex in the new OBJ, so we
#    store its IMPLICIT ORDINAL position in the file in a dictionary called v_keepers.
#    If FALSE, we will use its absence from the v_keepers file as a way to identify
#    faces that contain it and drop them.   All "v" lines are also copied unchanged to the
#    output file.
# 3) All "f" lines (face definitions) are inspected to verify that all 3 vertices in the face
#    are in the v_keepers list.   If they are, the f line is  output unchanged.
# 4) Any "f" line that refers to a vertex that was cropped, is prefixed by "# CROPPED: "
#    in the output file.  Lines beginning # are treated as comments, and ignored in future
#    processing.

# KNOWN LIMITATIONS: This program generates models in which the outside of bound faces
# have been removed. The vertices that were found outside the bounding box, are still in the
# OBJ file, but they are now disconnected and therefore ignored in later processing.
# The "f" lines for faces with vertices outside the bounding box are also still in the
# output file, but now commented out, so they don't process. Because this is non-destructive.
# we can easily change our bounding box later, uncomment cropped lines and reprocess.
#
# This might be an incomplete solution for some potential users.  For such users
# a more complete program would delete unneeded v, vn, vt and vp lines when the v vertex
# that they refer to is dropped. But note that this requires renumbering all references to these
# vertice definitions in the "f" face definition lines.  Such a more complete solution would also
# DISCARD all 'f' lines with any vertices that are out of bounds, instead of making them copies.
# Such a rewritten .OBJ file would be var more compact, but changing the bounding box would require
# saving the pre-cropped original.

# QUIRK: The OBJ file format defines v, vn, vt, vp and f elements by their
# IMPLICIT ordinal occurrence in the file,  with each element type maintaining
# its OWN separate sequence.  It then references those definitions EXPLICITLY in
# f face definitions. So deleting (or commenting out) element references requires
# appropriate rewriting of all the"f"" lines tracking all the new implicit positions.

# Such rewriting is not particularly hard to do, but it is one more place to make
# a mistake, and could  make the algorithm more complicated to understand.
# This program doesn't bother, because all further processing of the output
# OBJ file ignores unreferenced v, vn, vt and vp elements.
#
# Saving all lines rather than deleting them to save space is a tradeoff involving considerations of
# Undo capability, compute cycles, compute space (unreferenced lines) and maintenance complexity choice.
# It is left to the motivated programmer to add this complexity if needed.

import sys

#bounding_box = sys.argv[1] # should be in the only string passsed  (maxX, maxY, maxZ, minX, minY, minZ)
bounding_box = [10, 10, 10, -10, -10, 1]
maxX = bounding_box[0]
maxY = bounding_box[1]
maxZ = bounding_box[2]
minX = bounding_box[3]
minY = bounding_box[4]
minZ = bounding_box[5]

v_keepers = dict() # keeps track of which vertices are within the bounding box

kept_vertices = 0
discarded_vertices = 0

kept_faces = 0
discarded_faces = 0

discarded_lines = 0
kept_lines = 0

obj_file = open('sample.obj','r')
new_obj_file = open('cropped.obj','w')

# the number of the next "v" vertex lines to process.
original_v_number = 1 # the number of the next "v" vertex lines to process.
new_v_number = 1 # the new ordinal position of this vertex if out of bounds vertices were discarded.

for line in obj_file:
    line_elements = line.split()

    # Python doesn't have a SWITCH statement, but we only have three cases, so we'll just use cascading if stmts
    if line_elements[0] != "f":  # if it isn't an "f" type line (face definition)

        if line_elements[0] != "v": # and it isn't an "v" type line either (vertex definition)
            # ************************ PROCESS ALL NON V AND NON F LINE TYPES ******************
            # then we just copy it unchanged from the input OBJ to the output OBJ
            new_obj_file.write(line)
            kept_lines = kept_lines + 1

        else: # then line_elements[0] == "v":
            # ************************ PROCESS VERTICES ****************************************
            #  a "v" line looks like this:
            #  f x y z ...
            x = float(line_elements[1])
            y = float(line_elements[2])
            z = float(line_elements[3])

            if minX < x < maxX and minY < y < maxY and minZ < z < maxZ:
                # if vertex is within  the bounding box, we include it in the new OBJ file
                new_obj_file.write(line)
                v_keepers[str(original_v_number)] = str(new_v_number)
                new_v_number = new_v_number + 1
                kept_vertices =  kept_vertices +1
                kept_lines = kept_lines + 1
            else:     # if vertex is NOT in the bounding box
                new_obj_file.write(line)
                discarded_vertices = discarded_vertices +1
                discarded_lines = discarded_lines + 1
            original_v_number = original_v_number + 1

    else: # line_elements[0] == "f":
        # ************************ PROCESS FACES ****************************************
        #  a "f" line looks like this:
        #  f v1/vt1/vn1 v2/vt2/vn2 v3/vt3/vn3 ...

        #  We need to delete any face lines where ANY of the 3 vertices v1, v2 or v3 are NOT in v_keepers.


        v = ["", "", ""]
        # Note that v1, v2 and v3 are the first "/" separated elements within each line element.
        for i in range(0,3):
            v[i] = line_elements[i+1].split('/')[0]

        # now we can check if EACH of these 3 vertices are  in v_keepers.
        # for each f line, we need to determine if all 3 vertices are in the v_keepers list
        if v[0] in v_keepers and v[1] in v_keepers and v[2] in v_keepers:
            new_obj_file.write(line)
            kept_lines = kept_lines + 1
            kept_faces = kept_faces +1
        else: # at least one of the vertices in this face has been deleted, so we need to delete the face too.
            discarded_lines = discarded_lines + 1
            discarded_faces = discarded_faces +1
            new_obj_file.write("# CROPPED "+line)

# end of line processing loop
obj_file.close()
new_obj_file.close()

print ("kept vertices: ",  kept_vertices ,"discarded vertices: ", discarded_vertices)
print ("kept faces:     ", kept_faces,     "discarded faces:   ", discarded_faces)
print ("kept lines:     ", kept_lines,     "discarded lines:   ", discarded_lines)

【讨论】:

【参考方案2】:

很遗憾,(至少现在)没有办法在 Reality Capture API 中指定边界框。

【讨论】:

以上是关于如何将 .OBJ 3D 模型自动裁剪为边界框?的主要内容,如果未能解决你的问题,请参考以下文章

模型训练 - 对象的裁剪图像 VS 带有边界框的更大图像

如何将 .obj 转换为 .json 格式的 3D 模型?

有啥方法可以从 three.js Object3D 中获取边界框?

在 OpenGL 中的 3D 模型上创建 AABB

将 vec3 乘以模型矩阵的问题(缩放问题)

3D 图形:如何考虑模型的位置