見出し画像

点群データをボクセルに変換する処理を書いたよ

こんにちは、SAKAMOTOです。

この記事はタイトルの通り、ほとんど報告みたいなものです!
一番伝えたいメッセージは「ぬいぐるみモデリング可愛いよね〜。ていうかLumaAIって凄くね?」であって、実装について解説する記事ではありません(一応公開はしておきます)。



実行結果

可愛い!好きな粒度で点群データをローポリにして遊べます!

ボクセル化処理の実行例

ちなみにこの点群データはLumaAIというアプリを使って出力したモデル(OBJ形式)を点群データ(PCD形式)に変換して作成しました。OBJ→PCDの変換にはCloudCompareを使っています。


LumaAIってすごい

このアプリでは、複数の画像データから3Dシーンを生成する技術(NeRF)を応用して、スマホで撮影した動画からオブジェクトの3Dモデル(OBJ形式)を作成することを可能にしています!

↓作成したデータはこんな感じで公開もできる!↓

画像データから3Dモデルを作成できるということは、カメラさえ付いていればモデルが作成できてしまう訳です。
今までLiDAR(レーザースキャナー)を搭載していなければ作成できなかったはずの3Dモデルを誰でも作成できるようにした画期的な技術です。


この技術の応用先

実世界のデータから簡単にローポリデータを作成することができるようになったので、3Dゲーム用素材の作成が爆速になるかも?
地形データに対して実行すれば、実世界をマイクラで再現することも可能です。

作成したモデルはゲームエンジンで利用可能


実装

こちらに公開しています。

処理の流れ

  1. 点群(PCD形式)を読み込む(->o3d.geometry.PointCloud)。

  2. 点群をボクセルデータ(o3d.geometry.VoxelGrid)に置き換える。

  3. 見やすさのため、ボクセルを囲むLineSet(o3d.geometry.LineSet)を作成する。

  4. 作成したVoxelGridとLineSetを描画する。

  5. ボクセルデータをメッシュ(o3d.geometry.TriangleMesh)に変換する。

  6. できたメッシュデータをPLY形式で書き出す。

実行例

poetry run python voxelize.py -i <点群データのパス(str)> -v <ボクセルの一辺の長さ(float)>
import argparse
from typing import List

import numpy as np
import open3d as o3d  # type: ignore


def create_mesh_edges(
    cube_size: float, cube_center: np.ndarray
) -> o3d.geometry.LineSet:
    """
    与えられたサイズと中心点を持つ立方体のメッシュを作成します。(メッシュの枠線)

    Parameters:
    cube_size (float): 立方体の一辺の長さ
    cube_center (np.ndarray): 立方体の中心点

    Returns:
    o3d.geometry.LineSet: 立方体のメッシュ
    """
    vertices = (
        np.array(
            [
                [-1, -1, -1],
                [-1, -1, 1],
                [-1, 1, -1],
                [-1, 1, 1],
                [1, -1, -1],
                [1, -1, 1],
                [1, 1, -1],
                [1, 1, 1],
            ]
        )
        * cube_size
        / 2
        + cube_center
    )

    edges = np.array(
        [
            [0, 1],
            [0, 2],
            [0, 4],
            [1, 3],
            [1, 5],
            [2, 3],
            [2, 6],
            [3, 7],
            [4, 5],
            [4, 6],
            [5, 7],
            [6, 7],
        ]
    )

    lines = o3d.geometry.LineSet()
    lines.points = o3d.utility.Vector3dVector(vertices)
    lines.lines = o3d.utility.Vector2iVector(edges)
    return lines


def create_mesh_cube(
    cube_size: float, cube_center: np.ndarray, cube_color: np.ndarray
) -> o3d.geometry.TriangleMesh:
    """
    与えられたサイズと中心点を持つ立方体のメッシュを作成します。(立方体の実体)

    Parameters:
    cube_size (float): 立方体の一辺の長さ
    cube_center (np.ndarray): 立方体の中心点

    Returns:
    o3d.geometry.TriangleMesh: 立方体のメッシュ
    """
    cube = o3d.geometry.TriangleMesh.create_box(
        width=cube_size, height=cube_size, depth=cube_size
    )
    cube.translate(
        cube_center - np.array([cube_size / 2, cube_size / 2, cube_size / 2])
    )
    cube.vertex_colors = o3d.utility.Vector3dVector(np.tile(cube_color, (8, 1)))
    return cube


def voxel_grid_to_mesh(
    voxel_grid: o3d.geometry.VoxelGrid, voxel_size: float
) -> o3d.geometry.TriangleMesh:
    """
    VoxelGridをTriangleMeshに変換します。

    Parameters:
    voxel_grid (o3d.geometry.VoxelGrid): 入力VoxelGrid
    voxel_size (float): ボクセルの一辺の長さ

    Returns:
    o3d.geometry.TriangleMesh: 結合されたメッシュ
    """
    combined_mesh = o3d.geometry.TriangleMesh()

    # 各ボクセルを立方体メッシュに変換して結合
    for voxel in voxel_grid.get_voxels():
        cube_center = (
            voxel.grid_index * voxel_size + voxel_grid.get_min_bound() + voxel_size / 2
        )
        cube_color = voxel.color
        cube_mesh = create_mesh_cube(voxel_size, cube_center, cube_color)
        combined_mesh += cube_mesh

    return combined_mesh



def voxelize_pcd(
    pcd: o3d.geometry.PointCloud,
    voxel_size: float,
) -> tuple[o3d.geometry.VoxelGrid, List[o3d.geometry.LineSet]]:
    """
    点群をボクセル化し、ボクセルグリッドとラインセットのリストを返します。

    Parameters:
    pcd (o3d.geometry.PointCloud): 入力点群
    voxel_size (float): ボクセルの一辺の長さ

    Returns:
    tuple[o3d.geometry.VoxelGrid, List[o3d.geometry.LineSet]]: ボクセルグリッドとラインセットのリスト
    """
    voxel_grid = o3d.geometry.VoxelGrid.create_from_point_cloud(
        pcd, voxel_size=voxel_size
    )
    min_bound = voxel_grid.get_min_bound()
    points = np.array(
        [
            (voxel.grid_index * voxel_size + min_bound + voxel_size / 2)
            for voxel in voxel_grid.get_voxels()
        ]
    )
    colors = np.array([voxel.color for voxel in voxel_grid.get_voxels()])

    linesets = []
    for voxel in voxel_grid.get_voxels():
        cube_center = voxel.grid_index * voxel_size + min_bound + voxel_size / 2
        cube = create_mesh_edges(voxel_size, cube_center)
        linesets.append(cube)
    o3d.visualization.draw_geometries([pcd] + linesets + [voxel_grid])

    mesh = voxel_grid_to_mesh(voxel_grid, voxel_size)
    o3d.io.write_triangle_mesh("data/voxel.ply", mesh)

    return voxel_grid, linesets


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Load and display a point cloud")
    parser.add_argument(
        "--pcd_path",
        "-i",
        type=str,
        help="Path to the point cloud file (.pcd)",
    )
    parser.add_argument(
        "--voxel_size", "-v", type=float, default=0.2, help="voxel size"
    )
    args = parser.parse_args()

    pcd = o3d.io.read_point_cloud(args.pcd_path)
    o3d.visualization.draw_geometries([pcd])

    voxel_grid, linesets = voxelize_pcd(pcd, args.voxel_size)


いいなと思ったら応援しよう!