fullseye

固定カメラの向きを写真から決める(太陽とスカイライン) — 使い方ガイド

この族は何をする道具箱か

位置は分かっているが向きが分からない固定カメラ(道路・気象・観光の公共カメラ、工場の定点カメラ)の (yaw, pitch, roll) を、写真そのものから、学習なしで決める層です。向きが決まると、写真を地図・DEM・3D 都市に置ける(逆に、向きが無いと置けない —— 公開されているカメラ一覧の多くは位置だけで、向きは道路の増減方向や手校正しかありません)。

手掛かりは 2 つで、どちらも閉形式に近い:

7 op / 3 カテゴリ(numpy + scipy。台帳は opsgeocam.py、実体は geocam.py):

規約(ここが姿勢の定義)

世界 = ENU(東・北・上)、方位は北 0° 時計回り、仰角は水平 0°。カメラ = OpenCV(右・下・前)、K = (fx, fy, cx, cy)。yaw = 光軸の方位、pitch = 光軸の仰角(上が正)、roll = 光軸まわり(正でカメラが右に傾く)。R_wc = Rz(−yaw)·Rx(pitch)·Ry(roll)·B。DEM は行 0 が北端。太陽の方位・仰角も同じ規約なので、2 経路の答えはそのまま比べられます。

パイプライン

flowchart LR
    T[時刻 + 緯度経度] --> SP[sun_position]
    I[写真] --> SPX[sun_pixel_position]
    SPX --> OS[camera_orientation_from_sun]
    SP --> OS
    D[DEM] --> DS[dem_skyline]
    I --> SE[skyline_extract]
    DS --> OK[camera_orientation_from_skyline]
    SE --> OK
    OK --> RV[render_skyline_view]
    OS --> RV
    RV --> OV[写真に重ねて目で確かめる]

走る例

import numpy as np
import fullseye as fs

dem = ...                                   # (H, W) 標高 [m]、行 0 が北端
K = (280.0, 280.0, 160.0, 120.0)            # fx, fy, cx, cy
sky = fs.ledger.dem_skyline(dem, 30.0, (150, 150), eye_height=5.0, az_step_deg=0.5)
rows = fs.ledger.skyline_extract(photo)     # (W,) 列ごとの空と地形の境界の行
est = fs.ledger.camera_orientation_from_skyline(rows, K, sky)
print(est["yaw_deg"], est["pitch_deg"], est["roll_deg"], est["ambiguous"], est["margin_deg"])

# 同じカメラを太陽で検算(同じ日の朝と夕の 2 枚があれば足りる)
uv = np.vstack([fs.ledger.sun_pixel_position(morning), fs.ledger.sun_pixel_position(evening)])
est2 = fs.ledger.camera_orientation_from_sun(uv, [t_morning, t_evening], lat, lon, K)
sky_mask = fs.ledger.render_skyline_view(sky, K, photo.shape, est["yaw_deg"], est["pitch_deg"], est["roll_deg"])

真値で確かめてある性質(tests/test_geocam.py)

罠(honest)

実写で分かったこと(2026-09-21、Fintraffic 天候カメラ)

合成では sun_pixel_position で足りたが、実写の道路カメラでは局名の白い文字・標識・白い車が飽和して見た目では太陽と区別できない(300 局の探針で 24/24 が誤検出)。太陽は円盤でなく大きなブルームで、画像の上端や文字の帯で切れる。そこで 2 op を足した: