Blenderをちょっとした座標系可視化ツールとして使う
小ネタです。
今回、ちょっと複雑なプロジェクタ-カメラ系のキャリブレーションをすることになり、「さすがにキャリブレーション結果を可視化して確認したいな」と思ってスクリプトを書いてみました。
今回書いたのは、世界座標系に対する回転行列と並進ベクトルが与えられたとき、ローカル座標系をBlender上で可視化するPythonスクリプトです。
スポンサーリンク
もくじ
0. 実行環境
macOS Mojave 10.14.6 Blender 2.82 Blender Python 3.7.4
1. なぜBlender?
matplotlib, Plotly, WebGLなどの可視化ツールではなくBlenderを使ったのは、以下の理由からです。
- 無料
- インストールが楽
- デフォルトでグリッドが描画されていて、マウスでぐるぐる回す操作も直感的
特に最後の理由が大きいですね。 3Dを描画できるツールはたくさんあるのですが、視点移動やズームのUI設計が雑なものが意外と多いです。 その点、Blenderのマウス操作はかなり直感的で優秀だと思います。
2. 書いたスクリプト
Blenderは、すべてのオペレーションに対してPython APIを用意し、それをbpy
モジュールとして提供してくれています(そもそもBlender UIがPythonを通して実装されているらしい?)。
冒頭に書いたように、今回は世界座標系からローカル座標系までの回転行列と並進ベクトルが与えられたとき、ローカル座標系のXYZ軸を3本の矢印で描画する関数draw_coord_system
を書きました。
import bpy import numpy as np from scipy.spatial.transform import Rotation def draw_coord_system(name, rot, tvec): # coneとcylinderで矢印を描画する bpy.ops.mesh.primitive_cone_add( radius1=0.2, depth=0.4, location=(0, 0, 2.2)) bpy.ops.mesh.primitive_cylinder_add( radius=0.1, depth=2, location=(0, 0, 1)) cone = bpy.data.objects['Cone'] cylinder = bpy.data.objects['Cylinder'] # 2つをjoinして1つのオブジェクトにする ctx = bpy.context.copy() ctx['active_object'] = cone ctx['selected_objects'] = cylinder ctx['selected_editable_objects'] = [cone, cylinder] bpy.ops.object.join(ctx) arrow = cone arrow.name = 'axis_arrow' # オブジェクトの原点を矢印の根本に設定する arrow.select_set(True) bpy.context.scene.cursor.location = (0, 0, 0) bpy.ops.object.origin_set(type='ORIGIN_CURSOR') # 赤青緑の単色マテリアルを定義する x_mat = bpy.data.materials.new("x_mat") x_mat.diffuse_color = (1, 0, 0, 1) y_mat = bpy.data.materials.new("y_mat") y_mat.diffuse_color = (0, 1, 0, 1) z_mat = bpy.data.materials.new("z_mat") z_mat.diffuse_color = (0, 0, 1, 1) # 最初に作った矢印をZ軸とする me = arrow.data z_axis = arrow z_axis.name = 'z_axis' # Z軸をコピーし、オブジェクト原点の周りで回転させてX軸・Y軸を作る y_axis = bpy.data.objects.new('y_axis', me.copy()) bpy.context.scene.collection.children[0].objects.link(y_axis) y_axis.rotation_euler[0] = -np.pi/2 x_axis = bpy.data.objects.new('x_axis', me.copy()) bpy.context.scene.collection.children[0].objects.link(x_axis) x_axis.rotation_euler[1] = np.pi/2 # それぞれにマテリアルを適用する z_axis.data.materials.append(z_mat) y_axis.data.materials.append(y_mat) x_axis.data.materials.append(x_mat) # 3軸を1つのオブジェクトにまとめる ctx = bpy.context.copy() ctx['active_object'] = z_axis ctx['selected_objects'] = [x_axis, y_axis, z_axis] ctx['selected_editable_objects'] = [x_axis, y_axis, z_axis] bpy.ops.object.join(ctx) axis = z_axis axis.name = name # 指定されたローカル座標系の場所に移動させる axis.rotation_mode = 'XYZ' axis.rotation_euler = rot.as_euler('xyz') axis.select_set(True) axis.location = tvec
細かいことはBlenderのドキュメントやStack Overflowを参照していただきたいのですが、構造としてはmesh
, material
, object
のテーブルにレコードを挿入し、できたobject
レコードをscene.collection
に挿入するとUIに描画されるようになっています。
今回、複数の座標系を描画する際に同じmeshデータやmaterialデータを使い回すように実装しても良かったのですが、後から個別に形状や色を変えるかもしれないので結局は毎回新しいものを作成するようにしました。
また、オブジェクトを回転させる処理にはクォータニオンを使おうかとも思ったのですが、そのためにはaxis.rotation_mode = 'quaternion'
のように設定を変更しなければなりません。
BlenderはデフォルトではXYZのintrinsicオイラー角を使っているようなので、今回はその流儀に従ってオイラー角を使う実装にしました。
この関数を以下のように呼び出せば、ローカル座標系がBlenderのシーンの中に描画されます。
rot = Rotation.from_euler('XYX', [90, 90, 10], degrees=True) tvec = np.array([1, 1, 1]) draw_coord_system('camera_system', rot, tvec)
3. scipyのインストール
今回のスクリプトでは、三次元回転を扱うためにscipyのRotationモジュールを利用しています。
※ このモジュールの詳細は以前の記事で紹介しています。
ということで、実行前にscipyをインストールしておきます。
Blenderは我々が手動でインストールしたPythonではなく、Blenderインストール時に内包されているPythonを使います。 scipyをインストールするときは、そのPythonバイナリを通してpipを呼び出すようにしましょう。
例えば、私のMac環境では、以下のコマンドでscipyをインストールできました。
/Applications/Blender.app/Contents/Resources/2.82/python/bin/python3.7m -m pip install scipy
4. スクリプトの実行
一連の処理を書いたPythonファイルを適当な場所に保存したら、Blenderを開きます。 デフォルトのcubeは邪魔なので消しておきましょう。
Blenderが起動したら、レイアウトをScriptingに変更します。 すると画面右にスクリプトエディタが開くので、上部のボタンを押して保存しておいたPythonファイルを開きましょう。
ファイルを開いた後、Run Script
のボタンを押せばスクリプトが実行され、左上の画面に作成したオブジェクトが反映されます。
以上、Blenderで座標系を可視化するという小ネタでした。
ちなみに、このスクリプトを書くきっかけになったプロジェクトですが、オブジェクトトラッキングの方法を変えることになったので今回書いたスクリプトが活躍することはありませんでした…。
まあ、コードを取っておけば、また使う機会も来るでしょう。