位置は分かっているが向きが分からない固定カメラ(道路・気象・観光の公共カメラ、工場の定点カメラ)の (yaw, pitch, roll) を、写真そのものから、学習なしで決める層です。向きが決まると、写真を地図・DEM・3D 都市に置ける(逆に、向きが無いと置けない —— 公開されているカメラ一覧の多くは位置だけで、向きは道路の増減方向や手校正しかありません)。
手掛かりは 2 つで、どちらも閉形式に近い:
7 op / 3 カテゴリ(numpy + scipy。台帳は opsgeocam.py、実体は geocam.py):
sun_position / sun_pixel_position / camera_orientation_from_sun: 時刻 → 太陽の方位・仰角(屈折込み)、写真 → 太陽の画素、太陽の画素 ≥ 2 点 → 姿勢。dem_skyline / skyline_extract / render_skyline_view: DEM の 1 点 × 全方位の地平線仰角、写真 → 列ごとの境界の行、姿勢 → その向きの空のマスク(重ね描き・合成)。camera_orientation_from_skyline: 境界 + DEM スカイライン → 姿勢 + yaw の残差曲線 + 曖昧さ。世界 = 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"])
sun_position: 春分の正午に仰角 = 90 − |
緯度 | 、夏至の正午に赤道で 90 − 23.44°、方位が東 → 南 → 西と単調、赤緯は ±23.44° 以内。 |
_rotation → _pose_from_rotation が 1e−9 で戻る。画面の上の画素は仰角が高く、yaw 90° で光軸は東。render_skyline_view した空のマスクに雑音を足し、skyline_extract → camera_orientation_from_skyline で yaw / pitch / roll が 0.1° 以内に戻る。camera_orientation_from_sun が 0.05° 以内に戻る。1 点・共線(同じ時刻)は ValueError。ambiguous=True(黙って 1 つを返さない)。ambiguous を必ず読む。sun_pixel_position が ValueError。太陽より明るい人工光源がある夜景では使えない。earth_curvature=True が既定)。fx = W / (2 tan(fov/2)))。K の誤りは pitch と roll に化ける。合成では sun_pixel_position で足りたが、実写の道路カメラでは局名の白い文字・標識・白い車が飽和して見た目では太陽と区別できない(300 局の探針で 24/24 が誤検出)。太陽は円盤でなく大きなブルームで、画像の上端や文字の帯で切れる。そこで 2 op を足した:
sun_bloom_fit —— 最大の飽和塊の切れていない縁に円を当てて中心を返す(重心は切れた側の反対へ偏る)。太陽とは決めない。camera_orientation_from_sun_candidates —— フレームごとの候補(太陽も車も混在)から、時刻どおりに動く 1 本を RANSAC で選び、道路カメラの事前知識で非物理な仮説を捨て、焦点距離も探索する。1 時間の低仰角の弧では yaw は強く決まり、roll と焦点距離は弱い(loo_px と n_inliers を見る)。