本記事には広告が含まれています。
This article contains advertisements.

Python

3Dオブジェクトの頂点情報を2D stack 画像へ変換

Skull-san

今回は3Dモデリングソフトで作成したオブジェクトの頂点情報を2D画像に変換する方法を説明します。この画像はシミュレーションのファントムや線源の配置などに利用できます。

STL→2D stack TIFF画像です。
※2D stack TIFF画像→STLの話は含まれません。

3Dオブジェクトの作成

適当に3Dオブジェクトを作成します。

今回は無料で3Dモデリングができるblender ver.3.2.1を使用します。

※2024/9/11の時点で4.2.1がリリースされていますので、やり方が変化している可能性もあります。

また、日本語表示にしています。日本語表示に切り替える方法はこちらを参考にしてください。

まず、blenderを起動したら、初めから用意されているカメラ、立方体、ライトを選択してdeleteします。

次に物体を作成します。今回は球の中に小さな球が2つ入っているようなオブジェクトを作ります。適宜、作りたいものに変更してください。
Shiftを押しながらAを押すと、メニューが開かれますので、メッシュの中のUV球を選択します。
球が表示されたら、そのオブジェクトの情報を表示するため右上の「<」をクリックしてウィンドウを表示させます。

次に球の寸法を変えます。
今回はx, y, z座標ともに30cmの球とします。
サイズを変更すると表示が小さくなったり、大きくなったりするのでマウスホイールを回転させて表示を調整してください。

手のマークを左クリックしながらマウスを動かすと平行移動ができるので、都度、調整します。
右上のアイコンをクリックするとワイヤーフレーム表示にすることができます。

今回は球を3つ作りたいので、コピー&ペーストします。
右上の球を選択してCntl+Cでコピー、Cntl+Vでペーストします。
全く同じ球が作成され、完全に重なっている状態なので、コピーできていないように感じるかもしれませんが、右上に球001と新しいオブジェクトができていることが確認できます。

球001のサイズをx, y, z = 5cm, 5cm, 5cmとします。
位置をy方向に10cm移動させます。

最後の球を作成します。
球001をコピー&ペーストして、位置をy=10cmからy=-10cmに変更します。

今度はメッシュの細かさを変更します。
今回保存するのは、頂点の情報なので、頂点が繋がって線ができるくらい細かくします。
一番大きな球を選択した後、右下の工具マーク(モディファイア―プロパティ)をクリックします。
モディファイア―を追加をクリックします。

リメッシュを選択し、ボクセルを選択した状態で、ボクセルサイズを小さくしてください。
下の画像では0.01mとしていますが、後々、微調整したところ0.007mが最適でした。
最後に③の適用をしてください。適用をしないと、変更が反映されません。
これによって結構なメモリを使いますので、古いPCを使っている方は細かくし過ぎないようにしてください。

同様に、残りの2つの球もリメッシュします。
適用を忘れないでください。

できた3Dモデルを出力します。
ファイル→エクスポート→STLファイルを選択します。

STL頂点情報を2D stack画像へ変換

3Dモデルから2次元stack画像への変換はpythonプログラムで行います。
以下のプログラムをSTLファイルがある場所で実行してください。
モジュールが無いなど、エラーが出たら適宜インストールしてください。

実行して、
Please enter file BASE name:
と聞かれたらファイル名を入力して、Enterを押してください。
(sphere.stlならsphereを入力)

import os
import imageio
import numpy as np
from stl import mesh
from scipy.ndimage import binary_fill_holes

filename = input('Please enter file BASE name: ');

# STLファイルを読み込む
print("STLファイルを読み込んでいます...")
your_mesh = mesh.Mesh.from_file(f'{filename}.stl')

# 空の3次元配列を作成 (サイズは適宜調整)
grid_size = 256  # グリッドサイズを設定
margin = 10  # 周囲に持たせたい余裕(ピクセル単位)
padded_grid_size = grid_size + 2 * margin  # 周囲に余裕を追加したグリッドサイズ

voxels = np.zeros((padded_grid_size, padded_grid_size, padded_grid_size))

# STLメッシュの最小/最大座標を取得し、正規化
min_bounds = your_mesh.min_
max_bounds = your_mesh.max_
scale = grid_size / (max_bounds - min_bounds).max()  # オブジェクトのサイズをグリッドに合わせてスケール

# メッシュのボクセル化
print("メッシュをボクセル化しています...")
total_triangles = len(your_mesh.vectors)
for idx, triangle in enumerate(your_mesh.vectors):
    scaled_triangle = (triangle - min_bounds) * scale + margin  # スケールした後に余裕分のオフセットを追加
    for vert in scaled_triangle:
        x, y, z = np.clip(np.round(vert).astype(int), 0, padded_grid_size - 1)  # クリッピングと四捨五入を追加
        voxels[x, y, z] = 1  # 頂点をボクセルに変換
    
    # ボクセル化の進捗を表示
    progress = (idx + 1) / total_triangles * 100
    print(f"ボクセル化進行中: {progress:.2f}% 完了 ({idx + 1}/{total_triangles})", end='\r')

print("\nボクセル化が完了しました。")

# 3次元ボクセルを埋める(3次元的に閉じていれば内側が塗りつぶされる)------------
#print("ボクセルの穴を埋めています...")
#voxels = binary_fill_holes(voxels)
#print("ボクセルの穴埋めが完了しました。")
# --------------------------------------------------------------------------------

# 出力フォルダを作成
output_folder = filename
os.makedirs(output_folder, exist_ok=True)

# TIFF形式でスライスごとに保存
total_slices = voxels.shape  # z軸方向のスライス数
for i in range(total_slices):  # z軸方向のスライス
    # スライスを保存
    imageio.imwrite(os.path.join(output_folder, f"output_voxels_slice_{i}.tiff"), filled_voxels[:, :, i].astype(np.uint8))
    
    # スライス保存の進捗を表示
    progress = (i + 1) / total_slices * 100
    print(f"TIFF保存進行中: {progress:.2f}% 完了 ({i + 1}/{total_slices})", end='\r')

print(f"\nTIFFファイルが {output_folder} フォルダに保存されました。")

このコードの中間でコメントアウトされている

# 3次元ボクセルを埋める(3次元的に閉じていれば内側が塗りつぶされる)------------
#print("ボクセルの穴を埋めています...")
#voxels = binary_fill_holes(voxels)
#print("ボクセルの穴埋めが完了しました。")
# --------------------------------------------------------------------------------

を有効にすると、3次元的に閉じた物体であれば中を埋めて出力します。
細かすぎる2次元画像だと、一見繋がっているようで繋がっていませんので解像度を下げるか、リメッシュを細かくするようにしてください。

中を埋めないで出力した画像です。画素値はかなり小さいので、
Procee→Math→Multiplyで100倍にしてください。

中を埋める処理をした画像です。
大きな球は繋がっていないので、埋められていません。

これにより、3Dモデルを2次元stack画像にすることができました。

Skull-san

この画像を使って、次回はシミュレーションしてみます

COMMENT

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA