360度画像AIアノテーション完全実践ガイド~分割・逆変換・重複排除・自動可視化のリアル~

360度画像AIアノテーションの完全ガイド

360度カメラ(equirectangular画像)を使ったAIアノテーションは、普通の2D画像解析とは本質的に異なります
なぜなら、360度画像は投影歪みが強く、また1物体が複数パッチにまたがるため、単純な矩形やラベルだけでは「位置がズレる」「重複する」などの問題が多発します。
ここでは、現場実装レベルで
・パッチ分割
・推論座標の360度画像逆変換
・重複物体のマージ
・HTML可視化
まで、要点とコードをフル網羅で解説します。

1. なぜ360度画像アノテーションは特殊なのか

  • equirectangular画像は上下端や端部で歪みが大きく、通常の矩形アノテが通用しない
  • YOLOやSAMなど通常物体検出AIをそのまま使うと、認識精度もアノテ精度も大幅に落ちる
  • 1物体が複数方向から撮られる=複数パッチで「多重検出」が必ず起こる
結論:
パッチ分割→個別推論→逆変換→マージ(重複排除)という段階を踏むのが“唯一まともな現実解”です。

2. パッチ分割(equirectangular→perspective)

2-1. パッチ分割設計

  • 水平方向(yaw): 0, 45, 90, …, 315度
  • 垂直方向(pitch): -60, -30, 0, 30, 60度
  • FOV(視野角)は90度が定番
  • 隣り合うパッチで必ず重複(オーバーラップ)する設計

2-2. Python実装例

import cv2
import py360convert
import os
import itertools

input_file = "scene_360.jpg"
output_dir = "patches"
os.makedirs(output_dir, exist_ok=True)
fov_deg = 90
out_hw = (640, 640)

yaw_list = list(range(0, 360, 45))  # 8方向
pitch_list = [-60, -30, 0, 30, 60]  # 5段

img_360 = cv2.imread(input_file)

for u_deg, v_deg in itertools.product(yaw_list, pitch_list):
    persp_img = py360convert.e2p(
        img_360,
        fov_deg=fov_deg,
        u_deg=u_deg,
        v_deg=v_deg,
        out_hw=out_hw
    )
    fname = f"patch_yaw{u_deg}_pitch{v_deg}.jpg"
    cv2.imwrite(os.path.join(output_dir, fname), persp_img)

3. パッチ画像で物体検出 → アノテーションtxt生成

各パッチ画像に対して、物体検出を行い、YOLO形式(txt)で結果を保存します。この処理は弊社製ツールを利用しました。
例:patch_yaw90_pitch0.jpg → patch_yaw90_pitch0.txt


クラス x_center y_center width height
(例)0 0.512 0.440 0.20 0.12

4. 検出点の「360度画像座標」への逆変換

4-1. なぜ逆変換が必要か

YOLO等は「分割したパッチ内のピクセル座標」で検出点を返しますが、360度画像にマークを描くには元のequirectangular座標に戻す必要があるためです。

4-2. p2e関数(パッチ→360画像座標 逆変換)

import numpy as np
def p2e(x, y, fov_deg, u_deg, v_deg, w_p, h_p, w_e, h_e):
    nx = (x / w_p - 0.5) * 2
    ny = (y / h_p - 0.5) * 2
    fov = np.deg2rad(fov_deg)
    z = 1 / np.tan(fov / 2)
    vec = np.stack([nx, -ny, -z * np.ones_like(nx)], axis=-1)
    vec = vec / np.linalg.norm(vec, axis=-1, keepdims=True)
    yaw = np.deg2rad(u_deg)
    pitch = np.deg2rad(v_deg)
    Ryaw = np.array([
        [np.cos(yaw), 0, np.sin(yaw)],
        [0, 1, 0],
        [-np.sin(yaw), 0, np.cos(yaw)]
    ])
    Rpitch = np.array([
        [1, 0, 0],
        [0, np.cos(pitch), -np.sin(pitch)],
        [0, np.sin(pitch), np.cos(pitch)]
    ])
    R = Ryaw @ Rpitch
    vec_rot = vec @ R.T
    theta = np.arctan2(vec_rot[..., 0], -vec_rot[..., 2])
    phi = np.arcsin(vec_rot[..., 1])
    x_e = (theta / (2 * np.pi) + 0.5) * w_e
    y_e = (0.5 - phi / np.pi) * h_e
    return x_e, y_e

5. 逆変換→検出点を360度画像へマーク

  1. パッチ名(yaw, pitch)から中心方位を取得
  2. txtから(x_center, y_center, class)を取得
  3. YOLOの相対値をピクセル値に変換→p2eでequirectangular座標へ
  4. 360画像上にマークを描画(cv2.circleなど)

for patch_path in glob.glob('patches/patch_yaw*_pitch*.jpg'):
    # yaw, pitch取得省略
    txt_path = patch_path.replace('.jpg', '.txt')
    # 読み込み省略
    x_p = float(x_center) * patch_hw[0]
    y_p = float(y_center) * patch_hw[1]
    x_e, y_e = p2e(x_p, y_p, fov_deg, yaw, pitch, patch_hw[0], patch_hw[1], w_e, h_e)
    all_points.append((x_e, y_e, int(class_id)))

6. 重複物体の自動マージ(DBSCANクラスタリング)

単純な距離しきい値では「ゴミ」や「重複=分割画像で同じ対象物を違った角度から計算してしまい、多重に中心点を生成してしまう」が多く残るため、DBSCANによるクラスタリングが現場での正攻法です。

DBSCANによる重複排除ロジック


from sklearn.cluster import DBSCAN
def merge_points_dbscan(points, eps=30, min_samples=2):
    if not points:
        return []
    arr = np.array([[x, y, class_id] for x, y, class_id in points])
    merged = []
    for class_id in np.unique(arr[:,2]):
        arr_c = arr[arr[:,2]==class_id]
        coords = arr_c[:,:2]
        if len(coords) == 0:
            continue
        clustering = DBSCAN(eps=eps, min_samples=min_samples).fit(coords)
        labels = clustering.labels_
        for lbl in set(labels):
            if lbl == -1:
                continue
            idxs = np.where(labels == lbl)[0]
            cx, cy = np.mean(coords[idxs], axis=0)
            merged.append((cx, cy, int(class_id)))
    return merged

7. 統合スクリプト(フルバージョン)

import cv2
import numpy as np
import glob
import re
import os
from sklearn.cluster import DBSCAN

# p2e関数・merge_points_dbscan関数は上記参照

base_img = 'scene_360.jpg'
patch_dir = 'patches'
fov_deg = 90
patch_hw = (640, 640)
output_img = 'scene_360_annotated.jpg'
eps = 30
min_samples = 2

img_360 = cv2.imread(base_img)
h_e, w_e = img_360.shape[:2]
pattern = re.compile(r'patch_yaw(-?\d+)_pitch(-?\d+).jpg')
all_points = []

for patch_path in glob.glob(os.path.join(patch_dir, 'patch_yaw*_pitch*.jpg')):
    m = pattern.search(patch_path)
    if not m:
        continue
    yaw, pitch = int(m.group(1)), int(m.group(2))
    txt_path = patch_path.replace('.jpg', '.txt')
    if not os.path.exists(txt_path):
        continue
    with open(txt_path, 'r') as f:
        for line in f:
            vals = line.strip().split()
            if len(vals) < 5:
                continue
            class_id, x_center, y_center, w, h = vals
            x_p = float(x_center) * patch_hw[0]
            y_p = float(y_center) * patch_hw[1]
            x_e, y_e = p2e(
                x_p, y_p,
                fov_deg, yaw, pitch,
                patch_hw[0], patch_hw[1],
                w_e, h_e
            )
            all_points.append((x_e, y_e, int(class_id)))

print(f"全パッチ合計検出点数: {len(all_points)}")

merged_points = merge_points_dbscan(all_points, eps=eps, min_samples=min_samples)
print(f"クラスタ代表点(=物体数): {len(merged_points)}")

for x_e, y_e, class_id in merged_points:
    center = (int(round(x_e)), int(round(y_e)))
    color = (0, 0, 255)
    cv2.circle(img_360, center, 10, color, 2)

cv2.imwrite(output_img, img_360)
print(f"{output_img} に保存しました。")

このスクリプトを実行することで、360度画像上に「重複排除済み」の物体アノテーションが描画されます。
scikit-learn(DBSCANのため)もインストールしてください。

8. 応用:色分けや矩形アノテーション、Web可視化

  • クラス番号ごとに色分けする(cv2.circleのcolor引数を辞書等で切り替え)
  • 矩形も出したい場合は四隅(xmin,ymin/xmax,ymax)全て逆変換→cv2.rectangle等で描画
  • 結果JSON化し、Pannellumなど360度ビューアのhotspot情報に流し込む

9. よくある疑問とトラブルシューティング

  • Q. 点が多すぎる/消えない!
    DBSCAN epsやmin_samplesを上げてみてください。それでも駄目な場合は検出精度そのものやFOV/分割間隔を見直してください。
  • Q. なぜこの手順が必要?
    360度画像は投影上“歪む”ため、どんなAIも「普通に矩形アノテするだけ」では正しく検出・可視化できないため。
  • Q. py360convertにp2eがない!
    このページの自作p2e関数で十分実用可能です。
総まとめ:360度画像のAIアノテは、「パッチ分割→推論→逆変換→DBSCANマージ」が実用現場の鉄板パターンのような気がします。
時間軸を考えれば、動画等にも応用も可能なので、カスタマイズ・相談はいつでもどうぞ。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA