fullseye

言語 / Language: 日本語 · English

自動運転のデモは動くので、誰も正しさを測らない ―― 定理と第 2 実装で採点する PoC シリーズ

Hybrid A* の道を辿って 2 台の間に入る車

↑ 車(橙)が Hybrid A の道を辿って 2 台の間に入る。前進 30 区間・後退 12 区間、切替 8 回。入ったことは見れば分かる。この道が下界からどれだけ離れているかは、見ても分からない。*

この記事について

車が縦列駐車に入る。車線を見つけて追従する。前の車との距離を保つ。自動運転のデモは動きます。そして動くものは、合っているかを誰も確かめません。

経路が最短でなくても車は駐車枡に入ります。衝突までの時間を 2 割多く見積もっても、たいていの場面では止まれます。車線の曲率が 1 割ずれていても、映像の上では線に沿って見えます。間違いが動きに出ない——これがこの分野を書くときの難所です。

そこで自動運転のコードを足すときの基準を 3 つに絞りました。

(i) 定理が門になるか、(ii) 閉形式の構造を仮定しない第 2 実装が真値になるか、(iii) 公表値(データセットの公式指標)があるか。 どれも満たさないものは書かない。使った式で答え合わせをするのは禁止。

このシリーズは、その基準で作った op を 1 本ずつ並べます。毎回、動く図と一緒に成績表が出ます。学習済みモデルは使いません——numpy と scipy だけで、どの数字も手元で再現できます。コードは fullseye にあり、図はすべてスクリプト自身の出力です。

シリーズの現在地

回 何を測るか 門(真値の出どころ)
1 車は最短でどう曲がるか Dubins / Reeds–Shepp の定理 / 前進積分 / SLSQP の第 2 実装 / 空の格子で Hybrid A* = 閉形式
2 教習所が開校する —— 真値を持った世界を先に作る 規格寸法の閉形式の面積 / 平面へのレイの閉形式 / 2 センサ 1 世界の恒等式 / 多角形の内外判定の 2 実装
3 衝突までの時間と安全距離 —— τ 理論の光学流と RSS の閉形式を、教習所の世界の真値で採点する 深度像 + 剛体運動の閉形式(τ)/ 1 コマの恒等式 / RSS の公表パラメータ・試験値 / Lemma 2 = 最悪ケースの積分
4 世界を広げる —— 閉形式の地形と世界座標の材質、手続きの木と歩行者で、拡大の中心を流れから取り戻す Perlin の定理(格子点 0・周期・解析導関数)/ fBm のスペクトル β = 2H + 2 / 点-線分距離の eikonal / 発散定理の体積 / 描いた深度を世界へ戻す恒等式 / 純並進の流れは FoE から放射状(真の流れ = 運動)
5 車に慣性と坂を —— 空走 + 制動で停止線の手前に止まり、坂道で止まって逆行せずに発進する 停止距離の閉形式 / RSS の停止距離(第 2 実装)/ 坂道発進のずり下がりの閉形式 / エネルギー収支 / 警察庁 丙運発第 12 号の減点細目
6 太陽と天気 —— 朝日の逆光で信号が読めない時間帯、霧の中で見えてから止まれる速さ、雨の路面、夜の前照灯 国立天文台 暦計算室の公表値 / 影 = h cot(高度) / 光幕と色度の閾値の閉形式 / Koschmieder の法則 / 道路構造令の解説の停止距離(第 2 実装)/ 保安基準の前照灯の性能
7 終わらない地図 —— 区画を車の周りに作り足し、離れた区画は捨てて、50 km を途切れずに走る 継ぎ目の両側の一致 / 作り直しの指紋(SHA-256)/ 有理数で足し直した位置 / (2r + 1)² の上限
8 動く交通と死角 —— 路肩駐車の陰の子ども、対向車とのすれ違い、バス停、下手な運転、横断歩道で待つ人 死角の視線の閉形式 / IDM の平衡車間 / ∫λ と Adams の式 / 付けた癖と意図(真値)/ 教則の再現台帳
9 判断の場面 —— ミラーで左後方を確かめる、歩行者信号から黄を予測する、救急車に道を譲る、バスの発車を待つ 鏡面の Fermat の点 / 鏡の縁の光線の多角形 / 信号の時間割 / GHM と停止線の閉形式 / 発音時刻の幾何 / 40 条・31 条の 2
10 横の運動 —— カーブの手前で落とす、車線の中を保つ、左に寄って左折する、内輪差で巻き込まない 摩擦円と道路構造令 / 2 自由度の定常の横ずれ / 内輪差の閉形式 / 教則の寄り方 / 標本の門(0.1 % 点と KS)
11 踏切と交差点の優先 —— 直前で止まって左右を見る、警報の間は入らない、向こう側が詰まっていれば入らない、広い道へは譲る 鉄道の解釈基準の時刻 / 遮断機の状態機械 / 見通しの三角形 / 36・38・44・50 条の判定 / 1 mm 格子の禁止区間
12 追越しと見えない所 —— 見通しが足りなければ待つ、ルームミラーに映ってから戻る、環道の車を妨げない、カーブミラーは遠く見える 条文の総当たりの禁止区間 / 時間の行進 / 構造令の視距の表 / 凸面鏡の 3 次元光線追跡と Coddington の式
13 横断歩道を渡るヒューマノイド —— 車の物理は正確なまま、歩く側は安く描く 毎コマ接地と支持脚の滑り / 間引きのずれ ≤ √3·格子幅 / 直接の描画との一致度 / 手前の壁で隠れる

第 1 回: 車は最短でどう曲がるか

手順

2 つの姿勢 (x, y, θ) → 相対座標に直す → 語ごとの区間長を閉形式で → 各候補を前進積分して終点を検証 → 最短を選ぶ
障害物あり: 占有格子の上で {左・直進・右} × {前進・後退} を A* で繋ぎ、節点から目標へ閉形式の一撃を試す

前輪で舵を切る車には最小回転半径 ρ があり、それより急には曲がれません。姿勢 (x, y, θ) から姿勢 (x’, y’, θ’) へ移る最短の道は、この制約のもとで定理として分かっています。

言葉 かみくだくと
Dubins(1957) 前進だけの車の最短路は「円弧・直線・円弧」の 3 区間で、並べ方(語)は LSL / RSR / LSR / RSL / RLR / LRL の 6 つしかない
Reeds–Shepp(1990) 後退を許すと最短路は高々 5 区間、語は 48(Sussmann–Tang 1991 で 46 に減る)。区間長は閉形式
語(word) 区間の並び。L = 左いっぱい、S = 直進、R = 右いっぱい。小文字は後退
占有格子 地面を升目に切って「障害物か否か」を置いた画像
Hybrid A*(Dolgov ら 2010) 連続な姿勢を離散セル (x, y, θ) で枝刈りしながら A* を回す。障害物があるときの実用解
許容ヒューリスティック 真の残り費用を超えない見積り。障害物を無視した Reeds–Shepp 長がそれ
解析的な一撃 探索の途中で「ここから目標まで閉形式の道が衝突せずに通るか」を試すこと。通れば探索は終わる

op は 3 本です。

閉形式の op は、候補の一本一本を前進積分して終点に着くかを検証し、届かない候補は捨てて n_rejected に数えます。式の写し間違いは例外ではなく数に出ます。

Dubins の 6 語

↑ 同じ 2 姿勢に対する 6 語。この対では 5 語が目標に届き、いちばん短いものが答え。定理は「答えはこの 6 つの中にある」と言っているだけなので、6 つ全部を作って測る。

門と成績

真値に使ったのは定理・第 2 実装・下界の 3 種類だけです。学習済みモデルも外部のプランナーも要りません。

主張 実測 真値の出どころ
閉形式の候補は全部目標に着く 乱数 400 対で落ちた候補 0、対あたり RS 候補 6.6 本(3〜12) 前進積分
48 語の式が全部生きている 18 の語族が候補にも最短にも全部現れた 語族を数える
閉形式は本当に最短 3 対 × RS / Dubins の 6 件で第 2 実装と 1e-6 で一致 語ごとの区間長を SLSQP で解く(閉形式の構造は仮定しない)
定理の不等式と対称 距離 ≤ RS ≤ Dubins、可逆、鏡映、剛体、ρ 比例、三角不等式が 400 対 + 200 組で全部成立 定理
後退が買う分 Dubins − RS の最大 10.647 m(ρ = 1 m) ―
整列した目標 Dubins 5.000000 = RS 5.000000、語 S。真後ろ 2 m は RS 2.000000、語 s 距離が下界
Hybrid A* は閉形式を再現する 障害物の無い格子で費用 23.353319 m = RS 23.353319 m(展開 1、一撃)。前進のみでも Dubins 長と一致 閉形式
縦列駐車 費用 16.018 m ≥ 下界 7.986 m、展開 3,385、区間 42(後退 12・切替 8)、0.7 秒。姿勢列は車体で衝突せず、|Δθ| ≤ Δs/ρ 下界と運動学
塞げば拒む 壁で車室を塞ぐと ValueError。部分的な道は返さない fail-closed
第 2 実装の中身は、語の 5 区間の長さを未知数 l₁…l₅(符号つき、負 = 後退)にして、終点の 3 つの方程式(x, y, そして角は 2 sin(Δθ/2))を制約に Σ lᵢ を最小化するものです。多数の初期値から SLSQP で解いて最小を取ります。閉形式が何も知らない実装なので、一致すれば閉形式の写し間違いも語の漏れも同時に否定できます。

Reeds–Shepp の 16 目標

↑ 原点から 16 の目標へ。赤 = 前進、青 = 後退、灰 = 同じ目標への Dubins(前進のみ)。小文字が後退の区間。後退を許すと Dubins より必ず短いか等しい。

縦列駐車の探索木

↑ 縦列駐車(18 × 8 m、cell 0.25 m、θ 72 分割、車体 4.5 × 1.8 m、ρ 5.5 m、7.5 m の車室)。灰 = 障害物(縁石・駐車車両 2 台)、薄緑の点 = 展開した姿勢、赤 = 前進、青 = 後退。障害物を無視した Reeds–Shepp の道(下界 7.986 m)は駐車車両を突き抜けるので、費用 16.018 m はその 2 倍。差の内訳は「障害物のぶん」と「枝刈りの近似のぶん」で、この 2 つは分離できません(後述)。

★ 実装すると、ここで間違える

この筋道を自分で書く人が確実に踏む穴が 4 つあります。どれも例外は出ず、車はそれらしく走るので、門を置かないと気づけません。

1. mod 2π の折り方が違うと、8 つの語が一度も出てこない

Reeds–Shepp の 44 式は OMPL の実装から写すのが普通です。OMPL の mod2pi は (−π, π] に折ります。これを [0, 2π) に折る実装で写すと、区間長 t の条件 t ≥ 0 が常に真になり、CCSC 系の 8 語(LRSL・LRSR とその対称)が一度も候補に出ません。負の区間長は 2π − t の遠回りに化けるので、答えは出るし、目標にも着きます——ただ最短ではありません。

これは「候補が目標に着くか」の門では見つかりません。着いているからです。語族を数える門——48 語を 18 の族に分け、乱数の姿勢で各族が候補にも最短にも現れることを要求する——で初めて出ます。乱数 400 対で 8 族が 0 回なら、式が死んでいます。

2. 終点方程式を 4 本立てると、最適化器は収束しない

第 2 実装で終点の一致を「x、y、cos θ、sin θ」の 4 式にすると、未知数 5 に対して制約が実質 3 なのに 4 本あるので過剰決定になり、SLSQP は実行不能か inf を返します。角の一致は 1 本——2 sin((θ_end − θ_goal) / 2)——で書いてください。sin と cos の両方を等式にしてはいけません。

3. 衝突判定が時間の 8 割を食う

Hybrid A* の素朴な実装は、縦列駐車 1 回に数十秒かかります。プロファイルを取ると衝突判定が 8 割です。効くのは 3 つで、(a) 車体の矩形を標本点にして格子座標へ一括変換し、平坦な索引 flat[iy·W + ix] で引く、(b) 解析的な一撃は候補全部ではなく最短候補だけを衝突判定する、(c) 一撃の頻度を目標に近いほど上げる(残り費用 h に対して ⌈h / step⌉ 回に 1 回)。この 3 つで 37 秒が 1 秒になります。順番は (a) が先——(b)(c) は正しさを変えないぶん、効き目が測りにくいからです。

4. 「到達不能」が正しい答えのことがある

離散化は答えを変えます。車長 + 2.0 m の 6.5 m の車室は、cell 0.25 m・θ 72 分割では到達不能でした。7.5 m なら入ります。また 12 × 8 m の狭い格子で前進のみ(Dubins)の道を求めると、最短路が格子の外へ出るので到達不能が正解です。どちらも op は ValueError を投げます。部分的な道を返して「惜しかった」を装うより、拒んだほうが後段は安全です。空の格子で閉形式と厳密一致する門を持っていれば、到達不能が離散化のせいか実装のせいかを切り分けられます。

向かないこと

動く障害物と速度は扱いません。 ここにあるのは幾何(どの道か)だけで、いつ・どの速さで走るかは別の層です。歩行者や対向車がいる場面の計画は、この op の上に時間軸を足す別の道具になります。

Hybrid A* は最適ではありません。 離散セルでの枝刈りが近似を入れるので、縦列駐車の 16.018 m が「障害物のもとでの最短」である保証はありません。下界 7.986 m との差 8 m のうち、障害物のぶんと近似のぶんは分離できません。言えるのは「下界以上・衝突なし・運動学的に実現可能」の 3 つだけで、成績表もそう書いてあります。

曲率は連続ではありません。 Dubins も Reeds–Shepp も区間の境で舵を瞬時に切り替えます。実車ではクロソイド(曲率が線形に変わる曲線)で繋ぐ CC-steer や、後段の平滑化が要ります。

大きな格子は遅いです。 純 Python の heapq なので、100 × 100 m を cell 0.1 m で回す用途には向きません。研究の真値・小さな駐車場・教材の範囲です。

動かす

git clone https://github.com/furuse-kazufumi/fullseye
cd fullseye
py -3.11 examples/poc_car_parking.py            # 門 7 本と図 5 枚、約 16 秒
import numpy as np
import fullseye as fs

poses = np.array([[0.0, 0.0, 0.0],            # 始点 (x, y, θ)
                  [4.0, 3.0, np.pi / 2]])     # 目標
rs = fs.ledger.car_reeds_shepp_path(poses, radius=1.0)
print(rs["word"], round(rs["length"], 4), rs["n_candidates"], rs["n_rejected"])
# LSL 5.1763 9 0     ← 語、長さ [m]、生き残った候補、積分で落ちた候補(0 でなければ式を疑う)

occ = np.zeros((32, 72), bool)                # 8 × 18 m、cell 0.25 m
occ[:2, :] = True                              # 縁石
res = fs.ledger.car_hybrid_astar(occ, np.array([[2.0, 4.2, 0.0], [7.5, 1.5, 0.0]]),
                                 radius=5.5, cell=0.25, footprint=(4.5, 1.8, 1.0))
print(res["cost"], ">=", res["lower_bound"], res["n_expanded"])

n_rejected は必ず見てください。0 でない値が出たら、それは自分の姿勢が悪いのではなく式のどこかが死んでいる知らせです。


この回が作った図は全部で 5 枚あります —— 全部見る

この回の残りの図

真値の散布

↑ 真値 11 件の突き合わせ。第 2 実装 6 件、整列 3 件、空の格子 2 件。全部が対角線の上に乗る。

第 2 回: 教習所が開校する —— 真値を持った世界を先に作る

幹線で赤信号を待ち、クランクを抜けて周回コースへ合流する

↑ 追走カメラの画像に LiDAR(16 ビーム)の点を重ねる。灰 = 路面、黄 = 縁石、赤 = 車、緑 = 信号機、白 = 横断歩道。右上の枠は車載カメラの ROI と判定 —— red の間は停止線で止まり、green を読んだ次のコマで発進する(閉ループ)。信号待ち → 交差点 → クランク(切り返しあり)→ 連絡路 → 周回コース。車が走ったことは見れば分かる。その道が縁石線から何 cm 出たか、LiDAR の点がどの面から来たかは見ても分からない——世界の側に真値があるから数えられる。

第 1 回は道の幾何だけで、センサが無かった。センサを採点するには、何を見たかが最初から決まっている世界が要る。実写データセットの真値は人手のラベルで、箱の縁も点のラベルも人の判断だ。そこでこの回は世界を作る側に回った——ただし勝手な寸法ではなく、道路交通法施行規則 別表第三(指定自動車教習所のコースの基準、普通免許) の数字で。

手順

規格の寸法 → 課題ごとの多角形(クランク・S 字・坂道・縦列駐車・方向変換・踏切・周回・幹線) → (x, y, yaw) で配置 → 走れる領域の和
       → 3-D の世界: 路面の平面 + 縁石の帯 + 白線 + 横断歩道 + 停止線 + CC0 の車 + 日本の規格で手続き生成した信号機・標識(面ごとにラベルと色)
       → 車載カメラの画像から信号の色を読み(地図の位置 → ROI → 色度の検出)、緑を読むまで発進しない(閉ループ)
       → 回転式 LiDAR をメッシュに撃つ(点・range 画像・面のラベル) / 車載カメラで撮る(色・ラベル・深度)
       → 13 巡目の Hybrid A* で走らせ、全姿勢・全コマで採点する
用語 ここでの意味
周回コース 長円形。直線 80 m 以上・幅 8 m 以上。教習所の外周で、課題はこの内側にある
幹線コース 幅 7 m 以上の道が十字に交差し、周回と繋がる。すみ切り半径 3 m 以上
屈折コース(クランク) 幅 3.5 m、曲角間 12 m、出入口 4 m 以上、内側のすみ切り 1 m(普通免許)
曲線コース(S 字) 幅 3.5 m、半径 7.5 m(外側の弧)、弧の長さは円周の 3/8 が 2 つ逆向きに
方向変換 幅 3.5 m の道の脇に幅 3.5 m・奥行 5 m の車庫、すみ切り 1 m
坂道 幅 7 m 以上、高さ 1.5 m 以上、緩坂 6.5〜9 %・急坂 10〜12.5 %、頂上の平坦部 4 m 以上
レンジ画像 LiDAR の一掃を 行 = 仰角(ビーム)、列 = 方位角 の格子にしたもの。画素値は距離
逆センサーモデル 「この距離で反射が返った」から格子の各セルの占有を更新する規則(今回は縁石の点を落とすだけ)

新しい op は 3 つのモジュールに分かれます。drivecourse(規格寸法の 2-D 多角形。真値は閉形式の面積)、driveworld(3-D の世界。CC0 の Kenney Car Kit / City Kit Roads の車・街灯・コーンを実寸に合わせて置き、信号機と標識は roadjp(日本の規格の寸法で手続き生成: 横型 3 灯は左から青・黄・赤、交差点の向こう側の柱からアームで車線の上へ、灯器の下端 5.0 m、標識は円 600 mm・逆三角 600 mm・警戒の菱形 450 mm、板の下端 1.8 m)。カメラ像は三角形 id から色とラベルを引く)、lidarsim(Möller–Trumbore のレイ–三角形交差を、センサから見た各三角形の方位角・仰角の区間でビン分けして加速。8 万三角形 × 5.8 万レイで 1 秒未満)。

教習所の平面

↑ 真上から。周回(直線 80 m・幅 8 m・半円 R 30)の中に幹線の十字(信号 4 基、各腕に横断歩道と、その 2 m 手前の停止線)。北東にクランク、南西に S 字、南東に坂道(暗い部分が斜面)、北西に縦列駐車と方向変換、東の幹線に踏切。課題の出口は連絡路で周回へ戻るので、ぐるぐる回れる。

同じ世界を斜めから

↑ 3-D の世界。縁石(高さ 0.15 m)と白線は多角形の縁に沿って生成し、継ぎ目には置かない。車・街灯・コーンは CC0 のメッシュ、信号機(横型 3 灯、向こう側の柱からアームで車線上)と標識(止まれ・徐行・最高速度・横断歩道・踏切あり)は日本の規格の寸法で手続き生成。面ごとにラベルと色を持つ。

門と成績

真値は規格の寸法から導いた閉形式・平面と箱へのレイの閉形式・同じ世界を見る 2 つのセンサの恒等式の 3 種類。学習済みモデルも外部のシミュレータも要りません。

主張 実測 真値の出どころ
多角形は規格の寸法どおり 弧のある 5 要素の靴紐面積は、弧の点数 16 → 64 → 256 → 1024 で閉形式へ単調収束(クランク 3.1e-5 → 7.5e-9、S 字 2.8e-3 → 6.8e-7)。弧の無い 4 要素は厳密一致 閉形式(クランク = w·L + 2r²(1 − π/4)、S 字 = 2·(3/8)·π·(7.5² − 4²) + 出入口、半円 = π R w)
LiDAR は平面を正しく測る 平らな路面に当たった 10,903 点の range が h/(−sin e) と相対差 2.1e-16 閉形式
2 つのセンサは同じ世界を見ている LiDAR の 3,450 点をカメラに投影した画素の深度と点の深度: 相対差の中央値 2.9e-3、ラベル一致 100 %(1 画素の許容。ぴったりなら 99.1 %) 恒等式
車の点は車の中 ラベル「車」の 1,607 点が、置いた車の箱(姿勢 + 実寸)の内側 100 % 世界の側の配置
縁石の点は道の外 縁石の 4,667 点から作った占有格子は、真の占有(多角形の外)の 1 セル膨張の部分集合(precision 1.0000)。走行 68 コマ全部で 1.0000 多角形の内外判定(偶奇と巻き数の 2 実装)
走った道は脱輪していない 997 姿勢で車体 4 隅の縁石線からの越え幅は最大 0.117 m ≤ 半セル 0.125 m。厳密な多角形では 11 姿勢が越える(計画器の分解能) 多角形
ゼロ点 クランクの入口→出口を直線で進むと 60 姿勢中 50 で脱輪 ―
カメラは信号を読む(画像処理) 地図で分かる灯火の位置を車載カメラに投影した周り(ROI ± 24 px)を、色度の検出 op にかけて点いた円盤を探す。赤 → red、緑 → green、灯火を消すと unknown。真値の面 ID は使わない 世界の側の状態
緑を読むまで動かない(閉ループ) 停止線に着いてから毎コマ読み、green を読んだ次のコマから発進。赤の 6 コマは 1 mm も動かず、手前 10 m からの読み 11 コマは世界の状態と全コマ一致。ゼロ点: 灯火を消すと unknown が続き、8 コマ待っても発進しない 世界の側の状態

LiDAR の一掃

↑ 停止線の 10 m 手前での一掃(32 ビーム、−25°〜+15°、0.5°、10,903 点が路面)。点は当たった面のラベルで塗ってある——これが人手のラベルでなく生成時の真値であることが、この世界の値打ち。

車載カメラに LiDAR を重ねる

↑ 同じ瞬間の車載カメラ(60°、640 × 400)に LiDAR の点を投影して重ねた。点の深度と画素の深度、点のラベルと画素のラベルが一致することが「2 センサ 1 世界」の門。信号は赤。

★ 実装すると、ここで間違える

1. 規格の幅 3.5 m は、最大舵角と直進だけでは前進で通らない

第 1 回の Hybrid A* は {左・直進・右} × {前進・後退} の運動基本形です。4.5 × 1.8 m の車で規格のクランク(幅 3.5 m、すみ切り 1 m)を前進のみで通そうとすると、格子 0.25 m・θ 72 分割でも、0.125 m・144 分割でも到達不能——探索は数十回の展開で open set を使い切ります。S 字(外側 7.5 m・内側 4 m)も同じです。後退を許すと通ります(クランク 費用 57.9 m・後退 14 区間、S 字 29.5 m・後退 1 区間)。実際の検定でも切り返しは許され、回数で減点されます。中間の舵角を持つ基本形が無いと、規格ぎりぎりの道は幾何的には通れても離散化では通れない——これは「動いたから合っている」の反対で、動かなかったが間違っていない例です。

2. 計画器が「衝突なし」と言っても、多角形では 12 cm 出ている

衝突判定はセル(0.25 m)単位です。厳密な多角形で測ると 997 姿勢のうち 11 で車体の隅が縁石線を越え、最大 0.117 m でした。半セル以内なので計画器の約束どおりですが、「衝突なし」の意味は分解能の分だけ緩い。門は「半セル以内」に置き、越えた姿勢の数と最大幅を出しています。縁石に当てたくなければ clearance を足すか、格子を細かくするかで、どちらも時間を払います。

3. 要素を辺で接すると、継ぎ目に縁石の壁が立つ

周回の直線と半円を辺どうしでぴったり接すると、それぞれの端の辺は「隣の要素の内側」ではないので縁石が生成され、道を横切る壁になります。LiDAR がそれを見て、走れるはずのセルに縁石の点が落ちました(precision 0.90)。直し方は 2 つ: 隣り合う要素を 5 cm 重ねる、そして縁の小片(0.5 m)は両端と中点のどれかが隣の内側なら置かない。中点だけ見ると、口に半分かかった小片が壁になります。

4. 停止線の真横に信号を立てると、止まった車のカメラには入らない

最初は信号機を停止線の横に置いていました。停止した車の車載カメラ(前方 20 m を見る 60°)から灯火は 40° 上にあり、視野に入りません。日本の車両用灯器は交差点の向こう側(出口側)に置くのが基本で(信号機設置の指針)、そうすると停止線から 18 m 先・見上げ 9° で読めます。位置と高さは規格で決まっています —— 横型灯器は 1.5 m のアームで G.L. から 5.5 m 以上(県警の工事仕様書の標準設置姿図)、標識の板は下端 1.8 m・歩車道境界から 25 cm 以上(道路標識設置基準)。見た目で置くと、こういう所で嘘になります。

5. 細い物は 1 画素ずれると別の面に落ちる

縁石は高さ 0.15 m、20 m 先では 2 画素の帯です。LiDAR の点をカメラに投影して同じ画素のラベルを見ると 99.1 % で、外れの大半は縁石の点が隣の路面の画素に落ちたもの。3 × 3 の窓に同じラベルがあれば一致とすると 100 %。白線(幅 0.12 m)は遠くで 1 画素未満なので、路面と同じ面として数えます。「ラベル一致 100 %」と書くとき、許容が何画素かを一緒に書かないと意味がありません。

6. 巨大な三角形はカメラの背後で消える

路面を 2 枚の巨大な三角形にすると、車載カメラの像から路面ごと消えます。描画器はカメラの背後に頂点を持つ三角形を落とすからです(近クリップをしない設計)。4 m の格子に割ると、消えるのはカメラの真下の升だけになります。LiDAR 側にも似た罠があり、三角形の仰角の区間を頂点の最小・最大で取ると、センサの真上を通る辺の途中の仰角を取りこぼします(頂点 5.7° の辺の途中が 78.7°)。辺ごとに大円弧上の極値を足す必要がありました。

向かないこと

動く物と時間は扱いません。 世界は静止していて、車は姿勢列を辿るだけです。対向車の速度も歩行者もいません。信号は色が変わるだけで、変わる規則はありません。

センサの物理は幾何だけです。 LiDAR は単一反射で、反射強度・ビームの広がり・雨や霧・回転中の自車の動きによる歪みはありません。カメラは Lambert の陰影だけで、影も露出も無い。「この検出器は実車で動く」の証拠にはならず、幾何の間違いを見つける道具です。

車のメッシュは玩具の比率です。 Kenney の CC0 モデルを箱の寸法(車長 4.5 m・車幅 1.8 m)に軸ごとに引き伸ばしています(生の比との差は 1.8 倍以内)。点群の形は本物の車と違います。資産の向きは見た目で決めない: 生のモデルの最長軸・左右の鏡映対称(幅を反転すると形が自分に重なり、前後を反転すると重ならない)・灯の色(黄 = 前照灯、赤 = 尾灯)を門にしてあります。軸ごとの寸法合わせは向きを間違えると形を潰すので、倍率の最大 / 最小も門で見張っています。

縦列駐車の寸法は法令にありません。 通達の別添は図で、数値の一次情報が取れなかったので、車長 + 3.0 m を既定にして docstring にそう書いてあります。

動かす

git clone https://github.com/furuse-kazufumi/fullseye
cd fullseye
py -3.11 examples/poc_driving_school.py         # 門 14 本と図 6 枚、約 37 秒
import numpy as np
import fullseye as fs

crank = fs.ledger.course_crank()                                  # 規格: 幅 3.5, 曲角間 12, 出入口 4, すみ切り 1
P = crank["polygon"]                                             # 反時計回りの多角形 (K, 2)
area = 0.5 * abs(np.dot(P[:, 0], np.roll(P[:, 1], -1)) - np.dot(P[:, 1], np.roll(P[:, 0], -1)))   # 靴紐
print(crank["params"]["regulation"], round(area, 3), round(crank["area_closed_form"], 3))
# {'A': 3.5, 'B': 12.0, 'C': 4.0, 'D': 1.0} 82.682 82.679

world = fs.ledger.world_build(crank, props=[("sedan", 20.0, 2.0, np.pi / 2)])   # 3-D 化して車を 1 台置く
spec = fs.ledger.lidar_spec(n_beams=16, azimuth_res_deg=1.0)
T = np.eye(4); T[:3, 3] = (2.0, 0.0, 1.64)                        # 入口の中、センサ高 1.64 m
scan = fs.ledger.lidar_scan(world["V"], world["F"], spec, T, labels=world["face_label"])
hit = scan["ranges"] > 0
print(scan["n_hits"], np.bincount(scan["labels"][hit]))
# 2641 [2455  140   22    0    0    0    0    0    0   24]   ← 点の数、面のラベル別(路面 2455・縁石 140・車 22・…・白線 24)

labels は当たった面のラベルです。生成時の真値なので、検出器を書いたらこれと突き合わせれば、人手のラベルを待たずに採点できます。


この回が作った図は全部で 6 枚あります —— 全部見る

この回の残りの図

真値の散布

↑ 8 種の要素の面積(靴紐 vs 閉形式)と路面の range 200 点(実測 vs h/(−sin e))。全部が対角線の上。


第 3 回: 衝突までの時間と安全距離 —— τ 理論の光学流と RSS の閉形式を、教習所の世界の真値で採点する

対向車が来て、通り過ぎ、停車車両の前で RSS が止める

↑ 車載カメラで 0 → 10 s。対向車が来る間はその画素の光学流(矢印)と真の拡大の中心(十字)を描き、上に「τ の真値」と「流れから出した τ」を並べる。通り過ぎたあと、自車線の停車車両に RSS が「危険」を出した瞬間(t = 6.0 s)に制動し、10.25 m 手前で止まる。数字が動くのは世界の側が真値を持っているから——τ も間隔も、推定でなく生成時に決まっている。

第 2 回で世界はできたが、動く物がいなかった。この回は対向車を走らせる。問いは 2 つ: あと何秒でぶつかるか、何 m 空けるべきか。前者は Lee(1976)の τ 理論——近づく面の像が広がる速さだけで衝突までの時間が決まり、距離も速度も要らない。ハエもヒトもこれで制動する。後者は RSS(Shalev-Shwartz ら 2017)——応答時間と加減速の上限から「これより近いと責任を問われる」距離を閉形式で与える。どちらも教科書の式で、この世界なら真値と突き合わせられる。

手順

第 2 回の周回コース(南の直線 80 m、左側通行) → 自車 8 m/s(北の車線)・対向車 8 m/s(南の車線)・停車車両(自車線の先)
   → 車載カメラ 60°、640 × 400、10 Hz で 4.5 秒(46 コマ)
   → τ を 3 経路で: 真値(深度像 + 2 コマ間の剛体運動の閉形式)/ 流れから(LK → time_to_contact)/ 大きさから(面積の平方根)
   → RSS: 公表パラメータで安全距離の閉形式 → 最悪ケースを時間積分して突き合わせ → 教習所の 3 場面で判定と制動
用語 ここでの意味
τ(タウ) 衝突までの時間。Z を面までの奥行き、Ż をその変化率として τ = Z / (−Ż)。等速なら 1 秒に 1 秒ずつ減る(dτ/dt = −1)
拡大の中心(FoE) 並進するカメラで、像の流れがそこから放射状に出る点。進行方向の像
光学流 2 コマの間に各画素がどこへ動いたか(u, v)。ここでは Lucas–Kanade のピラミッド 5 段
RSS Responsibility-Sensitive Safety。「相手が最悪の行動をしても、自分が定められた応答をすれば当たらない」距離の定義と閉形式
ρ(応答時間) 危険に気づいてから制動を始めるまでの時間。その間は加速してしまうかもしれない、と最悪を見込む
a_min,brake / a_max,brake 自分が必ず出せる最小の減速度 / 相手が出しうる最大の減速度
proper response RSS が定める応答: ρ の間は a_max,accel 以下の任意、その後 a_min,brake 以上で停止まで

新しい op は 2 つのモジュールです。drivettc(深度像と剛体運動から真の流れと真の τ を画素ごとに閉形式で出す。光学流から τ を出すのは既存の time_to_contact を秒に直して束ねる)、rsssafety(RSS の同方向・対向・横方向の安全距離の閉形式、最悪ケースの時間積分、判定)。3 つの経路の τ は全部 最初のコマの時刻の τ に揃えてあります——ここに 1 コマ分の落とし穴があり、後で書きます。

t = 4 s の車載カメラと光学流

↑ t = 4 s。対向車の画素の光学流(矢印 ×3)は拡大の中心(十字)から外へ向かい、その半径方向の速さから τ = 0.84 s(真値 0.84 s)。路面には模様が無いので、流れは車と街灯にしか無い。

門と成績

真値は 深度像 + 剛体運動の閉形式(τ)、公表値(RSS のパラメータ表と試験値)、定理と積分の一致(Lemma 2)の 3 種類。

主張 実測 真値の出どころ
真の流れから τ が戻る(恒等式) 真の流れを time_to_contact に入れ 1 コマ足すと、真の τ₀ と相対差 8.5e-14(46 コマ、対向車の全画素) 閉形式
τ は 1 秒に 1 秒ずつ減る 対向車の最も近い画素の τ は「前バンパーまでの距離 / 閉じる速さ」と相対差 8.3e-3 以内、傾き −1.0045 姿勢と速度
流れから τ が出る 対向車が 150 画素以上の 22 コマで、LK の τ の相対差 中央値 0.026(90 % 点 0.30)。46 コマ全部では 0.135 閉形式
大きさから τ が出る 面積の平方根(コマ間隔 0.5 s)から、相対差 中央値 0.045(90 % 点 0.12)。量子化の許容区間 [面積 ±周長/2] に 17 / 19 コマ 閉形式
RSS の閉形式は公表値どおり ad-rss-lib の横方向の試験値 5 点と最大差 0.0065(許容 0.01)。50 km/h の同方向: 加速 0 で 39.8 m(公表 ≈ 40)、加速 4 m/s² で 83.6 m(公表「about 80」) 公表値
Lemma 2 は最悪ケースの積分と一致 v = 8 m/s で d_min = 26.28 m、そこから積分した最小間隔 4.3e-14。乱数 50 組でも |min_gap − (d₀ − d_min)| < 1e-13、衝突の有無も一致 第 2 実装
教習所で止まる 停車車両まで 26.25 m(< 26.28)で「危険」→ ρ = 1 s は速度維持、4 m/s² で制動 → 停止時の間隔 10.25 m = gap(t_b) − (vρ + v²/2b) と 1e-9 で一致 閉形式
τ は騒ぎ、RSS は騒がない 対向車線の車で τ の真値は 0.30 s まで落ちるが、横の安全距離 0.725 m < 車線の間隔 2.2 m で、RSS は一度も危険を出さない 閉形式
寄ってくれば危険 対向車が 0.6 m/s で横へ寄ると横の安全距離は 2.45 m に跳ね、t = 1 s に危険。そのとき縦の間隔 58.25 m は対向の安全距離 82.9 m の中で、最悪ケースの積分は衝突する(最小間隔 −24.7 m) 第 2 実装

τ の 4 本の曲線

↑ 真値(2 本、重なる)は 5 s から傾き −1 で落ちる。流れからの τ(水色)は t ≥ 2 s で真値に乗り、遠い区間(流れが 1 画素未満)は散る。大きさからの τ(橙)は全域で近い。

RSS が停車車両の前で止める

↑ 停車車両までの間隔(青)が安全距離(橙、v = 8 で 26.28 m)を割った t = 6.0 s に「危険」。1 秒は速度維持、その後 4 m/s² で制動。制動中は v が下がるので安全距離も下がる。

★ 実装すると、ここで間違える

1. 2 コマの流れから出る τ は「1 コマ後の τ」

τ 理論の式 τ = Z / (−Ż) は瞬間の量です。2 コマの差分で流れを取ると、像点は拡大の中心から Z₀/Z₁ 倍の位置に写るので、time_to_contact が返す「半径² / (半径方向の流れ · 半径)」は Z₁ / (Z₀ − Z₁) コマ —— 2 コマ目の時刻の τ で、最初のコマの τ より ちょうど 1 コマ小さい。10 Hz なら 0.1 秒、τ が 0.5 s のとき 20 % です。1 コマ足すと真値と 1e-13 で一致します(門 1)。等速でない場合も差は「1 コマ」なので、どの時刻の τ を名乗るかを決めて揃えれば済みます。大きさから出す τ にも同じ 1 コマの差があります(Δt·w₁/(w₁ − w₀) が最初のコマ、Δt·w₀/(w₁ − w₀) が次のコマ)。

2. 光学流の τ が効くのは、流れが 1〜十数画素のときだけ

遠い(対向車が 100 画素以下、流れが 1 画素未満)と LK の τ は 20〜70 % 外れ、近すぎる(流れが 50 画素以上)とピラミッド 5 段でも追えなくなります。中間の 22 コマでは中央値 2.6 %。コマ間隔を伸ばして流れを稼げばよさそうですが、この世界ではむしろ悪化しました(0.3 s で 4 倍以上外れる)——LK は見た目が変わらない前提で、近づく車は見た目が変わるからです。「流れの τ は測れる」は条件つきで、その条件は流れの画素数で書けます。

3. 大きさから出す τ は、面積の量子化に負ける

面積 n の平方根 ∝ 1/Z なので τ₀ = Δt / (1 − √(n₀/n₁))。1 コマ(0.1 s)では面積の増分(τ = 2 s、300 画素で 30 画素)が縁の量子化(周長 80 画素の半分)より小さく、区間が無限に開きます。コマ間隔を 0.5 s にすると 19 コマ中 17 で真値が区間に入り、外れた 2 コマは車のシルエットが平面でない分(近づくと側面が見えてくる)です。同じ 2 コマから、流れは短い間隔を、大きさは長い間隔を要る —— 両方を出すなら間隔を別に持つこと。

4. RSS の横方向は、論文の式と公開実装で符号の扱いが違う

論文の Lemma は「ρ の後も互いに向かって動いている」前提で書かれていて、その前提の外(片方がまだ離れている)で式を字義どおり使うと、離れる車の停止距離を近づく向きに足してしまいます。公開実装 ad-rss-lib は「ρ 後の速度が相手へ向くときだけ停止距離を足す」と場合分けし、さらに μ の扱い(論文 μ + [·]₊、実装 [· + μ]₊)も違います。公表の試験値は実装のものなので、こちらは実装に合わせ、前提の内側では論文と一致することを門にしました(乱数 300 組から前提を満たす 100 組超で 1e-9)。「論文の式をそのまま」と「公開実装と同じ」は別の主張です。

5. カメラの俯角ぶん、閉じる速さは v cos θ

車載カメラは少し下を向いています(1.4°)。深度は光軸に沿って測るので、対向車の閉じる速さは 16 m/s ではなく 16·cos 1.4° = 15.995 m/s。真値を「距離 / 16」で書くと 3e-4 ずれます。小さいですが、1e-9 の門は通りません。真値の式に光軸の向きを入れます。

6. 拡大の中心は、この世界では流れから推定できない

流れ全体から最小二乗で拡大の中心を推定すると、真値から中央値 62 画素ずれました。路面に模様が無く、流れが車と街灯にしか無いからです。この回は自車の運動を既知として真の拡大の中心を与えています(門にせず数字だけ出す)。実写では路面のテクスチャが拡大の中心を決めるので、世界に模様が無いことが推定を殺す——次に世界へ足すべきものが分かる失敗です。

向かないこと

自車の運動は既知です。 拡大の中心も、対向車の画素の切り出し(世界の面 id = 完全な検出器の代役)も真値から取っています。検出器と自己運動推定を載せた「知覚パイプライン全体」の採点ではなく、τ の式と RSS の式が世界と合っているかの採点です。

RSS の判定は縦と横の 2 軸だけで、交差点・優先権・遮蔽(論文の後半)は入れていません。パラメータは ad-rss-lib の「議論のための初期値」で、論文にも法にも数値はありません。

対向車は等速直線です。 減速・加速・車線変更の途中の τ(dτ/dt ≠ −1)は扱っていません。式はそのまま使えますが、門の「傾き −1」は等速の性質です。

世界はコースの外 8 m で終わり、路面は無地です。街路樹も歩行者も横断歩道もまだ無い——光学流が路面から取れない理由でもあります。

動かす

git clone https://github.com/furuse-kazufumi/fullseye
cd fullseye
py -3.11 examples/poc_ttc_rss.py         # 門 10 本と図 6 枚、約 35 秒
import numpy as np
import fullseye as fs

# τ: 深度像を 1 枚作り、8 m/s で 0.1 s 近づく(カメラ系で +z が前)
K = np.array([[34.6, 0.0, 31.5], [0.0, 34.6, 19.5], [0.0, 0.0, 1.0]])   # 64 × 40、縦 60°(τ は K に依らない)
depth = np.full((40, 64), 20.0)                                  # 20 m 先の壁
T = np.eye(4); T[2, 3] = 0.8                                     # 1 コマで 0.8 m 近づく
truth = fs.ledger.ttc_truth(depth, K, T, 0.1)
flow = fs.ledger.flow_from_depth_motion(depth, K, T)              # 真の流れ (u, v)
est = fs.ledger.ttc_from_flow(flow["u"], flow["v"], 0.1, foe=fs.ledger.foe_from_motion(K, T))
print(round(float(np.nanmedian(truth["tau"])), 6), round(est["tau"], 6))
# 2.5 2.5                                                         ← 20 m / 8 m/s。1 コマの補正込みで一致

# RSS: 自車 8 m/s、前が停車。応答時間 1 s、加速 3.5、制動 4 / 8 m/s²(ad-rss-lib の表)
p = fs.ledger.rss_params()
d_min = fs.ledger.rss_longitudinal_same(8.0, 0.0, p)
sim = fs.ledger.rss_worst_case_gap(d_min, 8.0, 0.0, p)
print(round(d_min, 3), round(sim["min_gap"], 9), sim["collided"])
# 26.281 0.0 False                                                ← 閉形式 = 積分、ちょうど 0 で当たらない

この回が作った図は全部で 6 枚あります —— 全部見る

この回の残りの図

平面

↑ 南の直線 80 m を真上から(t = 3 s)。自車(北の車線、東へ)、対向車(南の車線、西へ)、停車車両(x = 40)。

横の安全距離

↑ 車線どおりなら横の間隔 2.2 m > 横の安全距離 0.725 m で、τ(灰)が落ちても危険にならない。対向車が 0.6 m/s で寄ると安全距離が 2.45 m に跳ね、t = 1 s に危険。


第 4 回: 世界を広げる —— 閉形式の地形と世界座標の材質、手続きの木と歩行者で、拡大の中心を流れから取り戻す

起伏の中の周回コースを坂を上り下りしながら走り、路面の流れから拡大の中心を出す

↑ 車載カメラで 0 → 10 s(51 コマ、6 m/s)。矢印は Lucas–Kanade の流れ(×4)、橙の十字は流れから推定した拡大の中心、緑は真値。路面には粒と水溜りと摩耗した白線、横断歩道を歩行者が渡り、道の外は fBm の起伏と木。上の数字は勾配と FoE の誤差(全画素 / 路面だけ)。第 3 回では 62 px 外れた拡大の中心が、路面に模様が付いただけで 1〜2 px に来る——そして模様の一粒一粒が世界座標の式で決まっているから、誤差を画素で数えられる。

第 3 回の正直な但し書きは 2 つだった。拡大の中心(FoE)は「既知」にした——路面に模様が無く、流れが車と街灯にしか無いので、流れから推定すると中央値 62 px 外れたから。そして世界はコースの外 8 m で終わっていた。この回は世界を広げる。ただし広げ方に条件を付けた: 足すものは全部、生成の時に真値を持つこと。

これはこのシリーズの設計の軸で、第 2 回で世界を作る側に回ったときから決めている。合成データセット(Virtual KITTI、SYNTHIA、CARLA)もラベルと深度と流れを描画器から取るし、それは人手のラベルより厳密だ。違いは真値の種類にある。描画器が出せるのは「この画素はどの面か」「何 m 先か」で、面の高さや勾配、道からの距離、白線の摩耗率、水溜りの写り込みの強さはメッシュやテクスチャに焼かれた値であって式ではない。この回は地形の高さ・勾配・道への距離・摩耗率・反射率を閉形式の場にした。場が式なら、描いた画像から場を戻して式と突き合わせられるし、スペクトルの定理・eikonal・発散定理が門になる。Domain randomization(Tobin ら 2017)が見た目を撒いて学習器を鈍感にするのとは目的が逆で、ここは学習でなく採点のために、見た目を決める場そのものに真値を持たせる。

先行研究との違い。 着手前に手元の文献コーパス(手続き的地形・道路網・OpenDRIVE・合成運転データセット・domain randomization・判りにくい路面、OpenAlex の 1,652 本)を引きました。地形の生成器(fBm、Perlin、Weierstrass–Mandelbrot、浸食)は数多くありますが、「スペクトルの傾きとハースト指数の関係を検査に使う」と述べる抄録は無く、近いのは log–log の周期図の傾きからフラクタル次元を推定する 2006 年の論文と、指定した PSD から路面の断面を生成する 2023 年の論文でした。道路網の生成(Parish と Müller 2001 の L-system、Chen ら 2008 のテンソル場)は街路を作りますが、クロソイド・paramPoly3 を検査した不変量として挙げる抄録は無く、2022 年の OpenDRIVE の検証研究は 99 件のデータセットのおよそ 2 割に車線の隙間を見つけています——地図の幾何にはふつう門が無い、ということです。合成データセット(Virtual KITTI は検出・追跡・分割・深度・流れ、SYNTHIA は画素ラベル、GTA V から採った Playing for Data、CARLA 系の SELMA は多センサ・多天候、SynPeDS は安全性のメタデータつきの歩行者)はどれもラベルを描画器から取るので、定理と突き合わせられるものが中にありません。判りにくい路面——水溜り(偏光ステレオ 2017、反射に注目する cGAN 2019、AGSENet 2024)、摩耗した区画線、横断歩道(CDSet、3,434 枚)、遮蔽された歩行者——はほぼ全部が人手のラベルで、真値を持つ合成は 2025 年の 3D Gaussian splatting の雨と反射の描画器と 2026 年の Blender の水溜りセットだけ、摩耗率の真値つきで区画線を合成する生成器は見当たりませんでした。ここでの違いは、世界の場(高さ・勾配・道への距離・摩耗率・水溜りの反射率)が全部閉形式の関数なので、門が「描画器と合う」ではなく定理と恒等式になることです。

手順

起伏 = 乱数位相の正弦の和(fBm のスペクトル合成、H = 0.8、1,024 波、周期 6〜120 m) → 高さも勾配も閉形式
   → コースへの距離場(点-線分距離、道の外で |∇d| = 1)で、道の周り 2 m は平ら・12 m かけて起伏へ → 路面には長波長のうねり(±0.8 m)
   → 第 2 回の平らな世界に後から掛ける(路面の升を地形メッシュに差し替え、資産は姿勢の高さだけ持ち上げる)
   → 木 70 本(角柱 + 円錐 / 回転楕円体)を道から 4 m・互いに 5 m 以上に散布、横断歩道(9 縞)、歩行者(箱 + 回転体)、標識・街灯・車・コーン
   → 車載カメラで撮り、深度から画素の世界座標を戻し、そこで Perlin の雑音を評価して材質(粒・草むら・染み・水溜り・摩耗した白線)を画素ごとに置く
   → 30 fps の 2 コマから LK の流れ → 路面の画素だけで拡大の中心を最小二乗 → 真値と比べる。同じ地形・同じ木で模様だけ消した世界とも比べる
用語 ここでの意味
fBm(非整数ブラウン運動) 「どの倍率で見ても同じ荒れ方」の面。粗さをハースト指数 H(0〜1)1 つで決める。H が大きいほど滑らか
パワースペクトルと β 面を波の成分に分けたときの、周波数 f ごとの強さ。fBm なら f^{−β} の直線で、β = 2H + E(E = 次元、面なら 2。Saupe 1988)
スペクトル合成 振幅 ∝ f^{−H} の正弦波を、向きと位相を乱数にして足す作り方。和は式なので高さも勾配も閉形式
Perlin の勾配雑音 格子点に乱数の勾配ベクトルを置き、「勾配 · 格子点からの変位」を滑らかな多項式で補間する雑音(Perlin 2002)。格子点では変位が 0 なので値は必ず 0、置換表が 256 なので周期 256、補間多項式を微分すれば導関数が閉形式
距離場と eikonal 各点から道までの最短距離 d(x, y)。最寄り点が 1 つなら ∇d は最寄り点から外へ向く単位ベクトルで、|∇d| = 1 が成り立つ(eikonal 方程式)。地形はこの d で「道の近くは平ら」に混ぜる
発散定理の体積 閉じたメッシュの体積 = Σ v₀ · (v₁ × v₂) / 6。角柱と多角錐は閉形式と厳密に一致し、回転楕円体は内接なので上界を超えない
世界座標の材質 模様を画像の座標でなく世界の (x, y) で決めること。車が動いても模様は路面に貼り付いたままなので、流れが出る
均衡誤り率 2 クラスの取りこぼしと誤検出の平均。しきい値をどこに置いても下がらない値が「本当に判りにくい」の尺度

新しい op は driveterrain の 1 モジュールです(fBm・Perlin・距離場・地形・材質・木・歩行者・横断歩道・散布・体積)。FoE を流れから出す foe_from_flow は第 3 回の drivettc に足しました。

数式は 4 つだけ、どれも読める形です。(1) 起伏: h(x, y) = Σ_k A_k cos(2π f_k (x cos θ_k + y sin θ_k) + φ_k)。周波数 f_k を対数一様に撒くと 2 次元の周波数面での密度は ∝ 1/f²、振幅 A_k ∝ f^{−H} なので、パワーは A² × 密度 ∝ f^{−(2H+2)}——定理の β = 2H + 2 がそのまま出ます。勾配は項ごとに微分するだけ。(2) 地形: z = h · w(d) + うねり。w は smoothstep で、道から 2 m までは 0、そこから 12 m かけて 1。積の微分 ∇(h·w) = w∇h + h·w′(d)·∇d で、∇d は距離場の法線だから勾配も閉形式。(3) 材質: 描いた深度を逆投影して画素の世界座標 (x, y) を戻し、そこで Perlin の雑音を評価する。水溜りは雑音 > しきい値の所(ラベル 11)、摩耗率 wear ∈ [0, 1] で白線の色 = 白 · (1 − wear) + 路面 · wear。(4) FoE: 純並進なら流れは FoE から放射状(Longuet-Higgins & Prazdny 1980)なので、各画素を流れの向きに通す直線(流線)は全部 FoE を通る。FoE = Σ w_i · dist(F, 流線_i)² を最小にする点で、2 × 2 の正規方程式。重みは遠い画素ほど小さく(向きの誤差が位置に効く)、角度の残差で外れ値を落とす。

起伏の中の周回コース

↑ 世界を南西の上空から(t = 6 s)。fBm の起伏(H = 0.8、周期 6〜120 m、RMS 2.5 m の設定)は道の周り 2 m で平らになり、12 m かけて繋がる。路面には長波長のうねり(振幅 0.8 m)。木 70 本は道から 4 m 以上・互いに 5 m 以上に散布。世界はコースの外 40 m まで(第 3 回は 8 m)。

門と成績

真値は 定理(Perlin・fBm のスペクトル・eikonal・発散定理・FoE の放射)、閉形式の場(高さ・勾配・距離・摩耗率)、描いた深度を世界へ戻す恒等式 の 3 種類。学習済みモデルも外部の描画器も要りません。

主張 実測 真値の出どころ
Perlin の雑音は定理どおり 乱数 4,000 点で、格子点の |値| 最大 2.7e-15、周期 256 の誤差 9.3e-14、値域 [−0.594, 0.599]、解析的な導関数と中心差分の差 6.8e-10 定理(格子点で 0・周期・導関数)
起伏のスペクトルは 2H + 2 H = 0.8、1,024 波、周期 6〜120 m、RMS 2.70 m の 512² の面で、動径周期図の log–log の傾き β̂ = 3.591、定理 3.6(差 −0.009、12 帯、許容 0.25) Saupe 1988
コースへの距離場は eikonal 道の外 2,635 点で |∇d| の中央値 1.00000000(99 % 点 1.000000)、道の内側 335 点は d = 0 点-線分距離の閉形式
地形の恒等式 勾配の閉形式と中心差分の差 4.2e-10、メッシュ 8,625 頂点の高さ = 閉形式(差 0)、道の上の高さ = うねりだけ(差 0)。起伏は −7.91〜+9.56 m 閉形式
散布の約束 木 70 本は互いに 5.32 m 以上(約束 5 m)、道から 5.91 m 以上(約束 4 m) 距離場
体積の閉形式 針葉樹(8 角柱 + 8 角錐)の発散定理の体積 9.800076 = 閉形式 9.800076。広葉樹の冠 23.344 ≤ 楕円体 26.465(0.882 倍)、歩行者の頭 0.00513 ≤ 球 0.00565(0.907 倍)。横断歩道の面積 = 9 縞 × 0.45 × 4 = 16.20 m² 発散定理・内接の上界
描いた世界は式どおり 車載カメラ(60°、640 × 400、t = 6 s)の路面・地形・水溜り 119,575 画素で、深度から戻した高さと閉形式の差 中央値 0.4 mm、99 % 点 0.130 m(2 m 升の弦)。水溜り 1,991 画素は全部道の内側、地形 37,082 画素は全部道の外側。白線 2,624 画素の色 = 白と路面の摩耗率での混色 × 陰影(差 0) 描いた深度を世界へ戻す恒等式
判りにくい物は本当に判りにくい 摩耗した白線(明るさ 0.29〜0.54)と水溜り(0.37〜0.65、路面は 0.28)は、明るさのしきい値をどちら向きに置いても最良(θ = 0.47、「暗い方が白線」)で均衡誤り率 25 % 画素単位の真値(ラベル・wear・puddle)
FoE の定理 51 コマ全部で、真の流れから出した FoE と自車の運動から出した FoE の差 最大 6.4e-14 px。坂の上り下りで真の FoE は行 140.0〜147.5 px を動く(勾配 −1.5〜+1.5 %、頂点は t = 5 s) 放射の定理(真の流れ = 深度 + 運動)
模様が FoE を取り戻す LK(30 fps、路面の画素、流れの誤差の中央値 0.67 px)から: 模様あり・路面だけ 中央値 1.9 px(90 % 点 6.0)、全画素 1.2 px。模様なし(同じ地形・同じ木、1 s ごと 11 コマ)57.2 px 真値の FoE

同じコマで並べると差がはっきりします(t = 0, 1, …, 10 s の路面だけの誤差、px)——模様あり 2.0 / 0.7 / 1.7 / 2.4 / 4.3 / 3.1 / 25.9 / 0.6 / 1.3 / 0.6 / 1.4、模様なし 59.2 / 70.0 / 57.2 / 45.4 / 60.0 / 62.1 / 44.4 / 43.2 / 27.2 / 3.6 / 107.3。模様なしにも 1 コマ(t = 9 s)3.6 px があり、模様ありにも 1 コマ(t = 6 s)25.9 px があります。門は中央値で置いてあり、「2 倍以上」どころか 20 倍を超えました。

車載カメラの 1 コマとその真値

↑ 車載カメラ(60°、640 × 400、t = 6 s)の 1 コマと、その画素ごとの真値。(a) 色、(b) ラベル(道・地形・縁石・白線・横断歩道・水溜り・木・歩行者)、(c) 白線の摩耗率(0 = 白、1 = 路面)、(d) 真値の場(赤 = 染み、青 = 水溜り、緑 = 摩耗率)。材質は描画した深度から戻した世界座標で評価するので、ラベル・摩耗率・水溜り・染みは画素単位で厳密。摩耗した白線と水溜りは明るさのしきい値では最良でも 25 % を間違える。

流れから出した拡大の中心

↑ t = 1.2 s(路面だけの誤差が中央値に最も近い典型的なコマ)、480 × 300、30 fps の 2 コマ。路面の画素の LK の流れ(矢印 ×4)だけから拡大の中心を最小二乗で出す(橙)。真値は緑。(a) 模様のある路面では真値に 1.9 px。(b) 同じ地形・同じ木で路面を無地にすると、流れは白線と縁石の縁にしか無く、91.5 px 外れる。

★ 実装すると、ここで間違える

1. 周期図は環で「平均」する。積分すると β が 1 ずれる

2 次元の周期図を動径方向にまとめるとき、半径 f の環の中のパワーを足すと、環の面積 ∝ f が掛かって傾きが 1 だけ緩く出ます。定理の β = 2H + 2 は平均に対する値です。足してしまう実装は H = 0.8 で β̂ ≈ 2.6 を返し、「fBm になっていない」と誤診します。もう 1 つ、DC は除き、窓(Hann)を掛けること。有限の帯域と窓は傾きを急に見せる方向に偏るので、門には 0.25 の幅を持たせてあります。この実行の差は −0.009 で偏りの向きとは逆でした——1 回の実行で偏りの大きさは言えません。

2. 512² × 1,024 波を一括にすると 2 GB

正弦の和は (点 × 波) の行列を作れば 1 行で書けますが、512² の格子 × 1,024 波は倍精度で 2 GB です。点を 16,384 ずつに区切ると一時配列は 1 本 128 MB(16,384 × 1,024 × 8 B)で、時間は変わりません(512² の面で 5.4 s)。地形は「閉形式で速い」のではなく「閉形式で正確、速さは区切り方」です。

3. 描いた深度から戻した高さは、閉形式と 13 cm ずれてよい

地形メッシュは 2 m の升の三角形なので、描画した深度を逆投影した点はメッシュの弦の上にあり、閉形式の曲面の上にはありません。中央値 0.4 mm でも 99 % 点は 0.130 m で、これは升の弦の誤差です。門を「1 mm」に置くと正しい実装が落ちます。2 m 升の弦と分かっているなら、門は「中央値 < 1 cm、99 % < 15 cm」のように離散化の分だけ開けて、その理由を書きます。

4. 多角形の底面の円錐に π を使うと 10 % 合わない

針葉樹の冠は 8 角形の底面の錐なので、体積は ⅓ · (½ n R² sin(2π/n)) · h で、⅓ π R² h ではありません。n = 8 では ½ · 8 · sin(π/4) = 2.828 で π の 0.90 倍——真値の側が「円」を仮定すると、正しいメッシュが 10 % 外れます。回転楕円体は内接なのでそもそも等式にならず、「上界の 0.8〜1 倍」の門にしてあります(実測 0.882、頭は 0.907)。真値の式はメッシュの作り方と同じ離散化で書くか、不等式にするかのどちらかです。

5. 流れの 2 コマは 30 fps、記録は 5 fps

第 3 回で分かったとおり、LK の流れは 1〜十数画素のときだけ効きます。6 m/s の路面の近い画素は 0.2 s では 50 px を超えるので、流れの 2 コマは 1/30 s 離し、記録と GIF は 0.2 s 刻みにしました。この実行の流れの誤差の中央値 0.67 px は、その間隔での値です。FoE の最小二乗は遠い画素の流線を距離で割って重みを下げます——遠い画素は流れが小さく向きの誤差が大きいのに、流線が FoE の位置に一番効くからです。

向かないこと

木と歩行者は形が本物ではありません。 木は角柱と円錐・回転楕円体、歩行者は箱と回転体で、体積の門のために閉形式が要る形にしてあります。検出器の学習データとしての見た目は目的ではなく、「どの画素が木か」の真値と体積の真値が目的です。

水溜りの写り込みは空の色を混ぜただけで、鏡面反射の幾何(写る物が何か)は入っていません。反射率 refl は真値として画素ごとに残していますが、それは「どれだけ空に寄せたか」であって光学ではありません。

25 % の混同は意図した設計です。 摩耗した白線と水溜りを明るさで見分けられないように作ってあり、この数字は検出器の成績ではなく「世界が判りにくい物を持っている」証拠です。真値が画素単位で厳密だから、その間違いを数えられます。

自車は歩行者に制動しません。 歩行者は自車が横断歩道に着く前に渡り終える時刻で置いてあり、制動の判定は第 3 回の RSS の仕事です。

FoE の誤差 1〜2 px は LK の偏り込みです。 流れの誤差の中央値 0.67 px はこの世界の模様とこの窓(15 px、5 段)での値で、真値との差の内訳(LK の分と最小二乗の分)は分けていません。

動かす

git clone https://github.com/furuse-kazufumi/fullseye
cd fullseye
py -3.11 examples/poc_world_terrain.py         # 門 10 本と図 6 枚、約 46 秒
import numpy as np
import fullseye as fs

road = fs.ledger.course_road(30.0, 7.0)                      # 幅 7 m・長さ 30 m の直線(x = 0 → 30、中心線 y = 0)
tp = fs.ledger.terrain_params(1, hurst=0.8, amplitude=2.0, flat=2.0, blend=10.0, road_amp=0.5)
world = fs.ledger.world_build(road, ground_margin=30.0, ground_step=2.0)
fs.ledger.world_apply_terrain(world, tp, step=2.0)          # 平らな世界に閉形式の地形を掛ける(その場で書き換える)
mat = fs.ledger.material_params(1, grain=0.15, puddle_level=0.42, wear=0.5)
f = 160.0 / np.tan(np.radians(30.0))                         # 60° の水平視野、320 × 200(camera_intrinsics は台帳 op でないので K を直接書く)
K = np.array([[f, 0.0, 160.0], [0.0, f, 100.0], [0.0, 0.0, 1.0]])

def pose(x):                                                 # 高さは路面に追従、向きは固定(純並進)
    z = float(fs.ledger.terrain_height(np.array([[x]]), np.array([[0.0]]), tp, road)[0, 0]) + 1.35
    return fs.look_at((x, 0.0, z), (x + 20.0, 0.0, z - 0.45))   # world → camera の 4 × 4(camera_pose も台帳 op でない)

P0, P1 = pose(3.0), pose(3.2)                                # 6 m/s で 1/30 s
v0 = fs.ledger.world_camera(world, P0, K, 320, 200)
m0 = fs.ledger.world_materials(world, v0, P0, K, mat)        # 深度から戻した世界座標で材質を置く
print(int(m0["puddle"].sum()), "水溜りの画素")                # → 563 水溜りの画素(真値: ラベル 11、画素単位)

T = fs.ledger.relative_motion(P0, P1)
tru = fs.ledger.flow_from_depth_motion(v0["depth"], K, T)    # 真の流れ(深度 + 運動)
est = fs.ledger.foe_from_flow(tru["u"], tru["v"], tru["valid"] & (m0["label"] >= 0))
print(np.round(fs.ledger.foe_from_motion(K, T), 3), np.round(est["foe"], 3))   # → [160. 91.062] [160. 91.062](定理どおり、門は 1e-6)

world_materials の返り値には color のほかに label・puddle・refl・stain・wear・xyz が入っています。検出器を書いたら、これに突き合わせるだけで画素単位の成績が出ます——人手のラベルも、描画器の設定も要りません。


この回が作った図は全部で 6 枚あります —— 全部見る

この回の残りの図

起伏のスペクトル

↑ fBm の面(H = 0.8)の動径周期図は log–log で直線: 傾き β̂ = 3.59、定理(β = 2H + E、E = 2)は 3.6。帯域 [1/100, 1/10] 周期/m の 12 帯で当てはめ。

拡大の中心の誤差

↑ 拡大の中心の推定誤差(真値との距離、px)。模様のある路面の画素だけで中央値 1.9 px、全画素で 1.2 px、無地の路面(1 s ごとの点)で 57.2 px。自車は坂を上って下る(勾配 −1.5〜+1.5 %、頂点は t = 5 s)ので、真の FoE も像の中を動く。


第 5 回: 車に慣性と坂を —— 空走 + 制動で停止線の手前に止まり、坂道で止まって逆行せずに発進する

時計つきで教習所を走る

↑ 時計・速さ・停止線までの距離・ブレーキの段・車載カメラの信号の読みつきで走る。黄を読んでから空走して 2 段で制動し、停止線の手前 0.50 m に止まる。赤の間は動かず、青を読んでから発進。後半は坂道コースで一時停止と発進。左下は縦断図、右下は速度計。

第 4 回までの車は、2.5 m おきの姿勢の列で動き、停止線でいきなり止まっていました —— 時間も速さもありませんでした。この回は車に縦の運動を入れます。

手順

運動   : m dv/dt = 駆動 − 制動 − m g sin θ − c_rr m g cos θ − ½ρC_dA v|v|(止まっている間は、ブレーキで保持できる範囲なら動かない)
路面   : 坂道コースの (x, z) から、道なりの距離ごとの高さと勾配を作る(道路交通法施行規則 別表第三: 6.5〜9 %・10〜12.5 %)
停止   : 信号を読んだ瞬間の速さと距離から、反応時間の空走 → 2 段の制動 → 停止線の 0.5 m 手前で保持、を計画する
坂道   : 上り坂で一時停止して保持 → 発進(ブレーキを離してから駆動が立ち上がるまでの τ にずり下がる)
採点   : 警察庁 丙運発第 12 号(令和 4 年)の減点細目 —— 停止位置不適・逆行小/中/大・制動操作不良・発進手間どり

かみ砕き: 止まるまでの距離は 2 つに分かれます。気づいてからブレーキを踏むまでにそのままの速さで進む距離(空走距離 = 速さ × 反応時間)と、ブレーキで減速しながら進む距離(制動距離 = 速さ² ÷ (2 × 減速度))。上り坂では重力が止めるのを助け、下り坂では邪魔をします。だから同じ速さでも、下り坂のほうが止まるのに長い距離が要ります。

停止距離の閉形式と積分器

↑ 速さと停止距離。平地・上り・下りで、閉形式(線)と積分器(点)が重なる。

門と成績

# 門 真値 成績
1 停止距離 閉形式 vρ + (1/2k)ln(1 + k v²/A)、A = b ± g sin θ + c_rr g cos θ 平地・上り・下り × 20/40/60 km/h の 15 組で積分器と 8.5e-14
2 第 2 実装 別に書いた rsssafety の停止距離(平地) 閉形式 0.0、積分器 5.5e-13
3 坂の保持 最小の制動 max(0, |a_creep − g sin θ| − c_rr g cos θ) その上では動かず、1e-4 足りないと動く(4 勾配)
4 坂道発進のずり下がり 閉形式 ½a₁τ² + (a₁τ)²/(2a₂) 積分器と 1.8e-15。11 %・τ 1.0 s で 0.533 m
5 信号の読み 世界の信号の状態 読みを信じる 20 m の中で 117 / 117 一致
6 停止線 手前 0〜2 m(減点 0) 手前 0.500000 m(計画と 1e-9)
7 閉ループ 赤の間は動かない 0 mm、青を読んでから発進
8 坂道 手前 0〜2 m・逆行 < 0.3 m 手前 0.500 m、逆行 0 m
9 採点 警察庁 丙運発第 12 号の減点細目 減点 0・100 点
10 エネルギー収支 ½v² + g z + 転がり・空気・制動の仕事 − 駆動の仕事 = 一定 全 499 区間で 1.2e-9 J/kg
11 つまみ: 反応時間 閉形式のしきい値 ρ* = 2.546 s 2.50 s は手前 0.278 m、2.60 s は線を 0.278 m 越えて停止位置不適
12 つまみ: 路面の μ 閉形式の下限 μ* = 0.1134 その上は止まれ、0.01 下は 1.08 m 越える
13 つまみ: 勾配 ずり下がりの閉形式 踏み替えに 1 s かかる発進で 9 % 逆行小・10〜11 % 逆行中・12.5 % 以上 逆行大(試験中止)
14 ゼロ点 灯火を消す ‘unknown’ のまま止まって発進しない(fail-closed)

反応時間のつまみ

↑ 反応時間と止まった位置。閉形式のしきい値 2.546 s を境に、停止線の手前から向こうへ出る。

門 11〜13 の読み方。つまみは「動かしたら悪くなった」ではなく、どこで悪くなるかを先に閉形式で出し、その上下で走らせて確かめます。反応時間なら ρ* = (線までの距離 − 上限の制動での停止距離) ÷ 速さ。その 0.05 s 手前では止まり、0.05 s 後では越える。数字が閉形式の側から決まっているので、「なんとなく悪くなった」を許しません。

★ 実装すると、ここで間違える

1. 指令が切り替わる時刻をまたぐと、停止距離が v·dt/6 ずれる

積分の 1 歩が「制動を始める時刻」をまたぐと、その 1 歩の中に切り替え後の指令が混ざります。RK4 の中間点で指令を評価するからです。停止距離が 2 cm ずれて、閉形式の門に落ちました。切り替え時刻で刻みを割ります。

2. 止まる瞬間を見落とすと、坂で後ろへ走り出す

制動中に速さが 0 を横切った歩で「止まった → 保持」に切り替えないと、次の歩で重力とブレーキの向きが入れ替わり、車が後ろへ加速します。ブレーキと転がり抵抗は「動きに逆らう力」で、止まっている間は「保持できる範囲なら動かない」という別の扱いが要ります。

3. 停止線の座標は、塗った線の手前の縁ではない

コースの停止線の座標 x = −12.50 は線の中心で、幅 0.45 m の塗った線の手前の縁は −12.95 です。中心を基準に止めると、車の前端は塗った線の上で止まります(第 2 回の車がそうでした)。止まる基準は手前の縁です。

4. 路面が滑る時は、名目の制動を前提に「待つ」と間に合わない

μ が小さいと、制動の上限は μ g で頭打ちになります。名目の減速度で「まだ待てる」と計画してから上限にぶつかると、止まれるはずの μ でも線を 1.85 m 越えました。待つ時間は、上限で頭打ちにした制動で計算します。

向かないこと

止まる計画は、知覚した瞬間に決めて途中で測り直しません。 世界とモデルが同じなのでぴったり止まりますが、μ の読み違いへの頑健さは測っていません。

逆行と停止位置の距離の閾値は、通達に数字がありません。 逆行小 0.3 m・中 0.5 m・大 1 m と「停止線の 2 m 以上手前は不適」は二次情報(教習所の解説)です。質量 1300 kg・制動の上限 6 m/s²・反応 0.75 s・μ 0.8 は仮定、c_rr 0.012 は文献値の範囲。

車載カメラが信号を読めるのは停止線の約 20 m 手前からです。 20 km/h なら間に合いますが、速い接近は扱えません。霧や夜で読める距離が縮むと、ここが効いてきます(次回)。

坂道の縁石は地面の高さに描かれます。 世界の描画側(driveworld)の課題で、PoC の中で擁壁と白線を足して見えるようにしています。

動かす

py -3.11 examples/poc_driving_longitudinal.py            # 門 14 本(図なしで約 28 秒)
import math
import fullseye as fs

# 40 km/h、反応 0.75 s、制動 4 m/s²: 平地と 8 % の上り・下り
v = 40 / 3.6
for grade in (0.0, 0.08, -0.08):
    print(grade, round(fs.ledger.stopping_distance_grade(v, 0.75, 4.0, theta=math.atan(grade)), 2))
# 0.0 23.77
# 0.08 21.24
# -0.08 27.52

# 11 % の坂道発進: ブレーキを離してから駆動 2 m/s² が立ち上がるまで 1 s かかると(クリープ無し)
print(round(fs.ledger.hill_start_rollback(math.atan(0.11), 1.0, 2.0)["rollback"], 3))
# 0.914

# 技能試験の採点(停止線の手前 0.5 m で止まり、坂で 0.4 m ずり下がった)
print(fs.ledger.skill_test_score([{"kind": "stop", "gap": 0.5}, {"kind": "start", "rollback": 0.4}])["score"])
# 90

この回が作った図は全部で 5 枚あります —— 全部見る

この回の残りの図

速さと距離

↑ 走った全体の速さと、停止線・坂の位置。

勾配のつまみ

↑ 勾配と坂道発進のずり下がり(踏み替え 1 s)。閉形式(線)と積分器(点)、逆行の減点の帯。


第 6 回: 太陽と天気 —— 朝日の逆光で信号が読めない時間帯、霧の中で見えてから止まれる速さ、雨の路面、夜の前照灯

春分の一日

↑ 春分(2026-03-20)の東京、朝 5:15 から夜 20:15 まで 15 分おき。左 = 東へ向かう車の車載カメラ(停止線の 15 m 手前)と信号の読み、右 = 斜め上から。影は太陽と反対へ伸び、昼に短くなってまた伸びる。朝、太陽が信号の後ろの低い空にある間は、画像処理が赤を「読めない」。日が沈むと前照灯を点ける。

第 5 回までの車載カメラは、光の向きが決め打ちで、明るさは「面の色 × 陰影」だけでした。この回は世界を物理の単位(輝度 cd/m²・照度 lx)で照らし直し、太陽・影・逆光のまぶしさ・霧・雨・夜の前照灯を入れます。

測りたいのは「きれいに見えるか」ではなく、車載カメラの画像処理がどこで読めなくなるかです。それを画素の式から先に閉形式で出し、描いた画像の読みがその両側で変わることを確かめます。そして第 5 回の停止距離と組み合わせて、「見えてから止まれるか」を閉ループで走らせます。

手順

太陽   : 日時と緯度経度 → 高度・方位(NOAA の式 = Meeus の低精度版)。直達光は空気の量で弱まり、影は太陽から見た深度画像で落とす
逆光   : 太陽と視線の角 θ で光幕 L_v = 10 E / θ²(Stiles–Holladay)が画面に重なる。光幕は白いので灯火の色を灰色へ寄せる
霧     : Koschmieder の法則 L = L₀ e^{−βd} + L_h (1 − e^{−βd})、視程(気象光学距離)= 2.996/β
雨     : 濡れた路面の摩擦係数 f(道路構造令の解説の表)で停止距離が延びる。灯火が路面に映る
夜     : 灯火は自分で光る。前照灯は配光(光度の角度分布)から照度 I cos i / r² を与える
判断   : 灯火の色が読める距離から「止まれる速さ」を出し、その上下で走らせる

かみ砕き: カメラが信号の色を読むときは、灯火の画素の色の比(赤っぽさ)を見ています。逆光のまぶしさや霧は、画面に白い光を足す働きをします。白を足すと色の比は灰色へ近づき、ある量を超えると「赤」と言えなくなります。その量(W*)は画素の式だけで決まるので、「太陽がどの角度まで近づくと読めないか」「霧の中で何 m まで読めるか」を、描く前に計算できます。

逆光の閾値

↑ 横 = 太陽と灯火への視線の角 θ、縦 = 画素の式から先に出した閾値 θ。対角線の右下は読め、左上は読めない。赤が最も弱い。*

門と成績

# 門 真値 成績
1 太陽の位置 国立天文台 暦計算室の東京 2026 年(春分・夏至・秋分・冬至)の日の出・南中・日の入りの時刻と方位・南中高度 24 / 24 値が丸めの単位(1 分・0.1°)で一致。時刻の差は最大 0.45 分(丸めの前)
2 影の長さ 高さ × cot(高度) + 柱の角 真上から撮った画像の影の先端と、4 つの高度で最大 0.074 m(1 画素 0.10 m)
3 逆光 閾値 θ* = √(10 E / W)(W = 色度が許容 0.25 を割る白の量) 3 色 × 太陽の角 27 点で、読めた / 読めないが予測と食い違い 0。赤の θ* は 12.7〜17.8°(太陽の高度で変わる)
4 春分の朝 太陽の式で出した「読めない時間帯」 東へ向かう車から赤が読めないのは 05:53〜07:05。15 時刻を描いて、内側は「読めない」、外側は赤
5 霧の濃さを画像から 真の減衰係数 β 路面の縦の輝度(雑音 1 %)に Koschmieder を当てて、視程 30〜200 m で誤差 0.96 % 以内
6 霧の中で色が読める距離 d* = ln(1 + W*/L_h)/β 視程 150/200/250 m × 3 色の探針で、読める / 読めないが予測と食い違い 0
7 見えてから止まれるか 止まれる速さ v*(停止距離の閉形式を v について解く) 視程 200 m で赤が読めるのは停止線の 6.6 m 手前から → v* = 17.5 km/h。0.9 倍では手前 0.50 m に止まり、1.3 倍では 1.58 m 越える
8 第 2 実装 道路構造令の解説の停止距離 D = 0.694V + 0.00394V²/f(反応 2.5 s) 第 5 回の閉形式と 8 速度で 0.034 %(係数の丸めの分)
9 濡れた路面 停止距離の閉形式 40 km/h: 乾燥 18.39 m → 湿潤(f 0.38)24.31 m。積分器と 1e-6
10 雨の鏡像 路面を鏡にした灯火の投影 位置の差 0.22 画素。地図の ROI の外なので、信号の読みは乱れない
11 夜の前照灯 保安基準の性能(すれ違い 40 m・走行 100 m の障害物) 仮定した配光で、歩行者をすれ違い 60 m・走行 120 m まで画像で見つける。空の車線では何も見つけない
12 夜の閉ループ 見つけてから止まれる速さ v* = 81 km/h(すれ違い灯) 0.85 倍では歩行者の 15 m 手前に止まり、1.3 倍では止まれない
13 ゼロ点 灯火を消す 晴れ・霧・雨・夜のどれでも ‘unknown’(fail-closed)

霧の中で色が読める距離

↑ 霧の中で灯火の色が読める距離。閉形式 d = ln(1 + W/L_h)/β(線)と、描いた画像で読めた距離(点)。

門 3・5 の読み方。どちらも「描いてみたら読めなかった」ではありません。読めなくなる境目を画素の式から先に出し、その両側で描いて確かめます。逆光なら、色度が許容を割る白の量 W* と光幕の式 10E/θ² を等しいと置いて θ* = √(10E/W)。霧なら、灯火の画素 c·L·e^{−βd} + L_h(1 − e^{−βd}) の色度は c·L + L_h(e^{βd} − 1) と同じなので、d = ln(1 + W*/L_h)/β。大気光 L_h は画像の空から測ります。

★ 実装すると、ここで間違える

1. 地方時の datetime をそのまま渡すと、太陽が 9 時間ずれる

太陽の位置の式は UT で考えます。タイムゾーンの無い datetime を「日本時間のつもり」で渡すと、東京の正午に太陽が地平線の下にいます。タイムゾーンの無い datetime は受け付けないようにしました。

2. 日の出の定義が 1 分を動かす

「太陽の中心が高度 −0.833°」(NOAA)と「上辺が視地平線、地平大気差 35′8″」(暦計算室)では、日の出が 5〜8 秒ずれます。表が分に丸めてあるので、この差で丸めの結果が変わる日があります。比べる相手の定義で解きます。

3. 足元の地面が空になる

三角形を描く道具が「頂点の 1 つでもカメラの後ろにある三角形」を落とすため、足元の大きな地面の三角形が抜けて、画面の下に空が出ます。地面(z = 0 の平面)は視線との交点を解析的に取って埋めます。

4. 影の分位で閾値を決めると、影が見えない

影の画素は地面の 1 % 未満です。「明るさの 1 % 分位」を暗い側の基準にすると、基準が日なたになり、低い太陽ではまぶしさの明るさのむらを影と取り違えます。暗い側は最小値で取ります。

5. 走行灯の遠くの路面を、歩行者と取り違える

「車線の中の明るい塊」を障害物とすると、走行灯が照らす遠くの路面が見つかってしまいます。同じ行(= 同じ距離)の路面の中央値を引き、それより十分明るい所だけを拾います。

向かないこと

空の明るさ・薄明の照度・灯火の輝度・前照灯の配光・HDR カメラの階調は仮定です。 読めなくなる境目の形(√(10E/W)・ln(1 + W/L_h)/β)は式から出ますが、何 m・何度かという数字はこれらの仮定で動きます。

光幕の式は、人の目の中の散乱の経験式です。 カメラのレンズの散乱をこれで代用しています。

霧は、描いたのと同じ一様な Koschmieder の世界の上で測っています。 実際の霧(一様でない・灯火のまわりの光輪)への頑健さは測っていません。変曲点から β を出す閉形式(β = 2/d_i)は、雑音 1 % と画素の刻みでは大きく外れました(表の値は当てはめの方)。

雨筋と路面の鏡像のぼけは見た目だけです。 鏡像の位置は鏡の幾何で厳密です。

前照灯の影はありません。 太陽の影は、太陽から見た深度画像の解像度で縁が決まります。

動かす

py -3.11 examples/poc_driving_weather.py            # 門 13 本(図なしで約 134.9 秒)
from datetime import datetime, timedelta, timezone
import fullseye as fs

# 東京の春分の日の出・南中・日の入り(暦計算室の定義)
ev = fs.ledger.sun_events(2026, 3, 20, 35.6581, 139.7414, 9.0, h0="naoj")
print(ev["sunrise"].strftime("%H:%M:%S"), round(ev["sunrise_azimuth"], 1), ev["transit"].strftime("%H:%M:%S"), round(ev["transit_altitude_apparent"], 1))
# 05:45:17 89.8 11:48:34 54.2

# 視程 100 m の霧の減衰係数と、見える距離 40 m の中で止まれる速さ(反応 0.75 s、制動 6 m/s²)
print(round(fs.ledger.beta_from_mor(100.0), 5), round(3.6 * fs.ledger.sight_stop_speed(40.0, 0.75, 6.0), 1))
# 0.02996 64.3

# 赤の灯火(輝度 10000 cd/m²)の色度が許容 0.25 を割る白の量
print(round(fs.ledger.veil_chroma_limit((1.0, 0.12, 0.08), 10000.0, 0.25)))
# 2704

この回が作った図は全部で 7 枚あります —— 全部見る

この回の残りの図

太陽の高度と暦計算室

↑ 東京の太陽の高度(線)と国立天文台 暦計算室の公表値(点)。

霧の車載カメラ

↑ 霧の車載カメラ(停止線の 15 m 手前)。灯火は交差点の向こう、停止線から約 26 m 先にある。

霧の濃さを画像から

↑ 車線の中の路面の輝度を行ごとに並べ(点)、Koschmieder を当てた線から視程を戻す。

雨と夜

↑ 雨(路面に赤が映る)と夜(灯火は自分で光る)、すれ違い灯と走行灯で照らした歩行者。


第 7 回: 終わらない地図 —— 区画を車の周りに作り足し、離れた区画は捨てて、50 km を途切れずに走る

真上から見た区画の作り足し

↑ 赤 = 車。車の周り 5 × 5 の区画(一辺 200 m、1 km 四方)だけを持ち、区画をまたぐと進む側に 5 区画を作って反対側の 5 区画を捨てる。色の塗り分けは起伏、灰の帯は道です。MP4

著者の発案です —— 「マップが前後左右方向にエンドレスに作られていき、一定以上離れたマップは自動に消えていく形にすれば、もっと長距離の連続走行のテストもできるのではないか」。これまでの教習所の世界は数百 m 四方で、長い距離を走るテストができませんでした。

この回は世界を区画に分け、区画の中身を区画の番号と世界の種だけで決めます。乱数の状態を持ち越さないので、捨てた区画に戻ってきても 1 ビットも違わずに作り直せ、作る順番にも依りません。

仕組み

  1. 区画の番号のハッシュ(SplitMix64、整数だけの計算)から、その区画の乱数をすべて引く。
  2. 道は辺で決める。 区画の辺を道が横切るか・どこで横切るかを、辺の番号のハッシュで決める。東の区画の西の辺と、この区画の東の辺は同じ辺なので、両側が同じ値を読み、継ぎ目で必ずつながる。区画の中は、横切る点を分岐点へ直線で結ぶ。
  3. 起伏は全体で共通の整数格子。 格子の番号 = 区画の番号 × 升目数 + 区画の中の升目の番号(整数なので厳密)。補間は区画の中の小数だけで行うので、継ぎ目で連続し、遠くでも桁が落ちない。道の周り 18 m は平らにする。
  4. 位置は (区画, 区画の中の座標) で持つ。 全体の座標を浮動小数で持つと遠くほど刻みが粗くなりますが、区画の中の座標は常に [0, 200) m です。描くときも、車のいる区画を原点にして周りの区画をずらします(浮動原点)。
  5. 車の周り 5 × 5 区画だけを持ち、外れた区画は捨てる(tile_stream)。

追従カメラ

↑ 追従カメラ(赤 = 車)。1,000 km 先の区画から走り出しても、描く頂点の座標は ±600 m に収まります。区画の継ぎ目で道と地面は途切れません。MP4

門と成績

門 真値の出どころ 成績
継ぎ目 両側の区画が同じ境目を計算する 区画の組 300(番号は ±10⁶ = ±200,000 km まで)で高さの差 3.3 × 10⁻¹⁵ m、道の横切り 505 本はビット一致
作る順に依らない 番号順と乱数の順 5 × 5 の区画の指紋(SHA-256)が全部同じ
作り直し 最初に作ったときの指紋 50 km の間に捨ててから作り直した 214 区画がすべて一致
記憶の上限 (2r + 1)² 持っていた区画は最大 25、頂点と面は最大 0.90 MB
道を外れない 道の中心線までの距離 50 km の間、最大 2.1 × 10⁻¹¹ m(継ぎ目をまたぐ所も)
長距離の精度 同じ歩みを有理数(丸め無し)で足し直した終点 1,000 km 先から 50 km 走って、区画の中の座標は誤差 2.1 × 10⁻¹¹ m、全体の座標を float32 で持つと 33.4 m ずれる

走った経路

↑ 東寄りに道の網をたどった 50 km。通った区画は 956 で、持っていたのは常に 25 以下です。

実装で踏む穴

向かないこと

動かす

import numpy as np
import fullseye as fs

tp = fs.ledger.tile_params()                                # 一辺 200 m、世界の種 20261001

# 区画 (i, j) の東の辺と、東隣の区画の西の辺は同じ辺 —— 道が横切る位置は 1 ビットも違わない
print(fs.ledger.tile_edge_crossing(5000, 2, "E", tp) == fs.ledger.tile_edge_crossing(5001, 2, "W", tp))
# True

# 車の周り 3 × 3 だけを持ち、区画をまたいだら進む側を作って反対側を捨てる
cache = {}
fs.ledger.tile_stream(cache, 5000, 2, tp, radius=1)
r = fs.ledger.tile_stream(cache, 5001, 2, tp, radius=1)
print(r["n"], r["loaded"], r["evicted"])
# 9 [(5002, 1), (5002, 2), (5002, 3)] [(4999, 1), (4999, 2), (4999, 3)]

# 捨てた区画を作り直しても指紋は同じ
a = fs.ledger.tile_digest(fs.ledger.tile_mesh(4999, 2, tp))
b = fs.ledger.tile_digest(fs.ledger.tile_mesh(4999, 2, tp))
print(a == b, a[:16])
# True e2ad2e4cd85bd340

# 位置は (区画, 区画の中) で持つ: 1,000 km 先でも区画の中の刻みは同じ
print(fs.ledger.pose_normalize(5000, 2, 199.5 + 1.25, 30.0, 200.0))
# (5001, 2, 0.75, 30.0)      ← 東の区画へ繰り上がる

PoC 全体は py -3.11 examples/poc_driving_endless_map.py(図と動画は FULLSEYE_FIGURE_DIR を設定したとき)。


第 8 回: 動く交通と死角 —— 路肩駐車の陰の子ども、対向車とのすれ違い、バス停、下手な運転、横断歩道で待つ人

車載カメラ: 路肩駐車の陰から子どもが走り出る

↑ 車載カメラ。路肩駐車の白い車の陰に子どもが隠れています。自車は死角の手前で「止まれる最大速度」10.7 km/h まで落として横を抜け、走り出した子どもを背景差分で 0.133 s 遅れで見つけて手前で止まります(赤枠 = 地図の背景との差、右下 = 3 倍の拡大)。MP4

ここまでの世界は止まっていました。歩行者は箱と回転体を置いただけ、他の車は等速の先行車 1 台だけです。著者から続けて出た指摘を、そのまま場面にしました ——「歩行者とか外乱要素がまだまだ足りない」「自転車も道路を走ってる」「路肩駐車してる車もいる」「下手くそな運転してる奴もいる」「頻度は多くないけど、飛び出す奴や横切る奴もいる」「路肩駐車のある場合、対向車とのすれ違いのタイミングを考えないといけない」「バス停車中は人の乗り降りが多く飛び出してくる人がいやすいので、追い越しは注意がいる。出来れば発車まで待つほうが良い」「歩道で待つ人がいれば一時停止」。

そして「自動車の教本に書かれている内容を読み返し、その中にある要素を再現するべき」。そこで国家公安委員会告示「交通の方法に関する教則」から運転者の場面を 159 拾い、場面ごとに「再現済み / 一部 / 未着手 / 再現できない(理由)」を付けた再現台帳を作りました(告示は著作権法 13 条で権利の目的にならないので場面を拾えますが、本文は写さず要約にしています)。この回までで再現済み 8、一部 11、再現できない 7(飲酒・シートベルトのように外界の画像と車の運動に現れないもの)、未着手 133 です。台帳と PoC は互いに名指しし合い、片方だけ直すと門が落ちます。

場面と門

新しいモジュール drivetraffic(18 op)と PoC ㉙ で、8 つの場面を作りました。

場面 真値の出どころ 成績
死角からの飛び出し(教則 S039) 箱の角をかすめる視線の閉形式 → 停止距離の逆 隠れ場所 45 候補・445 試行の全部で子どもの手前に止まる(最小の余白 0.188 m)。1.3 倍の速さだと 3 試行で止まれない
対向車とのすれ違い(S089) はみ出す時間と対向車の到着の比較(閉形式の境目 D*) 時間を進めた PET = 1 s の境目と D* が相対 1.6 × 10⁻⁹ で一致。対向 600 台/時のとき平均の待ち 9.0 s = Adams の式
バス停(S029) 出現率 λ(x, t) の積分(erf + 時間の積分) 止まれない帯に人が出る期待件数: 発車まで待つ 0.0016 < 徐行で追い越す 0.015 < そのまま 0.025。既定は待つ(代償 15 s)
下手な運転の車列(S061) IDM の平衡車間 s_e(v) = (s0 + vT)/√(1 − (v/v0)^δ) 慎重・普通は平衡に収束、反応 1.3 s が車間時間 1.0 s より長い荒い運転は振動が育って t = 10.8 s に追突
危ない車を見分ける 付けた癖(真値) 横ふらつき(OU 過程)の強さを最尤で読むと 17 / 17 を当て誤検出 0(位置のばらつきで見ると 13 / 17)
横断歩道で待つ人(S042) 歩行者に持たせた「渡るつもり」 渡る人の見逃し 0。縁で立つだけの人 117 人にも止まる(安全側の誤り。教則も、渡る人がいないことが明らかでなければ止まれる速さに落とせ、という書き方)
稀な飛び出し 非一様ポアソンの件数 = ∫λ 件数の平均 = 分散 = ∫λ。重要度サンプリングは閉形式と素朴な MC の両方に一致し、1 試行の分散が 25.7 分の 1
自転車の追い越し(S053) 側方間隔(1.5 m は PoC で決めた値で、法の数値ではない) ふらつきの強さを読んで余裕を足すと 300 試行の全部で 1.5 m 以上。足さないと 234 試行で割る

俯瞰

↑ 通し走行(86 s・335 m)の俯瞰。青 = 自車、白 = 路肩駐車、紫の影 = 死角、橙 = 荒い運転の対向車、紫 = 自転車、黄 = 子ども。駐車車両・子ども・対向の車列・自転車・バスのどれとも接触せずに終点に着きます。MP4

下手な運転の車列の時空図

↑ 先頭が 10 → 8 m/s に落とすだけで、荒い運転(右)の車列は揺れが後ろへ育って追突します(×)。慎重(左)・普通(中)は平衡車間に収まります。

知覚は学習なしで

気づく側は、車載カメラの画像を地図の背景と引き算するだけです(フレームの差でなく背景との差。学習は使いません)。子どもが描画に写り始める時刻(面ラベルの真値)から 0.133 s(4 コマ)遅れで検知し、それより前の誤検知は 0 でした。検知 + 反応 0.5 s + 制動 6 m/s² で子どもの 7.09 m 手前に止まります。前の回で決めた方針どおり、手続きで作った世界は採点のための真値で、訓練には使いません。

★ 実装すると、ここで間違える

向かないこと

動かす

import fullseye as fs

# 路肩駐車の陰: 目は (0, 0) から +x へ進む。駐車車両 = 中心 (12.5, 2.5)・長さ 5 m・幅 2 m の箱。陰の点 (16, 3) が
# 初めて見えるとき、その点は目の何 m 先か(角をかすめる視線の閉形式)
d = fs.ledger.occlusion_reveal_distance((0.0, 0.0), 0.0, (12.5, 2.5, 5.0, 2.0, 0.0), (16.0, 3.0))
print(round(d, 3))
# 2.0

# 見えた瞬間から、その手前に止まれる最大速度(反応 0.5 s、制動 6 m/s²)
v = fs.ledger.occlusion_safe_speed(d, reaction=0.5, brake=6.0)
print(round(float(v) * 3.6, 1), "km/h")
# 9.9 km/h

# 駐車車両(5 m、前後の余裕 3 m)を避けて対向車線へはみ出す。自車 8 m/s、対向車 10 m/s
g = fs.ledger.passing_gap_required(5.0, 3.0, 3.0, 8.0, 10.0, lane_change_time=1.5)
print(g["t_occupy"], g["d_required"])
# 2.875 51.75      ← はみ出す時間 [s] と、対向車がこれより遠ければ行ける距離 [m]
for dist in (40.0, 60.0):
    print(dist, fs.ledger.passing_decision(dist, 5.0, 3.0, 3.0, 8.0, 10.0, lane_change_time=1.5))
# 40.0 wait
# 60.0 go

# 下手な運転: 車間時間と反応の遅れ(仮定の値)
s = fs.ledger.driver_style("sloppy")
print(s["T"], s["reaction_delay"])
# 1.0 1.3          ← 反応の遅れが車間時間より長い

PoC 全体は py -3.11 examples/poc_driving_traffic.py(図と動画は FULLSEYE_FIGURE_DIR を設定したとき)。教則の場面の台帳は docs/drive/kyosoku_scenarios.json です。


第 9 回: 判断の場面 —— ミラーで左後方を確かめる、歩行者信号から黄を予測する、救急車に道を譲る、バスの発車を待つ

車載カメラ: 左折の前にミラーで自転車を見つけて待つ、後ろから救急車

↑ 車載カメラ(右上 = ルームミラーとドアミラー)。場面 1: 左折の 30 m 手前で合図、その前に左ドアミラーで左後方を確かめ、路側帯を来る自転車を見つけて先に通します。場面 2: 後ろから救急車。サイレンの「近づく」とミラーの赤色灯の点滅で気づき、左に寄って交差点の手前(停止線の 1 m 手前)で一時停止、通り過ぎてから発進します。MP4

前の回は「動く相手」を置きました。この回は、その相手を前にして何を確かめ、いつ決めるかです。著者の指摘 ——「信号は歩行者信号の動きを見て予測も必要」「ミラーの確認もいる」「救急車が来た時もちゃんとしないといけない」「バス停車中は……出来れば発車まで待つほうが良い」—— を、前の回で作った教則の再現台帳の場面(合図の時期、緊急自動車、黄信号、バスの発進)に当てて作りました。

場面と門

新しいモジュール drivedecide(18 op)と PoC ㉚ です。

場面 真値の出どころ 成績
ミラーの鏡像(S067) 鏡面上の Fermat の点(光路最短)の光線追跡 鏡の向こうの仮想カメラで描いた目印の位置 = 光線追跡(12 点で最大 0.50 px)。左右反転を外すと中央値 228 px ずれる
ドアミラーの死角 鏡の両端で反射した光線で囲む多角形 柱を 295 点立てて凸面ミラーの仮想カメラで描いた結果と不一致 0。凸面(R 1.4 m)は同じ幅の平面鏡の死角 37.1 m² を 20.4 m² に減らす
左折の巻き込み(S054・S091) 自転車の位置(真値) ミラーで確かめると 400 試行の全部で巻き込まない(最小 1.08 m)。直接の視界だけだと 68 試行で巻き込む
確認の順序(S066・S067・S070) 教則の「ミラー → 合図(約 3 秒前 / 30 m 手前)→ 進路変更 → 合図をやめる」と、技能試験の減点(警察庁 丁運発第44号: 安全不確認 10 点・合図不履行等 5 点) 自車の左折と車線変更は減点 0。崩した 6 版はそれぞれの点数だけ減点
歩行者信号から黄を予測(S002) 信号の時間割(歩行者の青点滅 = 横断長 / 1.0 m/s、その後に車両の黄) 200 m 先の灯火(1.8 px)の点灯を画素から読んで全 690 コマで真値と一致。予測区間は 585 回の全部で真の黄を含む(半幅 0.267 s、赤を見た後 0.017 s)
ジレンマゾーン(S002) GHM(Gazis–Herman–Maradudin 1960)と停止線の読みの閉形式 600 試行で、予測ありは黄の瞬間にジレンマゾーンに 0 回。予測なしは 48 回(停止線の読みで 10 回)
救急車に気づく(S026) 音源と自車の幾何(ドップラーの式を使わずに発音時刻から合成) 「近づく / 遠ざかる」の判定は距離の変化率の符号と 99.89 % 一致、2 本のマイクの到着時間差の方位は幾何と最大 0.11°。赤色灯の点滅 2.498 Hz(式 2.500)、3.75 fps に間引くと折り返して 1.252 Hz(式 1.250)
救急車に道を譲る(S026・S027) 道交法 40 条 1 項(交差点の附近 = 交差点を避けて左に寄って一時停止)・2 項(左に寄って譲る) この自車は違反なし。左に寄らずに止まる版・進んで交差点の中で止まる版はそれぞれの違反で落ちる
バスの発進(S029) 31 条の 2「急に速度・方向を変えないと譲れない場合を除き、妨げない」→ 譲るのに要る減速の閉形式 既定は発車まで待つ(違反なし、バスとの最小間隔 3.18 m)。40 m 手前のとき要る減速 2.2957 m/s² = 刻み 0.1 ms のブレーキ + 二分法

信号の予測

↑ 並行する歩行者信号(右上 = 4 倍の拡大)の青が初めて消えたコマで「青点滅に入った」と分かり、車両の黄の時刻を予測します。下の帯 = 停止線までの位置と、その車の今の速さ・ブレーキの状態でのジレンマゾーン(橙 = GHM の読み、赤 = 停止線の読み)。予測ありの自車(青)は早めに減速し、予測なし(黒)は黄の瞬間にジレンマゾーンの中にいます。MP4

ジレンマゾーン

↑ 黄が点いた瞬間の (速さ, 停止線までの距離)。黒 = 止まれる境、橙 = GHM で交差点を抜けられる境、赤 = 停止線を越えられる境。

ドアミラーの死角

サイレンのスペクトログラム

↑ 合成したサイレンを左のマイクで聞いたスペクトログラム。白の点 = 幾何から出した真の周波数。近づく間は高く、前を過ぎると低くなります。

★ 実装すると、ここで間違える

向かないこと

動かす

import fullseye as fs

# ジレンマゾーン: 50 km/h(13.9 m/s)で黄が点いたとき、止まれず抜けられもしない停止線までの距離 [m]
# (反応 1 s・減速 3 m/s²・黄 3 s・交差点の幅 20 m・車長 4.5 m、GHM 1960 の読み)
z = fs.ledger.dilemma_zone(13.9, reaction=1.0, decel=3.0, amber=3.0, intersection_width=20.0, car_length=4.5)
print([round(x, 1) for x in z["dilemma"]])
# [17.2, 46.1]      ← 止まれず抜けられない停止線までの距離 [m]

# 歩行者信号の青が t = 20.53 s に初めて消えた(= 青点滅に入った)。青点滅 10 s・歩行者の赤から車両の黄まで 2 s
r = fs.ledger.predict_amber_onset([(0.0, "green"), (20.4, "green"), (20.53, "flash")],
                                  flash_duration=10.0, ped_red_to_amber=2.0)
print(r["lo"], r["hi"])
# 32.4 32.53      ← 真の黄はこの間

# 車線変更の確認の順序を採点(丁運発第44号: 安全不確認 10 点)。ミラーを合図の後に見た版
ev = [{"t": 1.0, "kind": "signal_on"}, {"t": 2.0, "kind": "mirror"}, {"t": 4.0, "kind": "start"},
      {"t": 7.0, "kind": "end"}, {"t": 7.5, "kind": "signal_off"}]
print(fs.ledger.check_sequence_score(ev)["score"])
# 90             ← 安全不確認 10 点の減点

# 赤色灯の点滅 2.5 Hz を 3.75 fps のカメラで見ると何 Hz に見えるか(折り返し)
print(fs.ledger.aliased_frequency(2.5, 3.75))
# 1.25

# サイレン 960 Hz の音源が 16.6 m/s で近づくときに聞こえる周波数
print(round(fs.ledger.doppler_shift(960.0, 16.6), 1))
# 1008.8      ← 960 Hz より高く聞こえる

PoC 全体は py -3.11 examples/poc_driving_decisions.py(図と動画は FULLSEYE_FIGURE_DIR を設定したとき)。


第 10 回: 横の運動 —— カーブの手前で落とす、車線の中を保つ、左に寄って左折する、内輪差で巻き込まない

車載カメラ: カーブの手前で減速して車線の中を曲がる、左に寄って左折する

↑ 車載カメラ(右上 = 摩擦円の小窓、使用率の点)。カーブの手前で曲率から決めた速さまで落とし、車線の中を保って曲がります。左折は、あらかじめ左に寄ってから曲がる版と、寄らずに曲がって後ろの自転車に入り込まれる版を並べました。MP4

ここまでは縦の運動(止まる・待つ・譲る)が中心でした。この回は横です。曲がるときに何が限界を決め、車線のどこを走り、左折でなぜ「あらかじめ左に寄る」のか。教則の台帳から、道路の左に寄って通行する・車線をまたがない・路肩にはみ出さない・曲がり角では徐行・左折は左端に寄って側端に沿って徐行・右折は中央に寄って中心のすぐ内側、の場面を拾いました。

部品はすべてルールベースです(著者の方針「AI には部品の組み合わせを考えてもらう、部品はルールベースに限定して増やす」)。学習は使わず、どの部品も閉形式・公表値・条文の判定のどれかで門が立ちます。もう 1 つの方針、「ランダムなものを載せるなら、統計に基づいて極端に外れすぎたものは採用しない」も門にしました。

場面と門

新しいモジュール drivelateral(20 op)と PoC ㉛ です。

場面 真値の出どころ 成績
乱数で載せる値の門 参照分布の両側 0.1 % 点(1 個ずつ)と KS 検定(集団) 自転車の速さ 24,000 個のうち 22 個を理由つきで落とし、集団は KS p = 0.905。ありえない値(μ = 1.9 など)5 個は 1 個ずつ落ちる。単位の誤り(m/s をさらに 3.6 で割った値)は 1 個ずつの門を 86 % が通るが、KS が D = 0.986 で落とす
道の形 道路構造令 第 15・18 条の表と、解説の許容(横加速度の変化率 0.5〜0.75 m/s³) 半径 100 m・緩和区間 40 m のクロソイドで、設計速度での変化率 0.670 m/s³。円のずれ 0.666 m(近似 L²/24R 0.667 m)
カーブの手前で落とす(S065) 摩擦円 √(ax² + ay²) ≤ μg と、曲率から前後 2 回なめる速度計画 計画した 120 人の全員が摩擦円の中(使用率 最大 0.48)、曲がり角の附近は 10 km/h 以下。計画なし(50 km/h のまま)は全員が円を出る(使用率 1.65〜2.42)
車線の中(S020・S024・S033) 車体の 4 隅と中央線・外側線の距離 全員が中央線をまたがず(最小 0.24 m)、路肩へ出ない(最小 0.34 m)。注視距離 20 m 固定だと 40 人全員が曲がり角で外側線を越える
位置保持の制御則 pure pursuit の定常の横ずれ(2 自由度の式) 円弧の終わりの横ずれ 0.144〜0.250 m を、式が 0.0001 m で当てる
内輪差(S091) 後輪の軌跡(追跡曲線)の閉形式 前輪の半径 6 m の 90° で、後車軸の半径を閉形式 5.4092 m と数値 5.4090 m(差 1.7 × 10⁻⁴ m)
左折(S092・S091) 教則「あらかじめ左端に寄り、交差点の側端に沿って徐行」の判定(turn_maneuver_check) 左に寄った計画は違反なし、隅で待つ自転車まで最小 0.66 m。前輪で隅に沿う版は「内輪差で隅に入った」で自転車に触れる(0.00 m)
後ろの自転車の巻き込み 自転車の位置(真値)と、重要度サンプリングの重みつきの件数 左に寄ると巻き込み 0(格子・素朴な MC・重要度サンプリングのどれでも)。寄らないと残る: 重要度サンプリングで p = 0.00024、素朴な MC 24,000 試行で 12 件(期待 5.8 件、両側 p = 0.03)。1 試行あたりの標準偏差は約 50 分の 1
右折(S093) 教則「中央に寄り、交差点の中心のすぐ内側を徐行」の判定 計画は違反なし(中心まで 1.41 m)。大回りは「外側を回った」、早回りは「すぐ内側でない」で落ちる

上から見た 2 台: 計画どおりと計画なし

↑ 上から見た図。左 = 計画どおり、右 = 計画なし(50 km/h のまま)。下の帯 = 120 人の速度計画、右下 = 摩擦円。計画なしは急な曲がり角で使用率 1.74 と円の外に出ます(線形モデルなのでタイヤの飽和は描かず、使用率で判定しています)。MP4

左折の内輪差

↑ 左折の内輪差(上から、縦横 1 m 等倍)。青・緑 = 左に寄って選んだ円の前・後ろの左の車輪、赤・橙 = 前輪で隅に沿う版。前輪が隅をなぞると、後輪が隅で待つ自転車(黄)まで入り込みます。

標本の門

↑ 標本の門。青 = 採った自転車の速さ、線 = 参照分布、黒の縦線 = 両側 0.1 % 点。赤 = 単位を間違えた 2,000 個。1 個ずつの門で落ちるのは 277 個だけで、残りは集団の KS が落とします。

★ 実装すると、ここで間違える

向かないこと

動かす

import fullseye as fs

# カーブの限界速度 v = √(g R (f + i)/(1 − f i)): 半径 100 m、横すべり摩擦 0.15、片勾配 6 % [km/h]
v = fs.ledger.curve_speed_limit(100.0, side_friction=0.15, superelevation=0.06)
print(round(float(v) * 3.6, 1))
# 51.9          ← km/h

# 内輪差: 前の内輪の半径 6 m、ホイールベース 2.7 m、トレッド 1.55 m で 90° 曲がった所(弧長 = 6 × π/2)と定常
o = fs.ledger.offtracking_circle(6.0, 2.7, 6.0 * 3.141592653589793 / 2, track=1.55)
print(round(float(o["offtracking"]), 2), round(float(o["steady"]["offtracking"]), 2))
# 0.68 0.74    ← 90° の所 / 定常 [m]

# クロソイド: 半径 100 m・緩和区間 40 m を 50 km/h で走るときの横加速度の変化率 [m/s³]
c = fs.ledger.clothoid_design(100.0, 40.0, speed=50 / 3.6)
print(round(c["lateral_jerk"], 3))
# 0.67         ← 解説の許容 0.5〜0.75 の中

# 車線の中央から右へ 0.2 m、向きのずれ 0.01 rad、直線を 15 m/s: 1.75 m 先の線を越えるまでの時間 [s]
print(round(fs.ledger.time_to_line_crossing(0.2, 0.01, 0.0, 15.0, line_offset=1.75), 2))
# 10.33         ← s

PoC 全体は py -3.11 examples/poc_driving_lateral.py(図と動画は FULLSEYE_FIGURE_DIR を設定したとき)。


第 11 回: 踏切と交差点の優先 —— 直前で止まって左右を見る、警報の間は入らない、向こう側が詰まっていれば入らない、広い道へは譲る

車載カメラ: 踏切の直前で止まり、警報の間は待って、左右を見て一気に渡る

↑ 車載カメラ(右上 = 上から見た小窓)。停止線の直前で止まって左右を見て発進した直後に警報が始まり、踏切の手前で止まり直します。遮断かんが下り、列車(80 km/h)が過ぎ、かんが上がり切ってから左右を見て一気に渡ります。後半は、向こう側が詰まっているのに入って踏切の中で止まってしまう版です(向こう側の余地 2.5 m < 必要 6.0 m、道交法 50 条 2 項)。MP4

教則の台帳の未着手から、ルールベースの部品で再現できる場面をまとめて拾いました —— 踏切(直前で一時停止して左右を確かめる・警報機が鳴っている間は入らない・向こう側が詰まっていれば入らない・踏切の中はやや中央寄りを)、交差点の優先(広い道路へは徐行して譲る・同じくらいの幅なら左方優先)、横断歩道(手前に止まっている車の横を通る前の一時停止・30 m 以内の追越し禁止)、駐停車禁止の距離(交差点と横断歩道の前後 5 m、踏切の前後 10 m、バス停 10 m)。

部品はすべてルールベースで、乱数で載せる値には前の回と同じ「標本の門」(参照分布の両側 0.1 % 点で 1 個ずつ落とし理由を記録、集団は KS)をかけています。

場面と門

新しいモジュール drivecrossing(17 op)と PoC ㉜ です。

場面 真値の出どころ 成績
踏切の時刻 鉄道に関する技術上の基準を定める省令の解釈基準(遮断機つき: 警報→遮断 15 s・遮断→到達 20 s、最小 10 / 15 s) 線区の速さ以下の列車は最小を満たす。始動点を固定すると警報の長さが列車の速さで 39〜120 s とばらつき、速さで始動を制御すると 35.0 s に揃う。固定の始動点が最小を割る速さは閉形式で 152 km/h
踏切を渡る(S120・S122・S123) 道交法 33 条 1・2 項、50 条 2 項の判定と、遮断機の状態機械 ルールの 240 人は違反 0、全員が渡り切り、列車が着くとき線路の上 0 人(余裕 最小 31.5 s)。止まらない版 no_stop 79、確かめない版 no_look 240、警報中でも入る版 157(状態機械で数えた 157 と一致、うち列車が着くとき線路の上 23 人)、詰まっていても入る版 no_exit_room 10・踏切の中で止まった 7
見通しの悪い踏切 見通しの三角形の閉形式 見える距離 閉形式 19.03 m / 光線の総当たり 19.07 m。見えない列車が渡り切る前に着く確率は、建物ありで 7.9 × 10⁻³、確かめ直さないと 2.1 × 10⁻²、見通しがよければ 0。閉形式・素朴な MC・重要度サンプリングが最大 1.1σ で一致(重要度サンプリングは同じ精度を約 1/56 の試行で)
交差点の優先(S098・S099) 36 条 1〜3 項の判定と、交差車の到達時間 ルールは譲るべき車に減速を一切させない(1 ms 刻みの再生で衝突の領域の重なり 0)。素朴な運転(30 km/h のまま)は進行妨害 104 / 240 場面。左方優先では右から来る車が譲った場面が 50 あり、「全部に譲る」だけの答えではない
横断歩道(S043・S044) 38 条 2・3 項の判定 停止車両の横で、素朴な運転は違反 240・隠れていた歩行者に触れた 2。30 m 以内で車の前に出る違反 34。自転車を抜くのは除外どおり違反にならない
駐停車禁止(S105〜S109) 44 条 1 項の号ごとの距離を 1 mm 格子で直接塗った答え 禁止区間は 340,001 格子点で一致。止まりたい所にそのまま止まると違反 476 / 1000(6 種類すべて)、ルール(いちばん近い適法の場所)は 0、余分に歩くのは平均 3.5 m
警報灯を画素から読む 灯の交互点滅(毎分 50 回)とカメラの fps の折り返しの式 0.833 Hz、2 灯の位相差 3.14 rad(交互)。3 コマに 1 コマへ間引くと 0.500 Hz = 折り返しの式

踏切を上から 3 方針で比べる

↑ 上から。左 = ルール、中 = 向こう側が詰まっていても入る、右 = 警報中でも入る。違反した車は、この 1 本では列車より先に渡り切っていますが、240 人のうち警報中に入った 23 人は列車が着くとき線路の上にいました。MP4

交差点の優先

↑ 広い道へ出る交差点。左 = ルール(徐行して譲る、交差道路の車に要った減速 0 m/s²)、右 = 素朴(30 km/h のまま、交差道路の車に 5.1 m/s² の急な減速をさせる。赤 = 急な減速)。MP4

横断歩道の手前の停止車両

↑ 横断歩道の直前に止まっている SUV の横を通る前に一時停止し、陰から渡ってくる歩行者を待ちます(38 条 2 項)。後半は 40 km/h のまま横を抜ける版で、見えてから止まろうとしても歩行者に触れます。MP4

★ 実装すると、ここで間違える

向かないこと

動かす

import fullseye as fs

# 踏切の時刻: 警報 t = 0、遮断 t = 15 s、列車の到達 t = 35 s は鉄道の解釈基準の最小(10 s / 15 s)を満たすか
r = fs.ledger.crossing_timing_check(0.0, 15.0, 35.0)
print(r["warn_to_closed"], r["closed_to_arrival"], r["meets_minimum"])
# 15.0 20.0 True          ← 警報→遮断 / 遮断→到達 [s]

# 止まった所から踏切の端まで 3.5 m、踏切の長さ 10 m、車長 4.5 m: 発進して渡り切るまでの時間 [s]
c = fs.ledger.crossing_clear_time(3.5, 10.0, 4.5, accel=1.5, v_max=5.56)
print(round(c["time"], 2))
# 5.18         ← s(発進して後端が向こう側の余裕を越えるまで)

# 見通しの悪い踏切: 角の建物に遮られて、線路の先が何 m まで見えるか(見通しの三角形の閉形式)[m]
print(round(fs.ledger.sight_triangle_distance(-1.13, 5.55, 4.5, 4.0), 2))
# 19.03         ← m

# 幅 4 m の道から幅 7 m の道へ: どちらが譲るか(道交法 36 条)
p = fs.ledger.priority_rule({"width": 4.0}, {"width": 7.0})
print(p["yield_to"], p["must_slow"])
# cross True        ← 交差道路の車に譲り、徐行する

# 駐停車禁止の区間(44 条): 横断歩道 54〜58 m と踏切 100〜110 m の前後
z = fs.ledger.no_stopping_zones([{"kind": "crosswalk", "start": 54.0, "end": 58.0},
                                 {"kind": "railway_crossing", "start": 100.0, "end": 110.0}])
print(z["merged"].tolist())
# [[49.0, 63.0], [90.0, 120.0]]   ← 前後 5 m・10 m を足した禁止区間 [m]

PoC 全体は py -3.11 examples/poc_driving_crossing.py(図と動画は FULLSEYE_FIGURE_DIR を設定したとき)。


第 12 回: 追越しと見えない所 —— 見通しが足りなければ待つ、ルームミラーに映ってから戻る、環道の車を妨げない、カーブミラーは遠く見える

運転席から見たカーブミラー: 鏡の中の車は 130 m 以上先に見えるが、本当は約 30 m

↑ 見通しの悪い T 字路のカーブミラー(凸面鏡 R 3 m・直径 0.8 m、仮定)。左 = 運転席から見た鏡(鏡の中は光線追跡で描いた)、右上 = 運転席の眺め、右下 = 上から見た本当の位置と映る範囲。鏡の中の赤い車を、平面鏡のつもりで像の大きさから読むと 130 m 以上先に見えますが、本当は鏡から約 30 m です。斜めに見る鏡なので、縦の大きさの読み(k_s = 4.75)と横の大きさの読み(k_t = 8.58)が別の値になります。最後に車は塀の陰から直接見えてきます。MP4

教則の台帳の未着手から、ルールベースの部品で再現できる「追越し」と「見えない所」の場面を拾いました —— 追越し(見通しが要る距離 D* に足りなければ待つ・禁止の場所では追い越さない・ルームミラーに前の車の全体が映ってから戻る)、追い越される側は速さを増さない、進路変更で後続車に急ブレーキをさせない、環状交差点(環道の車を妨げずに徐行で入る・出口の 1 つ手前の出口を過ぎたら左の合図)、坂の頂上の見通し、そしてカーブミラー。

カーブミラーは、前回までの車のミラー(仮想カメラ)の部品をそのまま道に立てたものです。「凸面鏡は遠く見える」はよく言われますが、どれだけ遠く見えるかは閉形式で出せます。

場面と門

新しいモジュール drivepass(17 op)と PoC ㉝ です。

場面 真値の出どころ 成績
追越し(S079・S081・S086) 28 条 4 項(対向車の妨げ)・30 条の禁止区間を 0.25 m ごとに当てた総当たり、D* の閉形式 = 1 ms の時間の行進 規則の 240 人は 188 人が追い越し、禁止区間 0、対向車との PET 最小 5.62 s、戻りの車間 = ルームミラーの車間 19.75 m(Fermat の最短経路の走査 19.74 m)。素朴(見えなければ出る・抜いたらすぐ戻る)は禁止区間 18、PET < 2 s が 44、対向車線で出会う(PET < 0)30、240 人全員がミラーの車間より前で割り込む
追い越される側(S085) 27 条 1 項 一定の速さの前の車は違反なし、追い越されながら 0.4 m/s² で加速すると speed_increased
進路変更(S067・S072) 26 条の 2 第 2 項、後続車に要る減速度の閉形式 = 2 台の時間の行進 閉形式の減速度で走ると最小車間が残す車間にちょうど(ずれ ≤ 0.0055 m)、0.97 倍に弱めると全件で割る。規則は 219 場面で変え、21 場面は後続車の理由で待つ。素朴は合図の遅れ 240、急ブレーキ 21
環状交差点(S069・S102) 37 条の 2・53 条、円周の時間の行進 規則は進行妨害 0・徐行、待った場面 44。左の合図の時刻 = 「出口の 1 つ手前の出口の通過」を外積で数えた再生と一致(ずれ 0.00 s)。素朴は進行妨害 38 / 240、合図の遅れ 240、右の合図 76
坂の頂上(S065・S081) 道路構造令(目 1.2 m・物 10 cm、凸形縦断曲線の半径)と 19 条の視距の表 表の 3 行を再現(20.0 / 20、161.0 / 160、209.4 / 210 m)。PoC の道の頂上は閉形式 89.28 m / 見通し線の走査 89.25 m。見通しが D* に届かない区間は 522 m あり、30 条の「付近」(30 m、仮定)が覆うのはその 60 m だけ —— 残りは 28 条 4 項の判定で止める
カーブミラー(S064) 凸面鏡の結像の閉形式 vs 3 次元の厳密な光線追跡 正面から見ると、大きさから読む距離 = k·a(k = 1 + 2e/R)。R 3 m を 8 m 離れて見ると、30 m 先の車が 190.0 m 先に見える(光線追跡 190.04 m)。45° の斜めでは Coddington の式どおり縦 142 m・横 257 m に分かれる。道の上で映らない手前の死角 8.16 m(光線 20,001 本の走査)

追越し: 規則と素朴を同じ運転者・同じ対向車で比べる

↑ 同じ運転者(#114)と同じ対向車の流れで。前半 = 素朴: 頂上の 144 m 手前で、見えている範囲に対向車がいないので出て、抜いたらすぐ戻る(車間 0.1 m)—— 坂に隠れていた対向車との PET 0.8 s。後半 = 規則: 見通しが D = 467 m に届かない間は待ち、頂上を 30 m 越えた所で出て、ルームミラーに前の車の全体が映る車間 19.7 m で戻る —— PET 31.1 s。上 = ルームミラー、下 = 横から見た縦断(高さを誇張)。MP4*

環状交差点

↑ 環状交差点で右折(出口を 2 つ通り過ぎる)。左 = 規則(環道の車を待って徐行で入り、黄の点 = 1 つ手前の出口を過ぎた所で左の合図)、右 = 素朴(そのままの速さで入って環道の車に急な減速をさせ、右の合図を出し、左の合図は出口で)。MP4

★ 実装すると、ここで間違える

向かないこと

動かす

import fullseye as fs

# 60 km/h で 40 km/h の車を追い越す(加速 1.2 m/s²、上限 80 km/h)。対向車 60 km/h が来ないために要る見通し D* [m]
r = fs.ledger.overtake_requirement(60 / 3.6, 40 / 3.6, lead_length=4.5, ego_length=4.7, gap_back=12.0, gap_front=15.0,
                                   accel=1.2, v_max=80 / 3.6, lane_change_time=3.0, v_oncoming=60 / 3.6, pet_min=2.0)
print(round(r["d_required"], 1))
# 308.8         ← m(対向車が来ないために要る見通し)

# ルームミラーに前の車の全体が映るまで戻らない: そのときの車間 [m]
print(round(fs.ledger.overtake_return_gap(lane_offset=3.25, lead_width=1.8)["gap"], 2))
# 19.75        ← m

# 坂の頂上(+4 % → -4 %、縦断曲線 160 m): 目 1.2 m から高さ 10 cm の物が見える距離 [m]
print(round(fs.ledger.crest_sight_distance(grade_in=0.04, grade_out=-0.04, length=160.0)["sight"], 2))
# 89.28        ← m

# カーブミラー(凸面鏡 R 3 m)を 8 m 離れて見る: 30 m 先の車は、像の大きさから読むと何 m 先に見えるか
m = fs.ledger.convex_mirror_image(30.0, 3.0, eye_distance=8.0)
print(round(float(m["k"]), 3), round(float(m["flat_equivalent_distance"]), 1))
# 6.333 190.0  ← k と、平面鏡なら何 m 先に見える大きさか

# 環状交差点(4 本の枝、右回り)で入口 0 から出口 1 へ(出口を 2 つ通り過ぎる): 左の合図を出す角(右回りに進んだ角)[rad]
import math
sp = fs.ledger.roundabout_signal_point([0.0, math.pi / 2, math.pi, 3 * math.pi / 2], 0, 1)
print(round(sp["signal_angle"], 4), sp["exits_before"])
# 3.1416 2  ← π = 2 つ目の出口を過ぎた所、通り過ぎる出口 2

PoC 全体は py -3.11 examples/poc_driving_pass.py(図と動画は FULLSEYE_FIGURE_DIR を設定したとき)。

第 13 回: 横断歩道を渡るヒューマノイド —— 車の物理は正確なまま、歩く側は安く描く

横断歩道を渡る 7 機種のヒューマノイド(マスク付きの事前描画)

↑ MuJoCo Menagerie の実在する人型ロボット 7 機種が横断歩道を渡る場面を、車載カメラの位置から撮った。1 体ずつ、向き 16 段 × 歩行の位相 12 段の絵を先に描いておき、毎コマはその絵を縮めて貼る。手前の物との前後は深度で比べる。MP4

これまでの回の歩行者は、箱 3 つと頭 1 つの手続き的な形でした。今回は、その代わりに実在のヒューマノイドを歩かせます。

自動運転の試験で正確な物理が要るのは車です。周りを歩く物体は、動きが自然に見えて、当たり判定が外形の箱で取れれば足ります。ところが実在ロボットの見た目のメッシュは重く、G1 は 1 体で 393,270 三角形。1 体 30,000 三角形まで減らしても、10 体を置くと 1 コマの描画に地面だけの 4.5 倍の時間がかかります。そこで、歩く側の見た目を 2 通りの方法で軽くしました。

場面と門

新しいモジュール drivehumanoid(台帳 4 op と fs.humanoid_walk_clip)と PoC ㉞ です。

部品 真値の出どころ 成績
歩行 1 周期 毎コマ接地する・前へ進む・支持脚が地面で滑らない 7 機種すべてで成立。1 周期で進む距離は 0.47〜0.94 m
間引いたメッシュ 格子にまとめた点は同じ升の中にある(ずれ ≤ √3 × 格子幅) 人型 1 は 393,270 → 1,497 三角形、格子 5.5 cm、最大のずれ 6.4 cm(上限 9.5 cm)
マスク付きの事前描画 同じ姿勢を直接メッシュで描いた像 向きと位相が段にちょうど乗るとき、6〜12 m で一致度(IoU)0.984・0.991・0.990・1.000。手前に壁を置くと 612 画素 → 0 画素
1 コマの費用(640 × 400、10 体) 地面だけを描く時間 事前描画 1.02 倍、間引いたメッシュ 1.23 倍、精細なメッシュ(1 体 30,000 三角形)4.50 倍

同じ姿勢を 3 通りで描く

↑ 人型 1 を同じ姿勢で 3 通りに描いた。左が精細なメッシュ、中が 1,497 三角形まで間引いたメッシュ、右が事前描画。中では首の部品が格子に潰れて消え、頭が浮いて見える(一致度 0.77)。右は、向きと位相を段に丸めたままでも見た目を保つ(一致度 0.85)。

7 機種の間引きの表

↑ 7 機種の見た目のメッシュを、1 体 1,500 三角形の予算で間引いた結果。元が 155 万三角形ある人型 7 は格子が 16.2 cm まで粗くなり、最大のずれは 14.1 cm。

★ 実装すると、ここで間違える

向かないこと

動かす

import os
import fullseye as fs

# Menagerie の人型(一覧の 1 機種目)から歩行 1 周期を作る(関節の役割は名前でなく体の形から決める)
import drivehumanoid
d, f = drivehumanoid.MENAGERIE_HUMANOIDS[0]
clip = fs.humanoid_walk_clip(os.path.join(os.environ["FULLSEYE_MENAGERIE_DIR"], d, f))
print(clip["decimation"]["tris_before"], clip["decimation"]["tris_after"], round(clip["cycle_length"], 2))
# 393270 1497 0.75   ← 元の三角形、間引いた後、1 周期で進む距離 [m]

# 0.5 m 歩いたときの形(腰の真下が原点、+x が前)
m = fs.ledger.humanoid_clip_mesh(clip, 0.5)
print(m["V"].shape)
# (634, 3)

# 向き 16 段 × 位相 12 段のマスク付きの絵を先に描いておく(毎コマは world_camera_impostors で貼る)
imp = fs.ledger.humanoid_impostors(clip, n_yaw=16, n_phase=12, res=128)
print(imp["color"].shape)
# (16, 12, 128, 128, 3)

PoC 全体は py -3.11 examples/poc_driving_humanoids.py(図と動画は FULLSEYE_FIGURE_DIR を設定したとき)。


次回

残りの台帳。 教則の台帳はまだ 97 場面が未着手です。今回 op の判定だけ作って PoC の場面で起こしていない二重追越し・右折しようとしている車の追越し・黄の線の進路変更・高速道路の追越しを場面に入れ、カーブミラーには自転車と歩行者を映します。

出典