Factor graph

**ファクタグラフ(factor graph)**は、SLAM問題を記述するための標準的な現代的手法である。これは、2種類のノードを持つ二部グラフ G=(V,F,E)\mathcal{G} = (\mathcal{V}, \mathcal{F}, \mathcal{E}) である。

このグラフは、すべての変数の結合確率が局所的なファクタの積にどのように分解されるかを符号化する。

p(V)fFf(Vf)p(\mathcal{V}) \propto \prod_{f \in \mathcal{F}} f(\mathcal{V}_f)

ここで Vf\mathcal{V}_f はファクタ ff に接続された変数である。ガウス雑音のもとでは、各ファクタは何らかの残差関数 rf\mathbf{r}_f に対して f(Vf)exp(12rf(Vf)Σf2)f(\mathcal{V}_f) \propto \exp\left(-\tfrac{1}{2}\|\mathbf{r}_f(\mathcal{V}_f)\|^2_{\Sigma_f}\right) という形を持つ。したがって、この積の負の対数を取ると、MAP推定はこのレベルの他項目で扱っているまさにその疎な非線形最小二乗問題に帰着する — 各ファクタは1つの二乗された、共分散で重み付けされた残差項になる。

おもちゃのSLAMファクタグラフ

3つの姿勢、2つのランドマーク。

 prior
   |
  x0 ---odom--- x1 ---odom--- x2
   \           /  \           /
    \         /    \         /
    proj   proj    proj   proj
      \     /        \     /
       [l0]           [l1]

ファクタのリストは、x0x_0に対する1つの事前分布ファクタ(ゲージを固定する)、連続する姿勢間の2つのオドメトリファクタ、そして1つの姿勢を1つのランドマークに結びつける4つの投影ファクタである。疎性を読み取るのは容易である。x0x_0x2x_2と直接相互作用することはなく、l0l_0l1l_1と相互作用することはない — ヘッセ行列にはそこにゼロブロックがある。ここで、ロボットが後のある姿勢でx0x_0の近くに戻ってきたと想像してみよう。その姿勢とx0x_0の間のループ閉じ込みファクタは追加のエッジ1本にすぎず、推定器の仕組みは変わらない。

一般的なファクタの種類

ファクタ接続対象残差の内容
事前分布(Prior)1つの変数固定された事前値からの偏差
Between/オドメトリ2つの姿勢相対姿勢観測における誤差
投影(再投影)姿勢+ランドマークピクセル誤差 zπ(TX)\mathbf{z} - \pi(T\mathbf{X})
IMUプリインテグレーション姿勢+速度+バイアスプリインテグレーションされた相対運動の誤差
ループ閉じ込み2つの非連続な姿勢認識された相対姿勢における誤差
GPS/絶対位置1つの姿勢計測位置からの偏差

この表現の強力さは、グラフ構造がそのまま疎性構造になるという点にある。各観測は1つの姿勢と1つのランドマークのみに関与し、オドメトリは連続する姿勢のみを接続する。グラフはこの局所性を明示的にし、その結果得られるヤコビ行列/ヘッセ行列の疎性こそが、ソルバーが数千の姿勢と数十万のランドマークを持つ問題を扱えるようにするものである。また、この表現は素晴らしく構成的でもある。センサーを追加することは、推定器を再設計することではなく、新しいファクタの種類を追加することを意味する。ポーズグラフは、すべての変数が姿勢であり、すべてのファクタが相対姿勢制約であるという特殊な場合にすぎない。

コードで見る

主要なライブラリはこの言葉を直接話す。

上記のおもちゃのグラフを、GTSAMのPython APIで書くと次のようになる。

import gtsam

graph = gtsam.NonlinearFactorGraph()
graph.add(gtsam.PriorFactorPose2(0, gtsam.Pose2(0, 0, 0), prior_noise))
graph.add(gtsam.BetweenFactorPose2(0, 1, gtsam.Pose2(1, 0, 0), odom_noise))
graph.add(gtsam.BetweenFactorPose2(1, 2, gtsam.Pose2(1, 0, 0), odom_noise))
# ... projection / bearing-range factors for landmarks ...
result = gtsam.LevenbergMarquardtOptimizer(graph, initial_values).optimize()

コードそのものがグラフである。addの呼び出しの一つ一つがファクタノードであり、整数のキー一つ一つが変数ノードである。

SLAMにおける意義

ファクタグラフは、かつては別々の問題設定として扱われていたもの — フィルタリング、ポーズグラフ最適化、バンドル調整、センサーフュージョン — を1つの図に統合した。すなわち、変数を定義し、ファクタを接続し、解く、というものである。あなたが出会うあらゆる現代的なバックエンド(ORB-SLAMのBA、VINS-Monoのスライディングウィンドウ、KimeraのiSAM2スマザー、LIO-SAMのLiDAR慣性グラフ)は、特定の変数、ファクタ、解法スケジュールの選択を持つファクタグラフである。あるシステムのファクタグラフを描くことを学ぶことは、そのSLAM論文のバックエンドを理解する最も速い方法である。

ハンズオン

関連ノート