見出し画像

Blenderで3Dモデルを読みこみ一括でDepthを撮影する

自分用メモです。

ランダムに色んな角度から連番で保存

import bpy
from mathutils import Vector
import glob
import os
import random
import math
import shutil

# Set the desired number of images to generate
num_images = 10  # For example, generate 10 images

# Path settings
modelpath = 'E:/desktop/obj/'
base_path = 'E:/desktop/base/'
depth_path = 'E:/desktop/depth/'

# Ensure the output folders exist
if not os.path.exists(base_path):
    os.makedirs(base_path)
if not os.path.exists(depth_path):
    os.makedirs(depth_path)

# Blender settings
bpy.context.scene.use_audio = False
bpy.context.scene.render.use_lock_interface = False
bpy.context.scene.render.use_simplify = True
bpy.context.scene.render.resolution_x = 1024
bpy.context.scene.render.resolution_y = 1024
bpy.context.scene.render.resolution_percentage = 100


# Setup camera function
def setup_camera(scene, target_location, distance=5):
    # Find existing cameras or create a new one
    cameras = [obj for obj in scene.objects if obj.type == 'CAMERA']
    if cameras:
        camera = cameras[0]
    else:
        bpy.ops.object.camera_add(location=(0, -distance, distance/3))
        camera = bpy.context.object
        camera.name = 'SceneCamera'
        camera.data.name = 'SceneCameraData'
    
    # Set camera location and rotation
    camera.location = target_location + (camera.location - target_location).normalized() * distance
    camera.rotation_mode = 'XYZ'
    camera.rotation_euler[0] = math.radians(90)  # Point camera downwards
    camera.rotation_euler[1] = 0
    camera.rotation_euler[2] = 0
    
    scene.camera = camera

# Setup light function
def setup_light(scene, location=(0, -4, 1.5)):
    # Find existing lights or create a new one
    lights = [obj for obj in scene.objects if obj.type == 'LIGHT']
    if not lights:
        bpy.ops.object.light_add(type='AREA', location=location)
        light = bpy.context.object
        light.data.energy = 100
        light.data.specular_factor = 0.5
        light.data.color = (1.0, 1.0, 1.0)
        light.data.size = 1
        light.rotation_euler[0] = math.radians(90)  # Point light downwards

# Setup depth rendering
def setup_depth_rendering(depth_file_out, name):
    scene = bpy.context.scene
    scene.use_nodes = True
    tree = scene.node_tree
    for node in tree.nodes:
        tree.nodes.remove(node)

    # Setup node graph for depth rendering
    render_layers_node = tree.nodes.new('CompositorNodeRLayers')
    bpy.context.scene.view_layers["View Layer"].use_pass_z = True

    file_output_node = tree.nodes.new('CompositorNodeOutputFile')
    file_output_node.base_path = ""
    file_output_node.file_slots[0].path = depth_file_out  # Use full path directly

    normalize_node = tree.nodes.new('CompositorNodeNormalize')
    
    # Add invert color node
    invert_node = tree.nodes.new('CompositorNodeInvert')

    # Connect nodes
    tree.links.new(render_layers_node.outputs['Depth'], normalize_node.inputs[0])
    tree.links.new(normalize_node.outputs[0], invert_node.inputs[1])  # Connect to invert node
    tree.links.new(invert_node.outputs[0], file_output_node.inputs[0])  # Connect inverted output to file output node
    
def post_process_images(directory, pattern="*png0001.png"):
    # Match all files in the directory with the given pattern
    files = glob.glob(os.path.join(directory, pattern))
    
    for file_path in files:
        new_file_path = os.path.join(depth_path, os.path.basename(file_path)[:-8])
        
        if os.path.exists(new_file_path):
            os.remove(new_file_path)
        
        # Move the file to the depth directory instead of renaming
        shutil.move(file_path, new_file_path)

# Main script
scene = bpy.context.scene
target_location = Vector((0, 0, 0))
setup_camera(scene, target_location)
setup_light(scene)

# Gather model files and limit to the desired number of images
files = glob.glob(modelpath + '*.stl') + glob.glob(modelpath + '*.fbx') + glob.glob(modelpath + '*.obj')
random.shuffle(files)
files = files[:num_images]

for i, file in enumerate(files):
    try:
        print(f'Processing file {i+1}/{num_images}: {file}')
        name = f"{i+1:05d}"

        base_file_out = os.path.join(depth_path, name + '.png')
        depth_file_out = os.path.join(base_path, name + '.png')  # Corrected file naming convention

        # Clean the scene before importing new model
        bpy.ops.object.select_all(action='DESELECT')
        bpy.ops.object.delete()

        # Import the model based on its file type
        if file.endswith('.fbx'):
            bpy.ops.import_scene.fbx(filepath=file)
        elif file.endswith('.stl'):
            bpy.ops.import_mesh.stl(filepath=file)
        elif file.endswith('.obj'):
            bpy.ops.import_scene.obj(filepath=file)

        # Adjust the model's scale and orientation
        obj = bpy.context.selected_objects[0]
        dimensions = obj.dimensions
        scale_factor = 3.0 / max(dimensions)
        obj.scale = (scale_factor, scale_factor, scale_factor)
        obj.rotation_mode = 'XYZ'
        obj.rotation_euler[2] = random.uniform(0, 2 * math.pi)
        bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)

        # Render depth image and process images
        setup_depth_rendering(base_file_out, name)
        bpy.context.scene.render.filepath = depth_file_out
        bpy.ops.render.render(write_still=True)
        post_process_images(depth_path, pattern="*png0001.png")
        
        # Delete the object to clean up for the next iteration
        bpy.ops.object.select_all(action='DESELECT')
        bpy.ops.object.select_by_type(type='MESH')
        bpy.ops.object.delete()

    except Exception as e:
        print(f'Error processing file {i+1}/{num_images}: {file} - {e}')
        continue


Blender 2.93.18だと

tree.links.new(render_layers_node.outputs['Depth'], normalize_node.inputs[0])

じゃないと動かなかった。
参照:https://github.com/panmari/stanford-shapenet-renderer/issues/8

①Blender起動
デフォルトオブジェクトを手動で消す

②コンソール表示

③scripttingタブを開き、+Newをクリック
上記のコードをコピペした後、実行すると動きました。

この記事が気に入ったらサポートをしてみませんか?