第11章

特徴点対応からVO・SLAMへ進む

特徴点を対応付ける実習と総合課題を通して、マーカーによる自己位置推定とVO・SLAMの違いを学びます。

この章で学ぶこと

  • 自己位置推定、Visual Odometry、SLAMの役割を区別する
  • ORB特徴点の対応候補を可視化する
  • 単眼の尺度の曖昧さ、ドリフト、再訪検出を説明する
  • 物体検出と自己位置推定の総合実験を行う

1. マーカーがない場所ではどうするか

前章では、既知の大きさと位置を持つマーカーを基準にしました。環境中の模様や角を使う場合は、複数の画像で同じ場所を見つけ、カメラの動きを推定するところから始めます。

方法 推定するもの 基準・特徴
固定マーカーによる自己位置推定 既知の世界座標での位置・姿勢 基準マーカーが必要
Visual Odometry(VO) 画像列からのカメラの相対運動 小さな誤差の累積でずれやすい
Visual SLAM 自己位置と環境の地図 地図との対応や再訪による補正も扱う

本教材で実装したのはマーカー基準の自己位置推定です。この章の特徴点実習はSLAMの入口であり、対応点を描くだけではVOやSLAMにはなりません。

2. ORB特徴点を対応付ける

カメラで、模様のある本や箱を含む静止した場面を撮ります。少し横へ移動して、十分に重なる範囲をもう一度撮り、view1.jpg と view2.jpg を保存してください。第6章の撮影機能でも用意できます。

python feature_matching.py view1.jpg view2.jpg --output matches.png

特徴点は位置を再発見しやすい局所的な模様です。ORBでその周辺を記述し、Hamming距離で記述が近い候補を探します。教材は相互に最良の候補になった組を使い、距離の小さい順に最大60組を線で結びます。OpenCVの特徴点マッチング資料

orb = cv2.ORB_create(nfeatures=1500)
key1, desc1 = orb.detectAndCompute(first, None)
key2, desc2 = orb.detectAndCompute(second, None)
matcher = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)
matches = matcher.match(desc1, desc2)

似た模様が繰り返す場所や、動いている物体には誤対応が起こります。相互一致を確認しても、正しい幾何学的対応だとは限りません。白い壁では特徴点そのものが足りない場合もあります。

3. 対応点から運動推定へ

校正済みカメラで、静止した立体的な場面を異なる位置から撮った場合、対応点から本質行列を求め、相対回転と並進の方向を推定する方法があります。OpenCVでは findEssentialMat、recoverPose などが入口になります。外れ値を含む対応から頑健に推定するため、RANSACなどの処理も必要です。OpenCVの3次元復元API

ただし、通常の単眼2視点だけでは並進のメートル単位の大きさが決まりません。出てきた並進ベクトルを毎フレーム1mの移動として足してはいけません。純粋な回転、奥行きの変化が乏しい場面、少ない視差、動体の多い場面などでも推定が不安定になります。

VOでは画像列の運動をつなぐため、誤差が積み重なるドリフトが生じます。SLAMでは地図を維持し、以前訪れた場所を認識するループ閉じ込みや、複数の観測を使った最適化で整合性を改善します。メートル単位の尺度を得るには、既知の長さ、ステレオの基線長、深度、適切に統合した慣性計測などの情報が必要です。

発展的な実装として、ORB-SLAM3の公式リポジトリに単眼・ステレオ・RGB-D・Visual-Inertialの例があります。別の依存環境とセンサー設定が必要なため、導入時は公式手順とサンプルデータから始めてください。本教材のPython環境にインストールするだけで動くものではありません。

4. 総合課題:何がどこにあり、自分はどこにいるか

机にYOLOで認識できるコップや本を置き、その付近へID 0のマーカーを固定します。校正したカメラで、物体とマーカーが同時に写る動画 scene.mp4 を保存します。校正と同じ解像度・画角を使い、途中でマーカーを動かさないでください。

python yolo_detect.py --source scene.mp4 --max-frames 0 --output scene-objects
python marker_localization.py --source scene.mp4 --calibration calibration.npz --marker-m 0.080 --max-frames 0 --output scene-camera

マーカーの寸法は自分の値に変更します。同じ保存動画を使うことで、両方のJSONLの frame が同じ画像を指します。別々にライブカメラを実行したログでは、フレーム番号だけで時刻を対応させられません。

両方のログを結合する例です。次を combine_logs.py として保存して実行します。

import json
from itertools import zip_longest

with open("scene-objects/detections.jsonl", encoding="utf-8") as objects, \
     open("scene-camera/poses.jsonl", encoding="utf-8") as poses, \
     open("scene-summary.jsonl", "w", encoding="utf-8") as output:
    for detection_line, pose_line in zip_longest(objects, poses):
        if detection_line is None or pose_line is None:
            raise ValueError("処理フレーム数が違います")
        detection = json.loads(detection_line)
        pose = json.loads(pose_line)
        if detection["frame"] != pose["frame"]:
            raise ValueError("フレーム番号が一致しません")
        output.write(json.dumps({
            "frame": detection["frame"],
            "objects": detection["detections"],
            "relations_2d": detection["relations"],
            "camera_pose": pose,
        }) + "\n")
python combine_logs.py

結果には、物体の種類と画像内の関係、カメラの位置・向きが入ります。物体自体の3次元位置はまだ入りません。 そこまで求めるには、物体上の対応点の奥行きや、既知の床面との交点など、画像の点を3次元へ戻す情報が必要です。

達成を確認する

課題 提出・記録するもの
物体検出 枠付き画像、検出クラス、見逃しと誤検出の例
位置関係 左右・上下・重なりのログと、人の観察との比較
校正 カメラ設定、パターン実寸、各画像の誤差
自己位置推定 座標軸と単位、既知移動量との誤差、見失い時の挙動
特徴点対応 対応線の画像と誤対応の例

最後に、YOLOの枠だけで距離が分からない理由、固定マーカー方式とSLAMの違い、マーカーを見失った後に推定を継続するために必要な仕組みを、自分の実験結果を使って説明してください。