ぱたへね

はてなダイアリーはrustの色分けができないのでこっちに来た

Genesisで色を付けてMeshのSimulationをする。

やったこと

GenesisでMeshのSimulationをするとき、入力データが各点に色を持っているときSimulationで色がつかず単色になってしまいます。生成AIで3Dモデルを作ったときや、元々点群のデータにありがちです。そういったデータを変換してカラーで表示できるようにしました。

いろいろやったのですが、結局はmtl情報を持つobj形式に変換するのが素直で良いです。

前回やったように、Simulation結果に後から色を付ける試みは上手く行きませんでいた。

natsutan.hatenablog.com

Genesisでobj形式のMeshを読み込んだときに頂点の数が減っていて、Simulation前後で頂点の一対一対応が取れなかったからです。

上手くいった結果です。

やり方

ChatGPTとやりとりしてこうなりました。ここまで行くのに数時間かかって結構大変でした。 MeshLabを使って頂点に色がついたobjを変換しています。

1. 元の点群OBJ(v x y z r g b形式)をMeshLabで開く

  • File > Open で読み込み

  • レイヤーに点群が表示される


2. 法線を付与する(必須)

  • Filters > Normals, Curvatures and Orientation > Compute normals for point sets

  • そのままApply


3. メッシュを作成する(Ball Pivoting法)

  • Filters > Remeshing, Simplification and Reconstruction > Surface Reconstruction: Ball Pivoting

  • Ball Radiusを設定(適切なサイズに)

  • Apply

  • (注意:ここで新しいレイヤーは作られないので、点群データを一度別に読み込んでおくと楽)


4. 元の点群をもう一度読み込む(Import Mesh)

  • File > Import Mesh

  • 同じOBJファイルを再読み込みして、レイヤーに「点群」と「メッシュ」が2つ並ぶようにする


5. 頂点カラーを転送する(Vertex Attribute Transfer)

  • Filters > Sampling > Vertex Attribute Transfer

  • Source Mesh → 元の点群レイヤー

  • Target Mesh → 新しいメッシュレイヤー

  • Transfer Colorにチェック

  • Apply


6. UV展開する(Flat Plane推奨)

  • Filters > Texture > Parameterization: Flat Plane

  • Apply

(Harmonicが使えればより自然だが、失敗する場合はFlat Planeが安全)


7. 頂点カラーをテクスチャに変換する

  • Filters > Texture > Transfer: Vertex Color to Texture

  • Texture Nameを設定(例:"texture")

  • Texture Width/Heightを設定(例:2048×2048)

  • Fill Textureにチェック

  • Apply


8. メッシュをExportする(OBJ+MTL+PNG)

  • hamburgerレイヤー(メッシュ側)を選択

  • File > Export Mesh As...

  • 形式は .obj

  • Save Optionsで

    • ✅ Normal

    • ✅ TexCoord

    • ✅ Save Texture Files

  • OKを押して保存!


9. 出力されるファイル

  • hamburger.obj(メッシュ)

  • hamburger.mtl(マテリアルファイル)

  • texture.png(テクスチャ画像)


✅ これで完成です!!

このOBJ/MTL/PNGセットを使えば
Genesisでも、Blenderでも、Unityでも、完璧に色付きメッシュが使えるようになります!!


🎯 超シンプル版まとめ(短縮版)

  1. 点群OBJをOpen

  2. 法線付ける

  3. Ball Pivotingでメッシュ化

  4. 点群を再Import

  5. Vertex Attribute Transferで色を転送

  6. Flat PlaneでUV展開

  7. Vertex Color to Textureでテクスチャ生成

  8. OBJ/MTL/PNGでExport!

Genesisのsimulation結果をOpen3Dで見る

やりたいこと

obj形式の3DモデルをGenesisで読み込んだときに、色情報が無くなって単色になってしまうときがあります。原因は同じobj拡張子でもmtlで色情報を持っているケースと、obj形式単体で色情報を持っているケースがあるからです。前者の場合は、Genesisでも色つきで描画されます。obj単体で色情報を持っているときは、頂点のデータxyzに続いて色情報が記述されているパターンです。

Blender等でExportしたときのobjフォーマットの例。v の行に三つしか値がありません。色の情報は、mtlファイルやそこから読まれるjpgが持っています。

# Blender 4.0.0
# www.blender.org
mtllib tripo_convert_a16ad3f8-f069-47d9-aa04-a853c62a7acd.mtl
o tripo_node_a16ad3f8-f069-47d9-aa04-a853c62a7acd
v -0.403771 -0.423104 -0.470257
v -0.403649 -0.422189 0.471268
v -0.403649 -0.420602 -0.472393

obj単体で色情報をもっているのは中身がこういうケース。vの所にxyzと恐らくrgbが入っています。このフォーマットの時、Genesisは色つきで表示できません。

# https://github.com/mikedh/trimesh
v -0.24586487 0.41282356 0.24905878 0.30196078 0.29019608 0.28627451
v -0.24905884 0.41282356 0.25203604 0.30588235 0.30980392 0.30588235
v -0.24905884 0.40883124 0.24905878 0.30196078 0.29019608 0.28235294

Simlutionの結果だけを表示するなら、それぞれのオブジェクトの位置を取得して別のレンダラーで表示すれば良いんじゃと思ってやってみました。

Genesisの実行

Genesisのソースコード

import os  
#os.environ['PYOPENGL_PLATFORM'] = 'glx'  
  
import genesis as gs  
import json  
  
  
########################## init ##########################  
gs.init(backend=gs.gpu)  
  
########################## create a scene ##########################  
scene = gs.Scene(  
    viewer_options=gs.options.ViewerOptions(  
        camera_pos=(3, -1, 2.0),  
        camera_lookat=(0.0, 0.0, 0.5),  
        camera_fov=30,  
        max_FPS=60,  
    ),  
    sim_options=gs.options.SimOptions(  
        dt=0.001,  
    ),  
  
    show_viewer=True,  
    rigid_options=gs.options.RigidOptions(  
        box_box_detection=True,  
    ),  
)  
  
########################## entities ##########################  
plane = scene.add_entity(  
    gs.morphs.Plane(),  
)  
  
# obj fileへのパス  
tresure_box_obj_path = 'D:/home/myproj/genesis/UR5/asset/work/tresure_box.obj'  
pot_obj_path = 'D:/home/myproj/genesis/UR5/asset/work/pot.obj'  
hamburger_obj_path = 'D:/home/myproj/genesis/UR5/asset/work/hamburger.obj'  
  
tresure_scale = 0.5  
pot_scale = 0.5  
hamburger_scale = 0.5  
  
tresure_box = scene.add_entity(  
    morph=gs.morphs.Mesh(  
        file=tresure_box_obj_path,  
        scale=tresure_scale,  
        pos=(0.55, -0.01, 0.18),  
        euler=(1.57, 0.0, 0.0),  
        convexify = True,  
    ),  
)  
pot = scene.add_entity(  
    morph=gs.morphs.Mesh(  
        file=pot_obj_path,  
        scale=pot_scale,  
        pos=(0.55, -0.40, 0.2),  
        euler=(0, 1.57, 0.0),  
        convexify = True,  
    ),  
)  
  
hamburger = scene.add_entity(  
    morph=gs.morphs.Mesh(  
        file=hamburger_obj_path,  
        scale=hamburger_scale,  
        pos=(0.55, 0.0, 0.40),  
        # euler=(0, 1.57, 0.0),  
        convexify = True,  
    ),  
)  
  
  
########################## build ##########################  
scene.build()  
  
for i in range(500):  
    scene.step()  
  
input()  
  
# それぞれの姿勢を取得し、floatに変換  
tresure_box_pose = list(map(float, tresure_box.get_pos()))  
pot_pose = list(map(float, pot.get_pos()))  
hamburger_pos = list(map(float, hamburger.get_pos()))  
  
tresure_box_quat = list(map(float, tresure_box.get_quat()))  
pot_quat = list(map(float, pot.get_quat()))  
hamburger_quat = list(map(float, hamburger.get_quat()))  
  
# jsonにして書き出す  
json_file_path = 'D:/home/myproj/genesis/UR5/asset/work/pose.json'  
pose_dict = {  
    'objects':  
        [ {  
            'object': 'tresure_box',  
            'position': tresure_box_pose,  
            'quaternion': tresure_box_quat,  
            'scale': tresure_scale,  
        },  
        {  
            'object': 'pot',  
            'position': pot_pose,  
            'quaternion': pot_quat,  
            'scale': pot_scale,  
        },  
        {  
            'object': 'hamburger',  
            'position': hamburger_pos,  
            'quaternion': hamburger_quat,  
            'scale': hamburger_scale,  
        }]  
}  
  
# jsonに書き出す  
with open(json_file_path, 'w') as f:  
    json.dump(pose_dict, f, indent=4)

普通にobjファイルを読み込んでシミュレーションした後に、get_pos, get_quatを使ってその物体の姿勢を取り出します。次のプログラムで読み出すために、json等にしてファイルに書き出します。

最後はこんな表示です。色がないですね。

open3dでの描画

Open3d側では、ファイルから情報を読み込んで表示するだけ

import json  
import open3d as o3d  
import numpy as np  
from scipy.spatial.transform import Rotation as R  
  
# 位置と回転を適用  
def apply_pose(mesh, pos, quat):  
    r = R.from_quat([quat[1], quat[2], quat[3], quat[0]])  # [x,y,z,w]  
    transform = np.eye(4)  
    transform[:3, :3] = r.as_matrix()  
    transform[:3, 3] = pos  
    mesh.transform(transform)  
  
def main():  
    # jsonファイルを読み込む  
    with open("d:/home/myproj/genesis/UR5/asset/work/pose.json", "r") as f:  
        objs = json.load(f)  
  
    meshes = []  
    for obj in objs["objects"]:  
        name = obj["object"]  
        pos = obj["position"]  
        quat = obj["quaternion"]  
        scale = obj["scale"]  
  
        # print(name, pos, quat, scale)  
  
        # 物体を読み込む  
        mesh = o3d.io.read_triangle_mesh(f"d:/home/myproj/genesis/UR5/asset/work/{name}.obj")  
        mesh.scale(scale, center=mesh.get_center())  
        apply_pose(mesh, pos, quat)  
  
        meshes.append(mesh)  
  
  
    # 表示
    o3d.visualization.draw_geometries(meshes)  
  
  
if __name__ == "__main__":  
    main()

結果はこうなります。カメラの位置を合わせてないので表示が一致しませんが、色つきでそれぞれの位置関係はあってそうです。

Pythonで学ぶ画像生成

Pythonで学ぶ画像生成読みました。 良い本だったので紹介します。

book.impress.co.jp

最初は手を動かして仕組みを理解しよう系の本だと思って写経をしていましたが、どちらかというとコードはgithubの物を見れば良くて写経する本では無かったです。前半で基本を抑えた後、後半から各手法と特徴がわかりやすくまとまっていて圧倒されてしまいました。最近はロボット関係で強化学習の方を追っていて画像生成が全然追えず、この本で一通り押さえられたのは良かったです。

表紙に「画像生成の基礎から実践までを一冊に凝縮」と書いてあるとおり、5章の拡散モデルによる画像生成技術の応用が特にすごく、順番に手法を紹介しながら、そこで出てくるキーワードの整理、既存手法との違いが図付きでわかりやすく説明されています。もちろん本気でやるならここからもっと調べないといけないですが、ちょっと雰囲気知るのであれば十分です。その後に実装のポイントがあり、実装で苦労するところ、改善のポイントが紹介されています。

5章一番最初のTextual Inversionの例で紹介します。

実装で躓きやすいポイント

  • placeholder_tokenとinitializer_tokenの扱い
  • マルチベクトルの利用
  • 勾配の更新を行う時の注意点

パラメータ調整や拡張の方法

  • 複数GPUの時とGPUが一つしか無いときの変更箇所
  • データセットが少ないときの調整箇所

デバッグ周辺

  • VRAM不足対策
  • placeholder_tokenの衝突時の挙動
  • 明らかにおかしくなったときの確認ポイント

発展例

  • サンプルを拡張して複数オブジェクトや複数スタイルを同時学習する方法
  • 生成画像の管理方法
  • テンプレートのカスタマイズ

まで、具体的なパラメータまで上げて説明してあります。これが、DreamBoothやAttension-and-Excite等、この後出てくる手法全てにそれぞれ違うアドバイスがあります。恐らくですが論文にはここまで全部は載っていないのと思うので、筆者(もしくは筆者のチーム)が実際に苦労した結果じゃないのかなと想像しています。

参考文献は章末にしっかりまとまっていますし、動くコードはgithubで動かせるし、とても良い本だと感じました。

github.com

今から画像生成の技術に追いつきたい人、画像生成をやっているがチューニングのポイントを知りたい人にお勧めです。

Genesisのバージョンを上げたら色々おかしくなった場所の解決方法

Genesisのバージョンを上げたら色々おかしくなった場所の解決方法

これの解決策

natsutan.hatenablog.com

ロボの色がおかしい件

ロボットを追加するときのvis_mode='collision'を無くす。これで表示は元に戻る。

ur = scene.add_entity(  
    gs.morphs.MJCF(file='D:/home/myproj/genesis/pybullet_ur5_gripper/robots/xml/ur5e.xml'),  
    # vis_mode='collision'  
)

ロボの軌道がおかしい件

control_dofs_positionで姿勢を設定しても重力に負けてしまっていた。

kp, kvを設定するタイミングで、set_dofs_force_rangeも設定する。

ur.set_dofs_kp(  
    kp,  
    dofs_idx  
)  
  
ur.set_dofs_kv(  
    kv,  
    dofs_idx  
)

ur.set_dofs_force_range(  
    np.array([-87, -87, -87, -87, -87, -87, -12, -12, -12, -100, -100, -100]),  
    np.array([87, 87, 87, 87, 87, 87, 12, 12, 12, 100, 100, 100]),  
    dofs_idx  
)

この2つで動きが元に戻りました。

線形代数セミナー

線形代数セミナーを読みました。多分JDLAのE資格の勉強で買ったまま積まれてた本です。もっと早く読めば良かった。

www.kyoritsu-pub.co.jp

コンピュータビジョンをやっていて、UΣVとか、固有値を大きい順にならべるとかで、何をしているのかわからない人にお勧めです。

内容は写像から入って、一般逆行列から数学と工学の間を埋めながら、最後は因子分解とカメラ行列まで一気に進みます。英日併記で、用語集などもあるのでボリュームは見た目より少ないです。少ないながらも、一つ一つの説明が丁寧でわかりやすかったです。

後書きにあるように、一般化にこだわらず基底を正規直交系に絞る事で説明をわかりやすく、実質1次元~3次元の話をすることでイメージがつかみやすくなっています。特に一般逆行列は全然わかっていなかったのがこの本でわかるようになりました。

線形代数の本を読んでいても「0じゃない固有値」という表現がよく出てきます。じゃどういうときに固有値が0になるのか、あまり上手く説明できないですよね。僕も面のフィッティングをしたいのに、観測値が直線に並んだ時くらいしか出てきませんでした。この本では、固有値の0は制約を表すという説明もあります。三次元の物体を画面に表示するとき、画面は二次元なので奥行き方向の誤差は発生しません(座標が面に拘束されている)。この時、その奥行き方面の固有値は0になると説明がありなるほどっと声が出てしまいました。

一般逆行列から、フィッティング、行列の因子分解と進め、最後それがコンピュータビジョンで何度も見ているカメラ行列に繋がるところは感動してしまいます。

ガチのコンピュータビジョンの本を読んで、なんちゃら分解して、固有値並べて、結局何やっているかさっぱりわからない人にお勧めです。

Genesisを最新にしたらおかしくなった。

とりあえずGenesisを最新にしたら、動いていた物が動かなくなった。

2025/04/12追記: vis_mode='collision' の指定を無くし、set_dofs_force_rangeを追加したら元に戻りました。

色もおかしいし、軌道もおかしい。 何かデータがずれているとか上手く読めてない気がする。

サンプルは動くし、関連するissueも見つけられないので、Genesis自体はこれで正しいんだと思う。

メモ 最新版に上げたときにこのエラーが出る。

  File "D:\home\myproj\genesis\Genesis\genesis\engine\entities\rigid_entity\rigid_entity.py", line 12, in <module>
    from genesis.utils import mesh as mu
  File "D:\home\myproj\genesis\Genesis\genesis\utils\mesh.py", line 18, in <module>
    from genesis.ext import fast_simplification
  File "D:\home\myproj\genesis\Genesis\genesis\ext\fast_simplification\__init__.py", line 2, in <module>
    from .replay import _map_isolated_points, replay_simplification  # noqa: F401
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "D:\home\myproj\genesis\Genesis\genesis\ext\fast_simplification\replay.py", line 3, in <module>
    from . import _replay
ImportError: cannot import name '_replay' from partially initialized module 'genesis.ext.fast_simplification' (most likely due to a circular import) 

github.com

ここに書いてあるとおり、.venv消して最初からインストールし直すとエラーはなくなった。 難儀である。