Raspberry Piカメラを使って、いろんなものを検出し観察する。
そんなことをしばらくやっている。
YOLO検出。何かを見つけて認識する。まずはそこからだが、
そこから徐々に広げていくのが、このシステムのコンセプトである。
車通りの多い時間、人が通る時間 ── よくあるAIカメラの活用法だ。
しかしCOCOのモデルには、まだまだ認識できないものが多数ある。
特にRaspiカメラでは、不鮮明な画像からの物体検出が意外とうまくいかなかったりする。
自前で学習することも可能だし、以前に比べれば自分のデータセットを作るのも
だいぶ楽になってきている。そういうアプローチも残しつつ、
世界を少しずつ認知していくのがこのプロジェクトのコンセプトである。
今回はその一例として、YOLO vs Sekai-Eyeの対比デモを紹介する。
よくある、車の検出。しかし「上から」の視点での検出には案外弱かったりする。
> 例えばドローンで真下に向けた画像の物体検出。
> 学習済みモデルが「車という見た目」を必要とするのに対し、このシステムは
> **「変化」さえあれば把握できる。古典的CVと領域知識だけで、YOLOが取りこぼす交通流を拾うことができる。
今回のデモの題材は、ロータリーを真上から撮った空撮クリップ1本。ここで起きる「物体検出の敗北」と、その回避策としての変化駆動パーセプションである。

*真上視点のロータリー。赤=YOLO(yolov8n)、緑=Sekai-Eyeが動きで拾った車。YOLOは1、Sekai-Eyeは50。*
1. なぜ真上だとYOLOは検出できないのか?
YOLOやCOCO系の検出器は、**地上・目線レベルの写真**で学習されている。「車」の教師画像は、
側面や斜め前方からの、タイヤ・フロントガラス・ボンネットが見えるものがほとんど。
ところが真上から見た車は ただの長方形の屋根。タイヤもガラスも見えない。
モデルが学んだ「車らしさ」の特徴がほぼ消えるため、検出されにくいといわけだ。
実際、この4Kクリップを各フレームYOLOに通すと、検出される車両は常時0〜2台。
しかも時々、赤い屋根の建物を「車」と誤検出している(下図左下の赤枠)。
真上視点では、検出器は車を見失うどころか、屋根を車と取り違える。
2. 複数の画像で「変化」を見る
Sekai-Eyeの設計思想。
静物を認識したいという時もあるが、このシステムは小さな変化をとらえるというところに重きをおいている。
そこで動きに着目した実装を盛り込んだ。これが変化駆動パーセプションである。
パイプラインは古典的CVのみ。学習パラメータ0、GPU不要、CPUで回る。
フレーム列 → 背景差分(MOG2) → ロードマスクで路面に限定 → サイズ制限 → 連結成分=車 → カウント
2-1. 背景差分(MOG2)
python
mog = cv2.createBackgroundSubtractorMOG2(history=120, varThreshold=24, detectShadows=False)
fg = mog.apply(frame, learningRate=0.5 if first else 0.02)
動いている車だけが前景に出る。**駐車車両は背景に溶けて消える**ので、
「実際に動いている交通」だけがクリーンに残るのが嬉しい点です。
ただしこれだけでは、木々の揺れ・水面のゆらぎ・ドローンの微小なドレ(撮影ブレ)・
JPEG圧縮ノイズまで前景化し、1フレームで**400以上の偽ボックス**が出てしまうことに
3. ロードマスク:誤検出との戦い
「車は道路の上にいる」── この当たり前の**領域知識(ワールドの法則)**を効かせる。
路面以外の動きは無視する。問題は「どうやって路面だけを取り出すか」。
3-1. まずはHSVマスク(失敗)
最初は「アスファルト=灰色=低彩度」でマスクを作りました。
python
road = (S < 42) & (V > 55) & (V < 200) # 灰色=低彩度・中明度
結果はイマイチ。**乾いた茶色の畑・ベージュの建物屋根も低彩度**なので一緒に塗られ、
さらに**ロータリー中央の星形モニュメント**まで路面扱いになり、その高コントラスト模様が
ドローンの微小ドレで誤発火。色だけでは路面と非路面を分離できない。
3-2. sekai-eye仕様:連結フラッドフィル(採用)
そこで本体の道路検出 `refine_road_bucket` に切り替えました。位置づけは
**「Photoshopの“隣接”バケツ塗りの内部実装」**です。
- 人が道路の概形ポリゴンを指す(**人間確認**)
- そのポリゴン内部をシードに、**連結した同色アスファルトだけ**を `cv2.floodFill` で塗る
- 膨張帯(margin)の外は塗り禁止にして暴走を防ぐ
python
def refine_road_bucket(bgr, polygon, tol=12, margin=38):
region = _mask(bgr.shape, [polygon])
band = cv2.dilate(region, kernel(margin)) > 0 # 塗ってよい帯
seed = cv2.erode(region, k9) > 0 # 縁を避けた内部コアから種をまく
for (x, y) in sample(seed):
cv2.floodFill(img, ff, (x, y), 0, (tol,)*3, (tol,)*3,
4 | (255 << 8) | cv2.FLOODFILL_MASK_ONLY)
return grown
**色の連結性**で塗るので、
- 離れた屋根・遠くの畑には**飛び火しない**(非連結だから)
- 砂利・草・畑との**色境界で自然に止まる**
「色が似ているが繋がっていない」ものを除外できるのが、単純な色マスクとの決定的な差である。
とはいえ、マスクの作成はまだ未完成なので必要に応じて手動で作った方が良い場合もあるかもしれない。
このシステムの特徴は、人が介在して、育てるというコンセプトで作っている。
もちろん全自動で行ければ全自動で良いのだが、どうせまだまだ試行錯誤中なので、苦手なところは人間が補助すれば良いというコンセプトで進めている。
3-3. 中央モニュメントは「アヌラス(ドーナツ)」で穴抜き
ロータリーの走行リングだけを指し、中央の星形モニュメントを外すために、
道路帯ポリゴンを**楕円アヌラス**で定義しました。
python
def _ellipse_annulus(cx, cy, rx_o, ry_o, rx_i, ry_i, n=48):
outer = [楕円(外周)]
inner = [楕円(内周, 逆回り)]
return outer + inner + [outer[0]] # fillPolyで中抜きの円環になる
これでシードが中央島に落ちず、フラッドフィルもモニュメントへ侵入せずにすむ。
3-4. 正直な限界
万能ではありません。このクリップでは乾いた畑・砂利広場がアスファルトとほぼ同色で、色連結が一部それらを越えて漏れます(`tol` を下げて緩和)。
俯瞰・低コントラスト素材ではこの種の「色が近すぎて分離できない」場面が必ず残ります。
ただし動きのない領域なので背景差分が発火せず、次のサイズ制限と合わせれることで問題を緩和している。
4. サイズ制限:1台=1ボックスに寄せる
最後に、断片化と過大ブロブを処理してしまうので対策。
- **断片マージ**:1台が複数ブロブに割れるので、ブロブを描画→膨張→再輪郭で連結
- **サイズ制限**:乗用車 ≈ 25×40px(面積~1000)。上限を**その約3倍**(バス/トラック相当)に。
これ超えは「複数台の結合・影・揺らぎ」として棄却
python
CAR_AREA = 1000
MAX_AREA = CAR_AREA * 3 # 乗用車の約3倍まで
MAX_DIM = 80
if not (6 < w and 6 < h and max(w, h) <= MAX_DIM and 30 < w*h <= MAX_AREA):
continue # 微小ノイズと過大ブロブを両端で落とす
この一手で、検出が**乗用車サイズで均一**になり、数が「ノイズの水増し」ではなく
「実際に動いている車の台数」として読める値になった。
**偽ボックスの推移:476 → 130(ロードマスク)→ 66(断片マージ)→ 50前後(サイズ制限)**
5. 結果
| YOLO (yolov8n) | Sekai-Eye(変化駆動) | |
| 検出台数 | 0〜2 | 50〜100 |
| 誤検出 | 赤い屋根を車と誤認 | 路面外を除外済み |
| 学習 | COCO(目線レベル) | 学習なし・古典的CVのみ |
| 計算 | GPU想定 | CPUで十分 |
真上視点で、汎用検出器は車を「屋根」に潰されて見失う。
一方、変化駆動パーセプションは見た目を問わず動きで交通流を追い続けることができる。
これは「大きい物体はYOLOで取れるが、点になるまで遠ざかると消える。Sekai-Eyeはそこで終わらない」
という、俯瞰観測の本質的な強みの可視化です。
6. 設計上の学び(CV実務者向け)
1. 巨大単一モデルがいつも正解ではない。俯瞰・ぼかし・微小という条件では、
学習済み検出器の前提(見た目)が崩れる。タスクの幾何を疑え。
2. 「認識」と「変化検出」は別物。対象が何に見えるか不明でも、
時系列の変化なら拾える。問題を「検出」から「変化+領域知識」に組み替える。
3. 領域知識は色でなく連結性で表現する。「低彩度=路面」は屋根・畑に漏れる。
「シードから繋がった同色」なら非連結の偽陽性を構造的に排除できる。
4. 人間が指し、機械が塗る。完全自動を狙わず、人が道路の概形を1回指すだけで、
バケツ塗りが実体に沿って広げる。ヒューリスティックと人間確認のハイブリッドは強い。
5. 数の誠実さ。断片やノイズを足した「盛った数字」は逆効果。
サイズ制限と1台=1ボックス化で、見た人が信頼できる値にする。
付録:再現方法
bash
cd demo
# ロードマスク単体検証(demo/roadmask_*.jpg を出力)
python3 road_detect.py# デモ動画の生成(各フレームでYOLO APIを叩く)
python3 render_topdown.py --start 40 --n 260 --step 2
# 出力: demo/sekai_vs_yolo_topdown.mp4
| ファイル | 役割 |
|---|---|
| `render_topdown.py` | レンダラ(MOG2+ロードマスク+サイズ制限+YOLO比較+ZOOM) |
| `road_detect.py` | sekai-eye仕様ロードマップ検出(本体 `refine_road_bucket` を利用) |
| `roadmask_*.jpg` | ロードマスク検証画像 |
| `sekai_vs_yolo_topdown.mp4` | デモ動画 |
**素材**:Pexels 1472013(Tom Fisk, Pexels License・商用可/帰属不要)。
真上ナディアのドローン空撮。
Sekai-Eyeの設計思想は「外部の正解に頼らず、自分の目で見て理解する」。
天文APIを使わずとも、明るさで日の出を知り、気象APIでなく鮮明度で雨を把握、
なんだったら、風の強度も木々の揺れで把握。
YoLOも使用するが、そこで終わりではなく、さまざまな要素を繋げて、ハイブリッドな仕組みとしている。
物体検出でなく**動きで交通を知る**。
引き続き、観察対象を広げており、徐々に把握できる対象やシーンは増えているので
また機会があれば紹介したいと思う。

赤がYOLO、緑の枠がsekai-eyeということで、Yoloがダメでもsekai-eyeがあるじゃない。という事である。





