🌏

第2回 Minecraft × PLY点群:見た目を整える外れ値除去&平滑化で効果を探る

に公開

🧹 PLY点群をMinecraftに配置する前の前処理

前回は、PLY点群をMinecraftのブロックで再現しました。
今回はより綺麗な再現を目指して、点群の前処理を行います。

3DGSなどから出力されるPLYは、点の飛びやばらつきがある場合があります。
そのままMinecraftに置くと、ブロックが散らばったり形が不自然になることがあります。
そこで、今回は以下の3ステップで前処理します。

  1. 外れ値除去(Outlier Removal)
  2. 平滑化(Smoothing)
  3. 両方同時(Denoise & Smooth)

前回の結果

scale=50, thinning=2

1️⃣ 外れ値除去:outlier_removed.py

import open3d as o3d

def remove_outliers(input_path, output_path):
    pcd = o3d.io.read_point_cloud(input_path)
    cl, ind = pcd.remove_statistical_outlier(nb_neighbors=20, std_ratio=2.0)
    pcd_clean = pcd.select_by_index(ind)
    o3d.io.write_point_cloud(output_path, pcd_clean)
    print(f"Outliers removed. Saved to {output_path}")

if __name__ == "__main__":
    remove_outliers("lego.ply", "outlier_removed.ply")

解説

  1. PLYを読み込み、点群を取得

  2. remove_statistical_outlier()統計的に外れ値を除去

    • nb_neighbors=20 : 近傍点数
    • std_ratio=2.0 : 標準偏差の閾値
  3. 外れ値を除いた点群を保存

外れ値が多い場合、Minecraftでブロックを置いたときに散らばるのを防げます。

結果

前回の記事のコードでMinecraft上に再現してみた結果

https://zenn.dev/kento_1938/articles/0ba1a34a01cc1e

"outlier_removed.ply", scale=50, thinning=2

2️⃣ 平滑化:smoothed.py

import open3d as o3d
import numpy as np

def smooth_point_cloud(input_path, output_path, iterations=1):
    pcd = o3d.io.read_point_cloud(input_path)
    points = np.asarray(pcd.points)

    for _ in range(iterations):
        pcd_tree = o3d.geometry.KDTreeFlann(pcd)
        new_points = []
        for i in range(len(points)):
            [_, idx, _] = pcd_tree.search_knn_vector_3d(pcd.points[i], 10)
            neighbor_pts = points[idx, :]
            avg_pt = np.mean(neighbor_pts, axis=0)
            new_points.append(avg_pt)
        points = np.array(new_points)
        pcd.points = o3d.utility.Vector3dVector(points)

    o3d.io.write_point_cloud(output_path, pcd)
    print(f"Smoothed point cloud saved to {output_path}")

if __name__ == "__main__":
    smooth_point_cloud("lego.ply", "smoothed.ply", iterations=3)

解説

  1. PLYを読み込み、座標をNumPy配列に変換
  2. KDTreeで近傍点を探索
  3. 近傍点の平均位置で点を移動 → 平滑化
  4. 指定回数(iterations)繰り返すことで、より滑らかに

平滑化により、Minecraft上でのブロック配置がより滑らかで自然な形になります。

結果

"smoothed.ply", scale=50, thinning=2

3️⃣ 両方同時:denoised_smoothed.py

import open3d as o3d
import numpy as np

def denoise_and_smooth(input_path, output_path, iterations=3):
    pcd = o3d.io.read_point_cloud(input_path)
    cl, ind = pcd.remove_statistical_outlier(nb_neighbors=20, std_ratio=2.0)
    pcd_clean = pcd.select_by_index(ind)

    points = np.asarray(pcd_clean.points)
    for _ in range(iterations):
        pcd_tree = o3d.geometry.KDTreeFlann(pcd_clean)
        new_points = []
        for i in range(len(points)):
            [_, idx, _] = pcd_tree.search_knn_vector_3d(pcd_clean.points[i], 10)
            neighbor_pts = points[idx, :]
            avg_pt = np.mean(neighbor_pts, axis=0)
            new_points.append(avg_pt)
        points = np.array(new_points)
        pcd_clean.points = o3d.utility.Vector3dVector(points)

    o3d.io.write_point_cloud(output_path, pcd_clean)
    print(f"Denoised and smoothed point cloud saved to {output_path}")

if __name__ == "__main__":
    denoise_and_smooth("lego.ply", "denoised_smoothed.ply", iterations=3)

解説

  1. まず統計的外れ値除去でノイズ点を削除
  2. その後、近傍点平均による平滑化を繰り返す
  3. 両方を同時に行うことで、よりクリーンで滑らかな点群を生成
  4. 保存したPLYを、前回紹介したMinecraftブロック化スクリプトに渡すことで、より自然な再現が可能

結果

"denoised_smoothed.ply", scale=50, thinning=2

💡 コメント

  • outlier_removed
    外れ値を除去するとブロック数は減り、多少スッキリしますが、形状の再現性への大きなメリットはあまり感じられませんでした。

  • smoothed
    平滑化によりノイズや飛び点は減りますが、削られすぎて形状が崩れる場合もあるため、もう少し工夫が必要だと感じた。

  • 総評
    PLYの前処理は、形状を保持しつつノイズを減らすという点で有効ですが、Minecraftでの再現ではもっと調整や適用範囲の工夫が必要だと感じた。

🎁 おまけ:スケール比較

前回記述したコードでは、scaleの値によってブロックのサイズ感や形状の認識が変わります
今回はそれを使って、間引き率thinning=2で3種類のスケールを比較してみました。

1. scale=20, thinning=2

  • 小さめのサイズ

  • コメント:これより小さいと形がわからなくなりそうです。


2. scale=30, thinning=2

  • やや小さめサイズ

3. scale=80, thinning=2

  • 大きめサイズ
  • コメント:これ以上大きくするとデカすぎてMinecraft上で扱いにくいです。

💡 補足
スケールはMinecraft上での「見やすさ」と「建造の扱いやすさ」のバランスで調整する変数です。
小さすぎると形状が潰れ、大きすぎると建造負荷が上がります。

Discussion