You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Python获取3D猫网格模型在3D bounding box各面的2D投影?

3D网格模型到Bounding Box各面的2D投影实现方案

问题需求

需要用Python实现3D猫网格模型向其3D bounding box的6个面分别做2D投影。目前仅能得到4个面的投影,还存在两个问题:

  1. 方位角90°/270°视角下,图像因bounding box过窄出现拉伸变形
  2. 不知道如何获取模型顶部和底部的投影面

参考图示:

  • 带青色3D bounding box的猫模型:带青色3D bounding box的猫模型
  • 目标投影效果示例:目标投影效果示例

现有尝试代码

import trimesh
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

# Load the 3D mesh file (e.g., STL, OBJ, PLY)
mesh = trimesh.load('/content/12221_Cat_v1_l3.obj')  # Change the path and file type as needed

# Define the angles for the four views
angles = [(azim, 0) for azim in [0, 90, 180 ,270]]  # Set elevation to 0 for a middle view


for i, (azim, elev) in enumerate(angles):
    # Create a new figure for each angle
    fig = plt.figure()

    ax = fig.add_subplot(111, projection='3d')
    ax.set_title(f'View {azim}')

    # Plot the mesh
    ax.plot_trisurf(mesh.vertices[:, 0], mesh.vertices[:, 1], mesh.vertices[:, 2],
                    triangles=mesh.faces, color='cyan', edgecolor='none', alpha=0.5)
    # Set the viewing angle
    ax.view_init(elev=elev, azim=azim)

    # Set limits based on mesh bounds
    max_range = mesh.bounds[1] - mesh.bounds[0]
    mid_x = (mesh.bounds[0][0] + mesh.bounds[1][0]) / 2
    mid_y = (mesh.bounds[0][1] + mesh.bounds[1][1]) / 2
    mid_z = (mesh.bounds[0][2] + mesh.bounds[1][2]) / 2

    ax.set_xlim(mid_x - max_range[0] / 2, mid_x + max_range[0] / 2)
    ax.set_ylim(mid_y - max_range[1] / 2, mid_y + max_range[1] / 2)
    ax.set_zlim(mid_z - max_range[2] / 2, mid_z + max_range[2] / 2)

    # Hide axes
    ax.axis('off')

    # Save the figure as an image without axes
    plt.savefig(f'mesh_view_{i + 1}.png', bbox_inches='tight', pad_inches=0, dpi=300)
    plt.close(fig)  # Close the figure

解决方案

1. 覆盖6个面的视角设置

要获取顶部和底部投影,需添加**仰角90°(顶部视图)和仰角-90°(底部视图)**的视角,完整视角参数需包含6个面的对应配置。

2. 解决拉伸变形问题

拉伸根源是matplotlib 3D绘图默认自动调整画布比例,导致投影面宽高比与bounding box实际面比例不符。解决方法是:

  • 根据当前视角对应的bounding box面宽高比,设置画布尺寸
  • 关闭3D轴自动缩放,严格匹配bounding box范围

修改后完整代码

import trimesh
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

# 加载3D网格模型
mesh = trimesh.load('/content/12221_Cat_v1_l3.obj')
bounds = mesh.bounds
# 计算bounding box各维度长度
dim_x = bounds[1][0] - bounds[0][0]
dim_y = bounds[1][1] - bounds[0][1]
dim_z = bounds[1][2] - bounds[0][2]
# 定义6个面的视角、宽高比及名称
view_params = [
    {"azim": 0, "elev": 0, "aspect": (dim_y, dim_z), "name": "front"},
    {"azim": 180, "elev": 0, "aspect": (dim_y, dim_z), "name": "back"},
    {"azim": 90, "elev": 0, "aspect": (dim_x, dim_z), "name": "right"},
    {"azim": 270, "elev": 0, "aspect": (dim_x, dim_z), "name": "left"},
    {"azim": 0, "elev": 90, "aspect": (dim_x, dim_y), "name": "top"},
    {"azim": 0, "elev": -90, "aspect": (dim_x, dim_y), "name": "bottom"}
]

for params in view_params:
    azim, elev = params["azim"], params["elev"]
    width, height = params["aspect"]
    # 根据bounding box面比例设置画布尺寸(单位:英寸)
    fig = plt.figure(figsize=(width/10, height/10), dpi=300)
    ax = fig.add_subplot(111, projection='3d')
    
    # 绘制网格模型
    ax.plot_trisurf(mesh.vertices[:,0], mesh.vertices[:,1], mesh.vertices[:,2],
                    triangles=mesh.faces, color='cyan', edgecolor='none', alpha=0.5)
    
    # 设置视角
    ax.view_init(elev=elev, azim=azim)
    
    # 固定坐标轴范围,匹配bounding box
    ax.set_xlim(bounds[0][0], bounds[1][0])
    ax.set_ylim(bounds[0][1], bounds[1][1])
    ax.set_zlim(bounds[0][2], bounds[1][2])
    
    # 关闭坐标轴显示
    ax.axis('off')
    # 关闭自动缩放,避免比例失衡
    ax.autoscale(False)
    
    # 保存图像,确保无多余边距
    plt.savefig(f'mesh_projection_{params["name"]}.png', 
                bbox_inches='tight', pad_inches=0, dpi=300)
    plt.close(fig)

关键改进点说明

  • 为每个视图明确指定画布宽高比,完全匹配对应bounding box面的实际比例,彻底解决拉伸问题
  • 添加顶部和底部视角,覆盖6个面的投影需求
  • 关闭ax.autoscale(False),强制坐标轴范围与bounding box一致,保证投影准确性

内容的提问来源于stack exchange,提问作者Laika

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 19:04:54