3次元画像処理における、N種の神器を紹介します

はじめに

3次元データの処理は、コンピュータビジョン、ロボティクス、拡張現実(AR)/仮想現実(VR)といった分野で不可欠な技術です。これらの分野では、現実世界の3次元情報を取得・解析し、それに基づいてシステムを制御したり、新たな情報を生成したりすることが求められます。本記事では、3次元処理に関わる主要なPythonツールを紹介し、それぞれの特徴と使い分けについて解説します。各ツールの具体的な使い方だけでなく、実践的なTipsや注意点にも触れることで、読者の皆様が日々の開発や研究に役立てられるような情報を提供することを目指します。

この記事を書いたひと

デジタルリアクタ合同会社 代表
機械学習・統計、数値計算などの領域を軸としたソフトウェアエンジニアリングを専門としています。スタートアップからグローバル企業まで、さまざまなスケールの企業にて、事業価値に直結する計算システムを設計・構築してきました。主に機械学習の応用分野において、出版・発表、特許取得等の実績があり、また、IT戦略やデータベース技術等の情報処理に関する専門領域の国家資格を複数保有しています。九州大学理学部卒業。日本ITストラテジスト協会正会員。

対象読者:

  • Pythonを用いて3次元データ処理に携わるエンジニアや研究者
  • コンピュータビジョン、ロボティクス、AR/VRなどの分野で3次元データ処理に関心のある方
  • 3次元処理の基礎から実践的なツールまで幅広く学びたい方

記事のポイント:

  • 3次元処理の主要なステップ(2D画像からの3D情報抽出、3D再構成、点群処理、可視化)ごとに最適なツールを紹介し、それらを連携させることで包括的なパイプラインを構築方法を示す。

2D画像からの3D理解

多くの場合、3次元処理の出発点は、カメラで撮影した2次元画像から、物体の奥行きや位置関係といった3次元情報を推定することです。このセクションでは、2D画像からの3次元情報抽出に焦点を当て、特徴点検出・マッチングとマーカーベースの3D位置推定という2つのアプローチと、それらを支えるツールを紹介します。

特徴点検出・マッチング

2D画像から3次元情報を抽出する際、まず画像中の特徴的な点(特徴点)を検出し、異なる画像間で対応する特徴点を結びつける(マッチング)処理を行います。この対応関係から、カメラの位置関係や物体の3次元構造を推定することができます。

OpenCV - 安定性と実績を兼ね備えた定番ツール

OpenCVは、コンピュータビジョンの分野で長年にわたり利用されている定番のライブラリです。特徴点検出においては、SIFT (Scale-Invariant Feature Transform) やORB (Oriented FAST and Rotated BRIEF) といった、実績のある手法を提供しています。これらの手法は、様々な条件下で安定した特徴点検出を可能にし、幅広いアプリケーションで利用されています。

import cv2
import numpy as np

def detect_and_match_features(img1, img2):
    # SIFTの初期化
    sift = cv2.SIFT_create()

    # 特徴点の検出とディスクリプタの計算
    kp1, des1 = sift.detectAndCompute(img1, None)
    kp2, des2 = sift.detectAndCompute(img2, None)

    # BFMatcherによるマッチング
    bf = cv2.BFMatcher()
    matches = bf.knnMatch(des1, des2, k=2)

    # Loweの比率テストでマッチングをフィルタリング
    good_matches = []
    for m, n in matches:
        if m.distance < 0.75 * n.distance:
            good_matches.append(m)

    return kp1, kp2, good_matches

# 画像の読み込みと特徴点検出
img1 = cv2.imread('image1.jpg', cv2.IMREAD_GRAYSCALE)
img2 = cv2.imread('image2.jpg', cv2.IMREAD_GRAYSCALE)
kp1, kp2, good_matches = detect_and_match_features(img1, img2)


特徴量にはいろいろ種類があります。比較はこちらでやっていますので、是非ご覧ください。

深層学習ベースの特徴点検出

近年では、深層学習を用いた特徴点検出・マッチング手法も登場し、目覚ましい成果を上げています。SuperPointとSuperGlueはその代表例で、従来手法と比較して、より安定した特徴点検出、より正確なマッチング、極端な視点変化への対応といった利点があります。これらは、深層学習モデルが画像の特徴をより高次元的に捉えることができるためです。GPUを活用することで高速な処理も可能です。ただし、計算リソースを多く必要とし、実装もやや複雑になるため、一般的な用途ではSIFTやORBで十分な場合が多いです。状況に応じて使い分けることが重要です。

マーカーベース3D認識

特徴点ベースの手法に対して、画像中に特定のマーカーを配置し、そのマーカーを検出することで、より安定した3D位置推定を行う手法があります。この手法は、環境に既知のパターンを導入することで、特徴点検出の曖昧さを解消し、高精度な位置推定を可能にします。

OpenCV ArUco - 環境への導入が容易なマーカーシステム

ArUcoマーカーは、OpenCVでサポートされているマーカーシステムです。OpenCVに組み込まれているため導入が容易で、高速な検出、複数マーカーの同時検出、カメラキャリブレーションとの親和性が高いといった特徴があります。

以下はArUcoマーカーを生成するコードです。

import cv2
import numpy as np

def generate_aruco_marker(marker_id=1, size=200):
    # マーカー辞書の指定(6X6_250を使用)
    dictionary = cv2.aruco.getPredefinedDictionary(cv2.aruco.DICT_6X6_250)

    # マーカー画像の生成
    marker_image = np.zeros((size, size), dtype=np.uint8)
    marker_image = cv2.aruco.drawMarker(dictionary, marker_id, size, marker_image, 1)

    # マーカーの周りに白い余白を追加
    margin = 20
    final_image = np.ones((size + 2*margin, size + 2*margin), dtype=np.uint8) * 255
    final_image[margin:margin+size, margin:margin+size] = marker_image

    return final_image

# マーカーの生成と保存
marker = generate_aruco_marker(marker_id=1, size=200)
cv2.imwrite('aruco_marker.png', marker)

このコードでは、6x6ピクセルの内部パターンを持つArUcoマーカーを生成し、マーカーの周りに余白を追加しています。余白を追加することで、印刷時の認識性能を向上させることができます。

以下は、ArUcoマーカーを用いてカメラの姿勢を推定するコード例です。

import cv2
import numpy as np

def detect_aruco_markers(image):
    # 辞書は、マーカー生成時のものと一致させる必要あり
    dictionary = cv2.aruco.getPredefinedDictionary(aruco.DICT_6X6_250)
    parameters = cv2.aruco.DetectorParameters()

    # マーカーの検出
    corners, ids, rejected = cv2.aruco.detectMarkers(
        image, dictionary, parameters=parameters
    )

    if ids is not None:
        # カメラパラメータ(キャリブレーション済みと仮定)
        camera_matrix = np.array([[fx, 0, cx],
                                [0, fy, cy],
                                [0, 0, 1]])
        dist_coeffs = np.zeros((4,1))  # 歪み係数

        # マーカーサイズ(メートル単位)
        marker_size = 0.05

        # 各マーカーの姿勢を推定
        rvecs, tvecs, _ = cv2.aruco.estimatePoseSingleMarkers(
            corners, marker_size, camera_matrix, dist_coeffs
        )

        return corners, ids, rvecs, tvecs

    return corners, ids, None, None

# カメラからの画像取得と処理
cap = cv2.VideoCapture(0)
ret, frame = cap.read()
if ret:
    corners, ids, rvecs, tvecs = detect_aruco_markers(frame)

    # 検出結果の描画
    if ids is not None:
        cv2.aruco.drawDetectedMarkers(frame, corners, ids)

        # 座標軸の描画
        for i in range(len(ids)):
            cv2.drawFrameAxes(frame, camera_matrix, dist_coeffs,
                            rvecs[i], tvecs[i], 0.03)

このコードは、カメラから取得した画像に対してArUcoマーカーの検出を行い、検出されたマーカーの3次元位置と姿勢(回転ベクトル rvecs と並進ベクトル tvecs で表現)を推定します。cv2.drawFrameAxes 関数を使うことで、推定された姿勢に基づいて座標軸を描画し、結果を視覚的に確認できます。

実装上の注意点:

  • 辞書セル数(6X6の部分)が多い(目安として、5x5以上)ほど誤り訂正が強くなり、安定した認識が可能になりますが、遠距離だと認識が難しくなり、また、常に相対的には高解像度のカメラを必要とします。
  • 辞書サイズ(250の部分)についても、大きいほど多くのIDを持たせることができるものの、誤り訂正が弱くなるというトレードオフがあります。
  • tvecsrvecsは、それぞれカメラから見たマーカーの座標になります。また、そのままでは使いにくいので、これらの値を4x4の変換行列にする関数を以下のように定義して利用するとよいでしょう。
    def params_to_transform(tvec, rvec):
    transform = np.eye(4)
    transform[:3, :3] = cv2.Rodrigues(rvec)[0]
    transform[:3, 3] = tvec.flatten()
    return transform

私の経験に基づく話ですが、チェッカーボードによるキャリブレーションの代わりに、ArUcoを並べたボードを使うことがあります。精度はチェッカーボードによるものの方が高いのですが、特にカメラの配置が並行ではないときに、チェッカーボードだと交点の順番を認識しにくいのに対し、ArUcoだと認識できるというメリットがあります。

AprilTag - 高精度な位置推定を可能にする代替マーカー

ArUcoマーカーの代替として、AprilTagというマーカーシステムも存在します。AprilTagは、ArUcoよりも高い検出精度(特にマーカーが小さい場合や遠い場合)を持ち、偽陽性が極めて少ないという特徴があります。ROS (Robot Operating System) での実績も豊富です。ただし、AprilTagはOpenCVには標準で組み込まれていないため、利用には別途ライブラリの導入が必要になります。

3D再構成

2D画像からの3D情報抽出や、複数の画像間の対応関係を利用して、3次元モデルを再構成する技術は、3次元データ処理の中核をなす要素の一つです。このセクションでは、Structure from Motion (SfM) と深層学習による3D形状推定という2つの主要なアプローチと、それらを支えるツールを紹介します。

COLMAP - 複数画像からの高精度な3Dモデル生成

COLMAPは、Structure from Motion (SfM) と Multi-View Stereo (MVS) という技術を用いて、複数の写真から3次元モデルを生成するツールです。SfMは、異なる視点から撮影された複数の画像間の対応点を利用して、カメラの位置と姿勢、そしてシーンの3次元構造を同時に推定する技術です。MVSは、SfMで推定されたカメラパラメータを基に、より密な3次元点群を生成する技術です。COLMAPは、これらの技術を組み合わせることで、オープンソースでありながら、非常に柔軟な設定が可能なソフトウェアになっています。ただし、入力画像がいまいちだと、正しく処理できないことが多く、撮影方法はそれなりに注意が必要です。

COLMAPの主な特徴:

  • 豊富なパラメータ設定: 詳細なパラメータ調整が可能で、様々な撮影条件や要求精度に対応できます。
  • GUIとコマンドラインの両方をサポート: GUIで対話的に操作することも、コマンドラインでバッチ処理を行うことも可能です。

実践的なTips:

  • 撮影時のコツとして、被写体の周りを30度ずつ移動しながら撮影し、各写真で60-80%程度のオーバーラップを確保すると、良好な結果が得られやすくなります。可能な限り固定焦点レンズを使用し、ISO感度は低めに設定してノイズを抑制することも重要です。光沢のある表面や透明な物体は避けるようにしてください。これらの工夫により、特徴点の検出とマッチングが安定し、より正確な3次元モデルを生成できます。
  • 再構成が途中で失敗する場合は、特徴点が少ない写真を除外してみてください。テクスチャが荒い場合は、露出過度な写真を除外すると改善する場合があります。スケールが不安定な場合は、基線長(カメラ間の距離)を適度に確保するように撮影してください。メモリ不足の場合は、--PatchMatchStereo.max_image_size 2000 のようにオプションを指定して、処理する画像のサイズを制限することを検討してください。

PyTorch3D - 深層学習による3D処理の最前線

PyTorch3Dは、Facebook AI Research (FAIR) が開発した、深層学習を用いた3D処理のためのライブラリです。3D深層学習の研究開発を加速させることを目的としており、効率的なGPU処理、豊富な3D操作関数、微分可能なレンダラーといった特徴があります。これらの特徴により、PyTorch3Dは、従来の3次元処理手法と深層学習を組み合わせた、最先端の研究開発を強力にサポートします。かなり応用的な用途で使用するものになります。

PyTorch3Dは以下のような用途で活用できます。

  • 3D物体認識・セグメンテーション:PointNet++やDGCNNなどの点群ベースモデル、Mesh R-CNNなどのメッシュベースモデルの実装、3D物体検出のための損失関数の実装などが可能です。
  • 3D形状生成:VAE (Variational Autoencoder) やGAN (Generative Adversarial Network) を用いた3D形状生成、単眼画像からの3D形状推定、テクスチャ合成などに応用できます。
  • 微分可能なレンダリング: 3Dメッシュの最適化やニューラルレンダリング、逆レンダリングといった問題に取り組むことができます。

実装上の注意点:

  • メモリ効率:特にメッシュ処理を行う際は、バッチサイズを小さめに設定することが重要です。点群は必要に応じてダウンサンプリングし、メッシュの面数も適度に削減することを検討してください。GPUメモリの使用量を常に監視し、必要に応じてモデルやデータのサイズを調整することが、安定した学習と推論の鍵となります。

3Dデータ処理

3次元データは、主に点群(ポイントクラウド)とメッシュという形式で表現されます。点群は3次元空間内の点の集合であり、LiDARセンサーやRGB-Dカメラなどから直接取得できるデータ形式です。一方、メッシュは頂点、辺、面で構成される多面体であり、3Dモデリングソフトウェアや3Dスキャナなどで生成されるデータ形式です。このセクションでは、点群処理とメッシュ処理に有効なツールを紹介します。

Open3D - 点群処理の包括的な機能を提供

Open3Dは、3D点群処理のための包括的な機能を提供するライブラリです。高速な実装と使いやすいAPIが特徴で、C++で実装されたコア部分をPythonから利用できるため、処理速度と使いやすさを両立しています。研究開発から実用的なアプリケーションまで、幅広い用途で活用されています。

Open3Dの主な機能:

  • 点群の読み込み・保存: 様々なファイル形式に対応しています。
  • 法線推定: 点群の各点における法線ベクトルを推定します。
  • セグメンテーション: 点群を意味のある領域に分割します。
  • ダウンサンプリング: 点群の密度を調整します。
  • 近傍点探索(k近傍法=kNN、半径探索=rNN): 各点の近傍点を効率的に探索します。
  • 3次元データの可視化。

以下は、Open3Dを用いた点群処理の基本的な例です。

import open3d as o3d
import numpy as np

# ノイズを含む球面上の点群を生成
def create_noisy_sphere(n_points=1000, radius=1.0, noise_std=0.1):
    # 球面上の点を生成
    theta = np.random.uniform(0, 2*np.pi, n_points)
    phi = np.random.uniform(0, np.pi, n_points)

    x = radius * np.sin(phi) * np.cos(theta)
    y = radius * np.sin(phi) * np.sin(theta)
    z = radius * np.cos(phi)

    # ノイズを追加
    points = np.vstack((x, y, z)).T + np.random.normal(0, noise_std, (n_points, 3))

    # Open3D点群オブジェクトを作成
    pcd = o3d.geometry.PointCloud()
    pcd.points = o3d.utility.Vector3dVector(points)
    return pcd

# 点群の処理例
def process_point_cloud(pcd):
    # 法線の推定
    pcd.estimate_normals(search_param=o3d.geometry.KDTreeSearchParamHybrid(
        radius=0.1, max_nn=30))

    # ダウンサンプリング(ボクセルグリッド)
    downsampled = pcd.voxel_down_sample(voxel_size=0.05)

    # 統計的外れ値除去
    cleaned, _ = downsampled.remove_statistical_outlier(
        nb_neighbors=20, std_ratio=2.0)

    return cleaned

# k近傍点の探索と処理
def process_knn(pcd, k=5):
    # KDTreeの構築
    pcd_tree = o3d.geometry.KDTreeFlann(pcd)

    # 点群をNumPy配列として取得
    points = np.asarray(pcd.points)

    # 各点について処理
    for i in range(len(points)):
        # k近傍点を探索
        # 戻り値: (成功/失敗, インデックスのリスト, 距離のリスト)
        _, idx, dist = pcd_tree.search_knn_vector_3d(pcd.points[i], k)

        # 近傍点の重心を計算
        neighbors = points[idx[1:]]  # 自分自身を除く
        centroid = np.mean(neighbors, axis=0)

        # 近傍点との平均距離を計算
        mean_dist = np.mean(np.sqrt(dist[1:]))  # 自分自身を除く

        # ここで近傍点の情報を使った処理を追加できます
        # 例:局所的な特徴量の計算、スムージング、など

# 使用例
pcd = create_noisy_sphere()
cleaned_pcd = process_point_cloud(pcd)
process_knn(cleaned_pcd)

実践的なTips:

  • 法線推定のパラメータradius は法線推定に用いる近傍点の範囲を決定します。大きすぎると細部が失われ、小さすぎるとノイズの影響を受けやすくなります。max_nn は考慮する近傍点の最大数を指定します。通常は30-50程度が適切ですが、点群の密度に応じて調整してください。適切なパラメータ設定は、後続の処理(セグメンテーション、特徴量記述など)の精度に大きく影響します。
  • 近傍点探索:KDTreeは構築に時間がかかるため、繰り返し使用する場合は再利用するようにしましょう。k近傍法(kNN)と半径探索(rNN)は用途に応じて使い分けます。kNNは点の密度が不均一な場合に有効で、rNNは局所的な形状特徴の計算に適しています(ただし、取得される点の数が不定になるため扱いにくい場合があります)。
  • メモリ効率:大規模な点群を扱う場合は、必要に応じてダウンサンプリングを行い、処理済みの中間データは適宜削除するなど、メモリ使用量に注意してください。

可視化・レンダリング

3次元データの処理結果を視覚的に確認したり、プレゼンテーション用に可視化したりすることは、3次元処理において不可欠な要素です。効果的な可視化は、データの理解を深め、新たな発見を促すだけでなく、研究成果の発表やアプリケーションのデモンストレーションにおいても重要な役割を果たします。このセクションでは、3Dデータの可視化・レンダリングに役立つツールを紹介します。

CloudCompare - 出力した点群ファイルを簡単に見たいときに

フリーの点群の可視化、加工ソフトウェアです。GUIで、点群のノイズ除去、切り抜き、回転・平行移動、法線計算、メッシュ作成など、一通りのことができます。点群の処理メインなので、メッシュの処理を本格的にGUIでやりたい場合には、MeshLabもお勧めできます。どちらもフリーソフトです。

Open3D - シンプルながらも強力な可視化機能

Open3Dは、点群処理だけでなく、インタラクティブな可視化機能も提供しています。シンプルなAPIで基本的な可視化機能を網羅しており、点群とメッシュの両方に対応しています。また、オフスクリーンレンダリング(ウィンドウを表示せずに画像ファイル等に出力するレンダリング)もサポートしているため、バッチ処理で大量のデータを可視化する際にも活用できます。Depthセンサーなどで取得した点群データを手軽に可視化・確認したい場合にも非常に便利です。

以下は、Open3Dを用いた可視化の基本的な例です:

import open3d as o3d
import numpy as np

def visualize_point_cloud_with_features():
    # ノイズを含む球面上の点群を生成
    pcd = create_noisy_sphere()

    # 法線の推定
    pcd.estimate_normals(search_param=o3d.geometry.KDTreeSearchParamHybrid(
        radius=0.1, max_nn=30))

    # 点群の色付け(法線方向に基づく)
    normals = np.asarray(pcd.normals)
    colors = (normals + 1) / 2  # 法線を[0, 1]の範囲にスケーリング
    pcd.colors = o3d.utility.Vector3dVector(colors)

    # 座標軸の作成
    coordinate_frame = o3d.geometry.TriangleMesh.create_coordinate_frame(
        size=0.5, origin=[0, 0, 0])

    # ビジュアライザの初期化
    vis = o3d.visualization.Visualizer()
    vis.create_window()

    # レンダリングオプションの設定
    opt = vis.get_render_option()
    opt.background_color = np.asarray([0.1, 0.1, 0.1])  # 暗めの背景色
    opt.point_size = 2.0  # 点のサイズを大きめに

    # ジオメトリの追加
    vis.add_geometry(pcd)
    vis.add_geometry(coordinate_frame)

    # カメラパラメータの設定
    ctr = vis.get_view_control()
    ctr.set_zoom(0.8)
    ctr.set_front([1, -0.5, -0.5])
    ctr.set_lookat([0, 0, 0])
    ctr.set_up([0, 0, 1])

    # 画像として保存
    vis.capture_screen_image("point_cloud_visualization.png")

    # インタラクティブな表示(必要な場合)
    vis.run()
    vis.destroy_window()

# 可視化の実行
visualize_point_cloud_with_features()

PyVista - VTKベースの高度な科学的可視化

PyVistaは、VTK (Visualization Toolkit) をベースにした科学的可視化ライブラリです。VTKは、3次元グラフィックス、画像処理、可視化のためのオープンソースのソフトウェアシステムであり、長年にわたる実績と豊富な機能を備えています。PyVistaは、VTKの機能をPythonからより簡単に利用できるように設計されており、研究者やエンジニアにとって強力なツールとなっています。

PyVista の特徴:

  • 豊富な可視化オプション:等値面、断面、ストリームライン、カラーマップの詳細なカスタマイズ、マルチビューレイアウトなど、多彩な可視化手法を提供します。これにより、データの様々な側面を多角的に捉えることができます。
  • 科学データの可視化に最適:構造化/非構造化グリッド、スカラー場・ベクトル場、時系列データなど、科学技術計算で扱う様々なデータ形式に対応しています。
  • Jupyter連携が優秀:Jupyter NotebookやJupyterLab上でインタラクティブな3D表示、ウィジェットによる操作、プログラマブルなアニメーションなどを実現できます。これにより、データの探索的な分析や結果の共有が容易になります。

以下は、PyVistaを用いた3D可視化の例です。

import pyvista as pv
import numpy as np

# プロッターの初期化(1行2列のサブプロット)
plotter = pv.Plotter(shape=(1, 2), off_screen=True, window_size=[1200, 400])

# 3次元スカラー場の生成
x, y, z = np.mgrid[-5:5:100j, -5:5:100j, -5:5:100j]
scalar_field = np.sin(np.sqrt(x**2 + y**2 + z**2))
grid = pv.StructuredGrid(x, y, z)
grid["values"] = scalar_field.flatten()

# 左側のサブプロットに等値面を表示
plotter.subplot(0, 0)
plotter.add_mesh(grid.contour([0.2]), opacity=0.5,
                cmap="viridis", show_scalar_bar=True)
plotter.add_text("Isosurface at value = 0.2", position="upper_edge")

# 右側のサブプロットに直交スライスを表示
plotter.subplot(0, 1)
plotter.add_mesh(grid.slice_orthogonal(), cmap="viridis")
plotter.add_text("Orthogonal Slices", position="upper_edge")

# カメラ位置の設定
plotter.view_isometric()
plotter.camera.zoom(1.5)

# 画像として保存
plotter.savefig("pyvista_visualization.png")

このコードは、NumPyで生成した3次元スカラー場をPyVistaで可視化する例です。等値面(特定の値を持つ面)と直交スライス(座標軸に垂直な断面)を表示し、結果を画像ファイルとして保存しています。加工したデータを表示するのに適しています。

Webブラウザでの3次元データ表示

Webブラウザ上で3次元データを表示したい場合は、Three.jsというJavaScript/WebGLベースのライブラリが有力な選択肢となります。Three.jsは軽量で、Webアプリケーションへの組み込みに適しています。Webベースのアプリケーションやインタラクティブなコンテンツを作成する際に、強力なツールとなります。

まとめ

本記事では、3次元処理のワークフロー(2D画像からの3D理解、3D再構成、3Dデータ処理、可視化・レンダリング)に沿って、各ステップで活用できるPythonツールを紹介しました。各ツールにはそれぞれ特徴があり、用途に応じて使い分けることで、効率的な開発が可能になります。

これらのツールを適材適所で活用することで、効率的な3次元処理パイプラインを構築し、様々な応用分野で3次元データの活用を推進することができます。例えば、自動運転におけるLiDARデータの処理、ロボットビジョンにおける物体認識、AR/VRコンテンツの作成、医療画像の解析など、幅広い分野でこれらの技術が活用されています。

本記事が、3次元処理に関わる皆様の一助となれば幸いです。

お気軽にご相談ください!

弊社のデジタル技術を活用し、貴社のDXをサポートします。

基本的な設計や実装支援はもちろん、機械学習・データ分析・3D計算などの高度な技術が求められる場面でも、最適なソリューションをご提案します。

初回相談は無料で受け付けております。困っていること、是非お聞かせください。