Site cover image

Site icon imageKeishu Blog

点群、深層学習等の技術で試してみたこと、調べたことを書き留めようと思います!

🌷三次元点群の位置合わせ

はじめに

概略

  • 位置合わせ、レジストレーションなど周辺概念について調べた
  • open3d を用いて実装し、花卉の三次元点群を位置合わせした

背景

点群の位置合わせ

点群の位置合わせとは、二つの点群に対して、対応する点を推定する処理のこと

具体的な手法としては、反復最近接点 (ICP: Iterative Closest Point) アルゴリズムがよく用いられる。ICPアルゴリズムは、2つの点群の間の剛体変換(回転と並進)を求めるための手法で、これにより一方の点群を他方の点群に位置合わせする。

ICPの欠点

  • データ同士の座標がある程度近くなければならない
  • 誤差収束まで反復するため、場合によっては処理時間が長くなる
  • あくまでも近似なため、厳密解ではない

前処理

  • ボクセルグリッドにより点群密度を均一にする
  • RANSACによる平面検出

レジストレーション

レジストレーションとは、複数の点群データをひとつの点群データに統合する作業のこと。

レジストレーションは位置合わせと同義かと思っており、登録という意味のregistrationという単語がなぜ使われているのかと思っていたが納得。

やり方

Image in a image block
  • 上のようなストックを対象として、2回三次元計測をした
Image in a image block
  • 初期ではこれほどズレがあった

モジュール

import open3d as o3d
import numpy as np
import copy
import csv
import os
import matplotlib.pyplot as plt

大まかな流れ

def main(inputfile,input_accurate):

    pcd_tar = o3d.io.read_point_cloud( f"model/{inputfile}" )
    pcd_acc = o3d.io.read_point_cloud( f"model/{input_accurate}")

    voxel_size = 0.01
    source_down, source_fpfh = preprocess_point_cloud(pcd_tar, voxel_size)
    target_down, target_fpfh = preprocess_point_cloud(pcd_acc, voxel_size)
    print("finish preprocess")


    result_ransac = execute_global_registration(source_down, target_down,
                                            source_fpfh, target_fpfh,
                                            voxel_size,True)
    print("finish ransac")
    
    
    global_reg_pcd = copy.deepcopy(pcd_tar).transform(result_ransac.transformation)


    print("start local reg")
    source_down, source_fpfh = preprocess_point_cloud(global_reg_pcd, voxel_size)
    target_down, target_fpfh = preprocess_point_cloud(pcd_acc, voxel_size)
    
    
    threshold = 0.02
    result= execute_local_registration(source_down,target_down,threshold)

    trans_reg = result.transformation
    pcd_reg = copy.deepcopy(global_reg_pcd).transform(trans_reg)
    
    print("finish local reg")

  1. 前処理
  2. ransacを用いたグローバルな位置合わせ
  3. 前処理
  4. icpアルゴリズムを用いたローカルな位置合わせ

前処理

def preprocess_point_cloud(pcd, voxel_size ):
    print(":: Downsample with a voxel size %.3f." % voxel_size)
    pcd_down = pcd.voxel_down_sample(voxel_size)

    radius_normal = voxel_size * 2
    print(":: Estimate normal with search radius %.3f." % radius_normal)
    pcd_down.estimate_normals(
        o3d.geometry.KDTreeSearchParamHybrid(radius=radius_normal, max_nn=30))

    radius_feature = voxel_size * 10
    print(":: Compute FPFH feature with search radius %.3f." % radius_feature)
    pcd_fpfh = o3d.pipelines.registration.compute_fpfh_feature(
        pcd_down,
        o3d.geometry.KDTreeSearchParamHybrid(radius=radius_feature, max_nn=100))
    return pcd_down, pcd_fpfh
  1. ダウンサンプリングをして密度を揃える
  2. 法線を計算
  3. FPFH特徴量の計算

グローバルな位置合わせ

def execute_global_registration(source_down, target_down, source_fpfh,
                                target_fpfh, voxel_size,flag):

    distance_threshold = voxel_size * 1.5
    print(":: RANSAC registration on downsampled point clouds.")
    print("   Since the downsampling voxel size is %.3f," % voxel_size)
    print("   we use a liberal distance threshold %.3f." % distance_threshold)
    result = o3d.pipelines.registration.registration_ransac_based_on_feature_matching(
        source_down, target_down, source_fpfh, target_fpfh, True,
        distance_threshold,
        o3d.pipelines.registration.TransformationEstimationPointToPoint(flag),
        3, [
            o3d.pipelines.registration.CorrespondenceCheckerBasedOnEdgeLength(
                0.9),
            o3d.pipelines.registration.CorrespondenceCheckerBasedOnDistance(
                distance_threshold)
        ], o3d.pipelines.registration.RANSACConvergenceCriteria(100000, 0.999))
    return result

ローカルな位置合わせ

def execute_local_registration(pcd_tar,pcd_acc,threshold):
   # ICPによる位置合わせ
    trans_init = np.identity(4)
    obj_func = o3d.pipelines.registration.TransformationEstimationPointToPoint()
    result = o3d.pipelines.registration.registration_icp( pcd_tar, pcd_acc,
                                                        threshold,
                                                        trans_init,
                                                        obj_func
                                                        )
    print("finish icp")
 
    return result

結果

グローバルな位置合わせの後

Image in a image block

RMSE of all inlier

0.006518986586785577

低いほど精度が良い

ローカルな位置合わせの後

Image in a image block

RMSE of all inlier

0.006247441569559895

まとめ

  • open3d を用いて点群の位置合わせを実装できた
  • RANSACを用いたレジストレーションでも十分なほど位置が合うが、それに加えICPを用いた方がより精度が良いことが確認できた

今後

  • 細かいパラメータについて追う
  • 撮影範囲が異なるもののレジストレーションを行ってみる

参考