「3次元の世界を、コンピュータに理解させる」
点群解析はこのテーマに対する現時点での有力なアプローチのひとつです。カメラ映像が2次元の投影であるのに対し、点群は3次元空間内の実際の形状・距離・体積を直接表現します。近年では深層学習を用いることで、大規模な点群から高速に有益な情報を引き出せるようになり、様々な産業で応用が進んでいます。
本記事では、3D深層学習に踏み込むための前提知識を体系的に整理します。
ビジネス価値と産業応用
主要な応用分野
自動運転
自動運転車に搭載された回転式LiDARは、毎秒数十万点の点群をリアルタイムで生成します。点群解析により、歩行者・車両・障害物の3D検出、走行可能領域の判定、高精度地図との照合による自己位置推定が成り立っています。WaymoやApolloが公開する自動運転データセットの中心にLiDAR点群が据えられており、業界における点群の重要性が端的に示されています。
建設・BIM(建築情報モデリング)
建設現場をレーザースキャナで3Dスキャンすると、数億点規模の「現況点群」が得られます。これをBIMモデルと照合することで、設計と施工の差異を定量化できます。ミリ単位の精度の点群を手軽に得られるスキャナの発展を待ちつつも、従来は職人の目視と手測りに頼っていた工程管理が点群解析によって客観的・定量的なプロセスに変わりつつあります。
製造・品質検査
自動車部品や電子基板の3Dスキャンデータを設計CADと比較する「3D差分検査」は、点群の産業応用として最も成熟した領域のひとつです。人間の目では見落としがちなミクロン単位の変形や欠損を高速に検出できます。
ロボティクスと物流
倉庫ロボットは3D点群を用いてパレット上の荷物の位置・姿勢を推定し、ピッキング動作を生成します。LiDARベースのSLAM(Simultaneous Localization and Mapping)は、GPS信号が届かない屋内環境での自己位置推定を可能にします。
インフラ保全と農林業
橋梁や送電線をドローン搭載LiDARで定期スキャンし、変形量の時系列変化を追うことでメンテナンスの優先度を判断する試みが広がっています。農業・林業分野では樹冠の点群から樹高・葉面積指数を算出し、収量予測や施肥最適化に活用されています。
なぜ今なのか —— コスト革命
2010年代初頭、高性能な回転式LiDARは1台800万円を超えていました。2020年代に入ると、コンシューマ向けSolid-stateチップが数万円台で手に入るようになり、iPhone 12 Pro以降のLiDARスキャナはスマートフォンに内蔵されるまでになりました。センサーのコモディティ化と、クラウド上での大規模計算基盤の整備、そして深層学習による特徴抽出の自動化が重なり、点群解析の参入障壁は劇的に下がっています。
ビジネス視点のメモ
点群解析プロジェクトのコスト構造は「センサー費用<データ処理費用<アノテーション費用」というパターンが多いです。センサーが安くなっても、3Dアノテーションの労働集約性は深層学習でも完全には解消されていません。自社データのラベリング計画は早期に検討すべきポイントです。
点群の特性と課題
点群とは何か
点群(Point Cloud)とは、3次元空間内に離散的に分布する点の集合です。各点 は最低でも3次元座標 を持ち、センサーや用途に応じて反射強度、RGB色情報、法線ベクトル、タイムスタンプなどの属性が付加されます。点数 はシーンによって数百点から数十億点まで幅広く変動します。
画像との根本的な違い
点群が画像処理と本質的に異なる点は4つあります。
| 特性 | 画像 | 点群 |
|---|---|---|
| 空間構造 | 均一グリッド(H×W×C) | 不規則に分布 |
| 順序 | 画素位置で一意に決まる | 順序が定まらない(順不同) |
| サイズ | 固定 | シーンによって可変 |
| 密度 | 均一 | 遠方ほど疎、近傍ほど密 |
不規則性は、畳み込みのような「グリッド前提」の演算が直接使えないことを意味します。順不同性は、点の並び順を入れ替えても同じ形状を表現できるという数学的な対称性(Permutation Invariance)が求められることを意味します。これらが、点群専用のニューラルネットワークが必要になる根本的な理由です。
代表的なタスク
- 物体分類(Classification)
点群全体が何の物体かを予測します。
- パーツセグメンテーション(Part Segmentation)
椅子の「脚・背もたれ・座面」など、物体の各部位をラベル付けします。
- 3Dセマンティックセグメンテーション
屋内シーンや自動運転シーンの点群において、各点に「床・壁・車・歩行者」などのクラスを割り当てます。
- 3Dインスタンスセグメンテーション
シーン中の各点がいずれの物体に属しているかラベル付けします。
- 3D物体検出(Object Detection)
シーン中の物体の3Dバウンディングボックス(物体を囲む直方体や円筒)とクラスを予測します。
センサーと取得方法
LiDAR(Light Detection and Ranging)
LiDARは光のパルスを発射し、物体に反射して戻るまでの時間(飛行時間、Time of Flight)から距離を計算するセンサーです。光を様々な方向に走査することで3次元の距離マップ、すなわち点群を生成します。
回転式LiDAR(Spinning LiDAR)
Velodyne HDL-64Eに代表される、複数の発光・受光素子を垂直方向に並べたユニットを水平に回転させる方式です。1秒間に10〜20回転し、水平方向360°の全方位スキャンが可能です。チャンネル数(垂直解像度)が16/32/64/128と増えるほど高密度になりますが価格も跳ね上がる傾向があります。自動運転の研究開発では長らく標準機材でした。
Solid-state LiDAR
可動部を持たず、MEMSミラーやOPA(光フェーズドアレイ)、フラッシュLiDARなどの固定素子で走査する方式です。小型・低コスト・高耐久性ですが、視野角(FoV)が限られる製品が多いです。iPhone 12 Pro以降のLiDARスキャナ、Livox Midシリーズ、Luminar Irisなどが代表例です。車載量産モデルの主流になりつつあります。
機器選定のポイント
LiDAR単体では物体の色は得られません。色付き点群を得るにはRGBカメラと組み合わせることが必要です。
測定対象との距離や、必要な点密度に基づいて機器を選ぶことが重要です。また人がいるシーンではレーザー強度の安全性も気にする必要があります。
RGB-DカメラとToFカメラ
Structured Light(構造化光)
赤外線の格子パターンをシーンに投影し、その変形量から深度を計算します。Microsoft Kinect(初代)、Intel RealSense D400シリーズが代表的です。屋内・近距離に強い一方、外乱光がある屋外の直射日光下が苦手です。
ToFカメラ(間接ToF)
変調した赤外光を照射し、反射光の位相差から距離を計算するアクティブ方式です。Microsoft Azure Kinect、iPhoneのフロントカメラ(Face ID用センサー)に採用されています。近距離から中距離の高フレームレート計測に向いています。
SfM/MVS(パッシブ3D計測)
カメラ画像だけから3次元復元を行うパッシブ手法です。複数視点の画像からカメラ姿勢を推定(SfM:Structure from Motion)し、密な視差マッチング(MVS:Multi-View Stereo)で点群を生成します。ドローン空撮画像からの地形モデル生成、スマートフォンだけでの3Dスキャン(Metashape、RealityCapture)などに応用されています。
センサー比較
| センサー | 測定距離 | 精度 | コスト感 | 屋外使用 | 主な用途 |
|---|---|---|---|---|---|
| 回転式LiDAR | 数m 〜 200m | mm 〜 cm | 高(数十万〜) | ◎ | 自動運転、インフラ |
| Solid-state LiDAR | 数m 〜 150m | cm | 中 〜 低 | ◯ | 車載量産、ドローン |
| Structured Light | 0.2m 〜 5m | mm以下 | 低(数万〜) | △ | 室内3Dスキャン |
| ToFカメラ | 0.1m 〜 5m | cm | 低 | △ | ジェスチャー、AR |
| SfM/MVS | 制限なし | cm 〜 m | 低(カメラのみ) | ◎ | 空撮、文化財記録 |
前処理パイプライン
生の点群データをそのまま機械学習モデルに入力することはほとんどありません。ノイズ除去・ダウンサンプリング・正規化を経て、扱いやすい形式に整える、前処理パイプラインが不可欠です。
以下のサンプルコードではOpen3D*1[1][2]を使用します。また、配列・行列計算のためNumpy*2[3]を使用します。
*1 Pythonの点群処理ライブラリ、執筆時点はバージョン0.19。
*2 Pythonの数値計算ライブラリ、執筆時点ではバージョン2.4。
import open3d as o3d
import numpy as np
# 点群の読み込み (実際のファイルパスに合わせてください)
pcd = o3d.io.read_point_cloud("scene.pcd")
print(f"元の点数: {len(pcd.points)}")
ノイズ除去
LiDARや深度カメラは、反射率の低い物体、反対に金属面やガラス面のような反射率が高い物体、遠距離物体に対して外れ値(outlier)を生成することがあります。
Statistical Outlier Removal(統計的外れ値除去)
点間の距離の分布に注目し、ノイズ点は信号点と異なる統計的特徴をもつと仮定した手法です。
各点について 個の近傍点との間の距離の平均 を計算します。次に点群全体での距離の平均 と標準偏差 を計算します。そして、 の閾値を超える点をoutlierとして除去します。 は感度に関わるハイパーパラメータで、小さくするとノイズ判定の条件が厳しくなります。
Open3DでStatistical Outlier Removalを行う例
import open3d as o3d
# 点群の読み込み (実際のファイルパスに合わせてください)
pcd = o3d.io.read_point_cloud("scene.pcd")
print(f"元の点数: {len(pcd.points)}")
# nb_neighbors: 近傍点数, std_ratio: 閾値(λ)
pcd_clean, ind = pcd.remove_statistical_outlier(
nb_neighbors=20, # 上式の k
std_ratio=2.0 # 上式の λ
)
print(f"除去後: {len(pcd_clean.points)}")
Radius Outlier Removal(半径外れ値除去)
局所的な点の密度に注目し、ノイズ点は周囲から「浮いた点」であると仮定します。
各点において、半径 の球内に 点未満の隣接点しか持たない場合は孤立した点とみなしoutlierとして除去します。
Open3DでRadius Outlier Removalを行う例
import open3d as o3d
# 点群の読み込み (実際のファイルパスに合わせてください)
pcd = o3d.io.read_point_cloud("scene.pcd")
print(f"元の点数: {len(pcd.points)}")
pcd_clean, ind = pcd.remove_radius_outlier(
nb_points=16, radius=0.05 # 5cm以内に16点未満は除去
)
print(f"除去後: {len(pcd_clean.points)}")
ダウンサンプリング
数百万点の点群をそのままネットワークに入力するのは計算コストが高いため、用途に応じたダウンサンプリングで点数を削減します。
Voxelダウンサンプリング
空間を一定の大きさのボクセル(3D格子)に分割し、各ボクセル内の点を重心1点に代表させます。点数の空間的均一性を保つことができ、特にボクセル畳み込みニューラルネットワークモデルの入力生成でよく使われます。サンプリング後の点数は自明でないため、点数を一定以下に収めるような用途には不向きです。
Open3DでVoxelダウンサンプリングを行う例
import open3d as o3d
# 点群の読み込み (実際のファイルパスに合わせてください)
pcd = o3d.io.read_point_cloud("scene.pcd")
print(f"元の点数: {len(pcd.points)}")
pcd_down = pcd.voxel_down_sample(voxel_size=0.05) # 5cm格子
print(f"ダウンサンプリング後: {len(pcd_down.points)}")
FPS(Farthest Point Sampling、最遠点サンプリング)
既に選ばれた点から最も遠い点を順番に選んでいく貪欲法で、Voxelダウンサンプリングやランダムサンプリングに比べると低速です。最遠点サンプリングの大きな利点は、サンプリング結果が局所的な点密度に引きづられにくく、細かい形状の輪郭までよく保持することができることです。下の「点群のサンプリング手法の比較」の図でティーポットの持ち手や注ぎ口の形が残っていることを確認してください。例えばPointNet++[8]のように、深層学習モデルの入力生成で使われることがあります。
Open3DでFarthest Point Samplingを行う例
import open3d as o3d
# 点群の読み込み (実際のファイルパスに合わせてください)
pcd = o3d.io.read_point_cloud("scene.pcd")
print(f"元の点数: {len(pcd.points)}")
pcd_sampled = pcd.farthest_point_down_sample(num_samples=500) # 500点をサンプリング
print(f"ダウンサンプリング後: {len(pcd_sampled.points)}")
ランダムサンプリング
最もシンプルで高速ですが、点数の均一性は保たれないです。大規模データの前処理や確率的拡張(Data Augmentation)として用いられます。
Open3Dでランダムサンプリングを行う例
import open3d as o3d
# 点群の読み込み (実際のファイルパスに合わせてください)
pcd = o3d.io.read_point_cloud("scene.pcd")
print(f"元の点数: {len(pcd.points)}")
pcd_sampled = pcd.random_down_sample(sampling_ratio=0.05) # 元の点数の5%までダウンサンプリング
print(f"ダウンサンプリング後: {len(pcd_sampled.points)}")
ダウンサンプリング手法の比較
点群のサンプリング手法の比較(Voxelダウンサンプリング・Farthest Pointサンプリング・ランダムサンプリング)。Voxelダウンサンプリングは他と点数が同等になるようボクセルサイズを調整しています。
法線推定
法線ベクトル(Surface Normal Vector)は各点における局所的な面の向きを表すベクトルで、レンダリング時のライティング計算・特徴量抽出・セグメンテーションの前提として必要となる場合があります。
法線ベクトルの計算には、一般にいくつかの点のセットが必要です。例えば、まず対象となる点の 個の近傍点を探索します。そして近傍点に対して例えば主成分分析(PCA)を行い、最小固有値に対応する固有ベクトルを法線として採用します。法線ベクトルの向きは、面の「裏表」の区別にも使われるため、向きに一貫性をもたせる補正も重要です。一般に法線ベクトルが伸びている方向が、面の表側と見なされます。
Open3Dで法線推定を行う例
import open3d as o3d
# 点群の読み込み (実際のファイルパスに合わせてください)
pcd = o3d.io.read_point_cloud("scene.pcd")
pcd.estimate_normals(
# 法線推定のための近傍点について、半径10cm以内の最大30点を検索
search_param=o3d.geometry.KDTreeSearchParamHybrid(radius=0.1, max_nn=30)
)
# 視点(原点)方向に法線を統一
bunny_copy.orient_normals_consistent_tangent_plane(k=30) # 近傍点について最大30点を検索
o3d.visualization.draw_geometries([bunny_pcd, bunny_copy], point_show_normal=True)
データ構造と近傍探索
一般に点群データにおける点の順序は距離の近さと無関係です。一方、点群処理における多くのアルゴリズムでは「近傍探索」、すなわち「ある点の周囲にある点を効率的に見つける」処理が中核になっています。近傍探索はナイーブに全点総当たりすると の計算量が必要となり、点数が大きくなると実用的ではなくなるため、専用のデータ構造が不可欠です。
KD木(K-Dimensional Tree)
KD木は空間を超平面で再帰的に二分割した、二分探索木の構造を持ちます。 次元空間の 点に対して の計算量で構築でき、K近傍探索は平均 の計算量で実行できます。点の追加や削除がなければ、一度構築したKD木は使い回すことができます。
- 構築手順(2D例)
-
座標で分割 → 左/右に再帰
座標で分割 → 上/下に再帰
以降 → → → … と軸を交互に切り替える
Open3DでKD木構築を行う例
import open3d as o3d
# 点群の読み込み (実際のファイルパスに合わせてください)
pcd = o3d.io.read_point_cloud("scene.pcd")
# Open3D での KD木
pcd_tree = o3d.geometry.KDTreeFlann(pcd)
# K 近傍探索(最近傍10点)
query_point = np.array([0.0, 0.0, 0.0])
[k, idx, dist] = pcd_tree.search_knn_vector_3d(query_point, 10)
# 半径探索(半径0.1m以内)
[k, idx, dist] = pcd_tree.search_radius_vector_3d(query_point, 0.1)
適用場面
低〜中次元空間(3次元点群には最適)、静的な点群での繰り返し探索。次元数が増えると「次元の呪い」で性能が劣化するため注意が必要です。
KD木の応用例
局所的な特徴量算出
物体の形状を特徴づける、直線性/平面性、曲率などの計算に応用できます(法線ベクトル推定と同様)。これらの特徴量を算出するには複数の点が必要、すなわち、まず対象となる点の近傍点を探索しておく必要があります。
アップスケーリング
K近傍探索では、構築済みのKD木に対し別の点群の点をクエリに用いることができます。例えば、低解像度の点群で計算された特徴量があり、その特徴量を高解像度の点群にマッピングしたいとします。この場合、低解像度点群でKD木を構築しておき、高解像度点群の点座標をクエリとします。そうすることで高解像度点それぞれに対し、最も近傍にある低解像度点を得ることができます。
下の図は低解像度点群の特徴量(法線ベクトルの向きを疑似カラー化)を高解像度点群にマッピングした例です(1近傍点の特徴量をそのまま割り当て)。
Open3DでKD木を用いてアップサンプリングを行う例
import numpy as np
import open3d as o3d
# 法線ベクトル3成分を RGB に変換するヘルパー関数
def encode_normal_vector_to_color(vector):
# ベクトル成分を [0, 1] の範囲に正規化
normalized_vector = (vector - np.min(vector)) / (np.max(vector) - np.min(vector))
color = np.zeros((len(vector), 3))
color[:, 0] = normalized_vector[:, 0] # Red channel
color[:, 1] = 1 - normalized_vector[:, 1] # Green channel
color[:, 2] = 0.5 * (1 + np.sin(2 * np.pi * normalized_vector[:, 2])) # Blue channel
return color
# 点群の読み込み (実際のファイルパスに合わせてください)
pcd = o3d.io.read_point_cloud("scene.pcd")
# 低解像度点群 (ここではダウンサンプリングで作成。また特徴量は法線ベクトルとします)
pcd_low_res = pcd.voxel_down_sample(voxel_size=0.02)
pcd_low_res.estimate_normals(search_param=o3d.geometry.KDTreeSearchParamHybrid(radius=0.1, max_nn=30))
pcd_low_res.orient_normals_consistent_tangent_plane(k=30)
# 低解像度の特徴量を擬似カラー化
colors_low_res = encode_normal_vector_to_color(np.asarray(pcd_low_res.normals))
# 低解像度点群の KD 木
kd_tree_low_res = o3d.geometry.KDTreeFlann(pcd_low_res)
# KD 木を用いたアップスケーリング。高解像度点群の各点に対し、低解像度点群側の近傍点を検索
colors_high_res = np.zeros((len(pcd.points), 3))
for i in range(len(pcd.points)):
_k, idx, _ = kd_tree_low_res.search_knn_vector_3d(pcd.points[i], 1) # 近傍1点
colors_high_res[i] = np.mean(colors_low_res[idx], axis=0)
pcd_low_res.colors = o3d.utility.Vector3dVector(colors_low_res)
pcd.colors = o3d.utility.Vector3dVector(colors_high_res)
o3d.visualization.draw_geometries([pcd_low_res, pcd], point_show_normal=True)
Octree(八分木)
3次元空間を8つの子ノードに再帰的に分割する木構造です。空間の疎/密を柔軟に表現でき、大規模シーンの階層的管理に向きます。Octreeは空間的な索引だけでなく、点群の圧縮にも利用できます。空ノードを記録しないことにより、疎な点群を効率よく格納することができます。
- ルートノード(シーン全体のバウンディングボックス)
-
- 子ノード1(空) → 枝刈り
- 子ノード2(空) → 枝刈り
-
子ノード3(点あり) → さらに分割
- 孫ノード3-1(空)
- 孫ノード3-2(点あり) → 葉ノード
Open3DでOctree構築を行う例
import open3d as o3d
# 点群の読み込み (実際のファイルパスに合わせてください)
pcd = o3d.io.read_point_cloud("scene.pcd")
octree = o3d.geometry.Octree(max_depth=5) # 最大深さ5
octree.convert_from_point_cloud(pcd)
o3d.visualization.draw_geometries([pcd, octree])
Octreeの応用例
レンダリングにおけるLevel of Details(LoD)
点群をレンダリングするとき、カメラから離れた場所の点を間引いても視覚的に問題ないことが多いです。そのようにカメラとの距離に応じてレンダリング内容の詳細度を変えることをLevel of Detailsといいます。Octreeを用いたLoDでは、最下位ノードに最も高解像度の点を格納し、ノードを遡る(グリッドサイズが倍になる)につれてノードに格納する点数を減らしていきます。レンダリング時は、ビューポート上でノードのグリッドサイズが一定の大きさを超える場合(つまり高解像度な点が必要)に下位ノードを描画対象に追加します。
Voxel Hash(Hashing-based Voxel Grid)
空間を固定サイズのボクセルに分割し、ハッシュテーブルで管理する手法です。KD木のような構築コストがなく、ボクセル単位の挿入・検索を 期待値の計算量で行えます。大規模なオンラインSLAMや、Sparse Convolution(MinkowskiNetなど)の空間インデックスとして広く使われています。
データ構造の比較
| KD木 | Octree | Voxel Hash | |
|---|---|---|---|
| 構築コスト | 期待値 | ||
| K近傍探索 | 平均 | 〜 | +ハッシュ |
| 半径探索 | 良好 | 良好 | ボクセル粒度に依存 |
| 動的更新 | 困難 | 容易 | 容易 |
| メモリ効率 | 中 | 高(疎なシーン) | 高 |
| 最適な用途 | 小〜中規模、静的 | 大規模、階層管理 | オンライン処理 |
古典的アルゴリズム
深層学習が登場する以前、点群処理は精巧に設計された古典的アルゴリズムによって成り立っていました。これらはいまも実用システムの前処理・後処理として現役で使われており、深層学習との組み合わせで使われることも多いです。
ICP(Iterative Closest Point)によるレジストレーション
点群スキャンを行うとき、空間が大規模なケースでは複数回のスキャンが必要な場合があります。また、同じ場所を繰り返しスキャンして空間の変化を点群の差分として検出したい場合があります。ここで、一般に点群の座標はスキャン開始地点からの相対座標であり、点群間で座標系が異なっていることが問題になります。つまり点群の結合や差分検出では、前処理として点群間の座標系を一致させる処理が必要になります。このようなタスクをレジストレーション(registration、位置合わせ)と呼びます。
レジストレーションでは2つの点群 (移動させる点群)と (固定しておく参照点群)が与えられたとき、 を に重ね合わせる剛体変換(回転行列 と並進ベクトル )を求めます。ここでレジストレーションは大きく、変換行列の初期値が必要なローカルレジストレーションと、初期値不要のグローバルレジストレーションの2タイプに分けられます。ICP(Besl & McKay 1992[4])はローカルレジストレーション手法であり、最近傍点間の距離の和が最小になるような剛体変換を推定します。
アルゴリズムの流れ
初期化:回転行列 (3×3単位行列), 並進ベクトル ()
-
繰り返し(収束するまで)
Correspondence: の各点 に対して 中の最近傍点 を対応付ける
Transform:SVD(特異値分解)を用いて最適な , を計算
Apply: に変換(, )を適用
収束判定:平均距離の変化量が閾値未満なら終了
Open3DでICPによるレジストレーションを行う例
import open3d as o3d
# レジストレーション結果を表示するヘルパー関数
def draw_registration_result(source, target, transformation):
source_temp = copy.deepcopy(source)
target_temp = copy.deepcopy(target)
source_temp.paint_uniform_color([1, 0.706, 0])
target_temp.paint_uniform_color([0, 0.651, 0.929])
source_temp.transform(transformation)
o3d.visualization.draw_geometries([source_temp, target_temp],
zoom=0.6459,
front=[0.9288, -0.2951, -0.2242],
lookat=[1.6784, 2.0612, 1.4451],
up=[-0.3402, -0.9189, -0.1996])
# Open3D の ICP
use_initial_transform = True
demo_icp_pcds = o3d.data.DemoICPPointClouds()
source = o3d.io.read_point_cloud(demo_icp_pcds.paths[0])
target = o3d.io.read_point_cloud(demo_icp_pcds.paths[1])
threshold = 0.02
trans_init = np.asarray([[0.862, 0.011, -0.507, 0.5],
[-0.139, 0.967, -0.215, 0.7],
[0.487, 0.255, 0.835, -1.4],
[0.0, 0.0, 0.0, 1.0]
]) if use_initial_transform else np.eye(4)
print("Apply point-to-point ICP")
reg_p2p = o3d.pipelines.registration.registration_icp(
source, target, threshold, trans_init,
o3d.pipelines.registration.TransformationEstimationPointToPoint(),
o3d.pipelines.registration.ICPConvergenceCriteria(max_iteration=2000))
print(reg_p2p)
print("Transformation is:")
print(reg_p2p.transformation)
draw_registration_result(source, target, reg_p2p.transformation)
限界と対策
ICPは局所最適に陥りやすく、変換行列の初期値が悪いと収束しません。一般には、FPFH特徴量などによるグローバルレジストレーション(後述)で粗い初期値を得てから、ICPで精密化するというワークフローが採用されます。
RANSAC(Random Sample Consensus)
外れ値(outlier)を多く(ただし最大でも全点の50%未満)含むデータから、目的のモデル(平面・直線・球など)のパラメータを堅牢に推定するために用いられます。
RANSAC(Fischler & Bollers 1981[5])は少数の点のランダムサンプリングを繰り返して、試行錯誤的に最適パラメータを推定します。ランダムに選んだ点を使って、モデルの仮のパラメータを推定します。そして残りの点に対するモデルのフィッティング度合いを算出します。モデルのフィッティング度合い(モデルとの距離が閾値内の、inlier数を指標とするのがミソ)が十分よければ、最適モデル候補として残しておきます。このようなランダムサンプリングとモデル評価を繰り返し、最もinlier数が多かったモデルを採用します。
アルゴリズムの流れ(3D平面推定の例)
ランダムに最小サンプル数(平面なら3点)を選択する
選択した点からモデル(平面パラメータ )を推定する
全点についてモデルへの距離を計算し、閾値 以内の点をinlierとしてカウントする
1〜3を 回繰り返し、inlier数が最多だったモデルを採用する
採用モデルのinlierのみを使って最終的なパラメータを推定する
古典的な特徴量記述子
深層学習以前の点群認識では、局所的な幾何情報を数値ベクトルとして記述する「特徴量記述子」がマッチングの中核を担っていました。
FPFH(Fast Point Feature Histograms)
各点の近傍点との法線角度・距離の分布をヒストグラム化した33次元の特徴量です(Rusu et al. 2009[6])。FPFHを用いたグローバルレジストレーションは、前述のICPの初期値を得る手段として今でも現役で使われています。
Open3DでFPFH特徴量を計算する例
import open3d as o3d
# 特徴ベクトルを RGB 疑似カラーに変換するヘルパー関数
def encode_vector_to_color(vector):
# 主成分分析 (PCA) でベクトルの次元を3次元に削減、各次元を RGB 値とみなす
pca = np.linalg.svd(vector - np.mean(vector, axis=0), full_matrices=False)
color = np.dot(vector, pca[2][:3].T)
# 色の値を [0, 1] の範囲に正規化
color = (color - np.min(color)) / (np.max(color) - np.min(color))
return color
# 点群の読み込み (実際のファイルパスに合わせてください)
pcd = o3d.io.read_point_cloud("scene.pcd")
# 法線推定が前提
pcd.estimate_normals(o3d.geometry.KDTreeSearchParamHybrid(radius=0.1, max_nn=30))
pcd.orient_normals_consistent_tangent_plane(k=30)
# FPFH 特徴量を計算
fpfh = o3d.pipelines.registration.compute_fpfh_feature(
pcd,
o3d.geometry.KDTreeSearchParamHybrid(radius=0.25, max_nn=100)
) # fpfh.data: shape (33, N)
colors = encode_vector_to_color(np.asarray(fpfh.data.T))
pcd.colors = o3d.utility.Vector3dVector(colors)
o3d.visualization.draw_geometries([pcd])
古典的手法の意義
これらのアルゴリズムは人間が解釈可能で、デバッグが容易で、ラベル付き教師データを必要としない利点があります。3Dスキャンの位置合わせや平面抽出といった「明確なルールで解ける問題」では今でも深層学習より速く・軽く・十分な精度を出す場合も多いです。「どこで深層学習を使うか」を判断するためにも古典的手法の理解が不可欠です。
深層学習への橋渡し
古典的手法の限界
ここまで紹介してきたアルゴリズムは、いずれも人間が特徴量を設計しているという共通点を持ちます。ICPは「最近傍対応」という人間が定義したルールで動き、FPFHは「法線角度の分布」という人間が設計した特徴量を使います。
この特徴エンジニアリングのアプローチには本質的な限界があります。
まず、設計した特徴量がパターンの認識に必要な情報を完全に捉えられているという保証がありません。複雑な形状をもつカテゴリ(ものによって細部が異なる家具や、種類によって幅広い構造バリエーションをもつ植物の識別など)では、人間が考えつく特徴量だけでは限界に当たりやすくなります。また、新しいタスクへ適用しようとするたびに専門知識を持つエンジニアが特徴量を再設計する必要が生じ、スケールしません。さらに、屋外の雨天シーンや反射の強い物体など、想定外の環境では事前に設計した特徴量の性能が急落する傾向があります。
深層学習で何が変わったか
2017年に発表されたPointNet(Qi et al. 2017[7])は「点群に直接作用する深層学習モデル」の最初の成功例です。大量のデータから、モデルが自動的に最適な特徴表現を学習し、人間の設計を不要にするというアプローチは点群認識の精度と汎用性を一気に引き上げました。
「前処理 → 特徴エンジニアリング → 分類器」という分離したパイプラインから、「入力点群 → End-to-Endの深層学習 → タスク出力」という統合されたアプローチへの転換が起きました。
その後、3Dに拡張された畳み込みニューラルネットワーク(CNN)により局所的な特徴量の性能が向上し、また自然言語モデルで発展したTransformerの導入によって大域的な特徴や関連度を踏まえた推論が可能になり、性能が一層向上しています。点群においては近年グラフニューラルネットワーク(GNN)の応用も進んでおり、インスタンスセグメンテーションやパーツセグメンテーションのような難易度が高いタスクの精度向上の試みが行われています。
主要なデータセット
深層学習モデルの学習と評価には大規模なアノテーション済みデータセットが必要になります。代表的なものを示します。
| データセット | タスク | 規模 | 特徴 |
|---|---|---|---|
| ModelNet40 | 分類 | 12,311モデル/40クラス | 合成CADモデル |
| ShapeNetPart | パーツセグメンテーション | 16,880モデル/16カテゴリ | パーツラベル付き |
| ScanObjectNN | 分類 | 14,510点群/15クラス | 実スキャン(背景ノイズあり) |
| S3DIS | 3Dセマンティック | 6エリア/13クラス | 屋内シーン(RGB-D) |
| ScanNet v2 | 3Dセマンティック | 1,513シーン/20クラス | 屋内RGB-Dスキャン |
| SemanticKITTI | 自動運転セマンティック | 23,201スキャン/19クラス | 屋外LiDAR |
| KITTI | 3D物体検出 | 7,481シーン | 車・歩行者・自転車 |
| nuScenes | 3D物体検出 | 700シーン(1000時間相当) | マルチセンサー融合 |
まとめ
本記事では以下の内容を扱いました。
点群解析は自動運転・建設・製造・ロボティクスにわたる広範なビジネス価値を持ち、センサーの低コスト化を背景に急速に普及しつつあります。
点群は画像と異なり「不規則・順不同・可変サイズ・疎」という4つの特性を持ち、これが専用アルゴリズムを必要とする根本的な理由です。
センサーはLiDAR・RGB-D・SfM/MVSと多岐にわたり、用途に応じた選択が重要です。
前処理(ノイズ除去・ダウンサンプリング・法線推定)はどの深層学習パイプラインにも共通する基礎工程です。
KD木・Octree・Voxel Hashは近傍探索の効率化において中心的な役割を担います。
ICP・RANSAC・クラスタリング・特徴量記述子という古典的アルゴリズムは、解釈可能性と計算効率の点で今も実用価値を持ち、深層学習との組み合わせで使われ続けています。
これらの基礎を踏まえ、今後深層学習モデルの設計原理に踏み込む記事を公開する予定です。
参考文献
- [1] Open3D: A Modern Library for 3D Data Processing(Open3D公式ドキュメント)
- [2] Q. Y. Zhouら『Open3D: A Modern Library for 3D Data Processing』(CVPR. DOI: 10.48550/arXiv.1801.09847)
- [3] Numpy: The fundamental package for scientific computing with Python(Numpy公式ドキュメント)
- [4] P. J. Besl and N. D. McKey『A Method for Registration of 3-D Shapes』(IEEE TPAMI. DOI: 10.1109/34.121791)
- [5] M. A. Fischler and R. C. Bolles『Random Sample Consensus: A Paradigm for Model Fitting with Applications to Image Analysis and Automated Cartography』(CACM. DOI: 10.1145/358669.358692)
- [6] R. B. Rusuら『Fast Point Feature Histograms (FPFH) for 3D Registration』(IEEE ICRA. DOI: 10.1109/ROBOT.2009.5152473)
- [7] C. R. Qiら『PointNet: Deep Learning on Point Sets for 3D Classification and Segmentation』(CVPR. DOI: 10.48550/arXiv.1612.00593)
- [8] C. R. Qiら『PointNet++: Deep Hierarchical Feature Learning on Point Sets in a Metric Space』(NeurIPS. DOI: 10.5555/3295222.3295263)