如何用Python获取3D猫网格模型在3D bounding box各面的2D投影?
3D网格模型到Bounding Box各面的2D投影实现方案
问题需求
需要用Python实现3D猫网格模型向其3D bounding box的6个面分别做2D投影。目前仅能得到4个面的投影,还存在两个问题:
- 方位角90°/270°视角下,图像因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
相关产品推荐
相关产品推荐

