0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

RON-V #23 — Raspberry Pi 5の4モデル画像AIを、根拠画像・Grafana・Google Driveへつなぐ

0
Last updated at Posted at 2026-10-21

RON-V 連載リンク
← 前の記事:#22補足 — Kaggleでの学習・保存手順
→ 次の記事:#24 — 実機構成・UPS・バックアップ

実機確認の記録(2026-10-20)

#22で構築した道路・災害画像AIをRaspberry Pi 5へ載せた記録です。撮影と推論に加え、AI結果をL3、根拠画像をL4へ対応付け、Google Drive転送とSheetsへの記録まで2台の代表イベントで確認しました。Grafana用のダッシュボードも配置しました。ここでは、実機で確認した範囲と再確認の手順を説明します。

はじめに

AtomS3Rのボタン操作や衝撃検知をきっかけに画像を保存し、道路・冠水・煙・視界の4モデルで解析します。その結果を、根拠画像と結び付けて保存・転送できるようになりました。

#22はAIの構築方針、#22補足はKaggleでの学習・変換・保存を説明します。この記事は、その後のONNXをPiへ置く → 設定する → BUTTON / SHOCKで自動推論する → 結果と画像を観測データとして扱う流れを扱います。10節以降に、L3/L4への登録、Grafana、Drive、Sheetsの確認をまとめます。

前提は、RON-Vのコード、Pythonの仮想環境、AtomS3Rからのイベント受信、RingCamが既に動いているPiです。4つのONNXは、自分で保存したモデルや利用可能なバックアップから用意します。GitHubを取得するだけではモデル本体はそろいません。リポジトリは記事作成時点で非公開なので、読む権限も必要です。

順番 作業 確認するもの
1 ONNXをPiへ配置する 4モデルのファイルが models/ 以下にある
2 同じ仮想環境でONNX Runtimeを読み込む onnxruntime / numpy / Pillow をimportできる
3 config/ronv.toml の [vision] を設定する 有効化、モデルのパス、confidence
4 保存画像で手動実行する vision_result.json と4モデルそれぞれの status
5 BUTTON / SHOCKで自動実行する 新しいイベントの画像・撮影記録・推論結果
6 もう1台にも同じ構成を配置する コードの版、モデルの照合値、設定、各機の動作
7 AI結果を観測・表示・転送へつなぐ L3の結果とL4の画像の対応、Drive転送、SheetsのINDEX、Grafanaの表示用データ

設定例は6節、保存画像での再確認は4節、自動実行は5節で説明します。1〜9節には最初のAI展開時の記録も残し、10〜13節で10月19〜20日の更新結果を説明します。以下は既存のRON-V環境への追加手順です。OSや各観測機能をゼロから構築する手順ではありません。

RON-VではRTL-SDR、LTE、GNSS、CAN、カメラなど複数の処理を同時に扱うため、
画像AIを常時推論させるのではなく、イベントが発生したときだけ実行する構成にしました。

今回の最終構成は次です。

profile task threshold
road-v02 fallen_tree / road_debris / pothole 0.20
flood-v05 flood 0.50
smoke-v01 smoke 0.20
visibility-v02 clear / low_visibility top-1

1. イベント駆動にする

RON-Vでは AtomS3R が車両側イベントを Raspberry Pi へ送ります。

処理 入力 保存・次の処理
RingCamが画像を蓄える USBカメラの画像 data/ringcam/buffer/
AtomS3Rからイベントを受信 BUTTON / SHOCKのJSON data/camera/triggers/ に撮影要求
撮影要求を処理 要求とRingCamの画像 data/ringcam/events/<event_uid>/ に画像と撮影記録
4モデルで推論 代表画像 event.jpg 同じイベントフォルダーへ vision_result.json

BUTTONでは代表画像1枚、SHOCKではイベント時刻に対する−3、−2、−1、0、+1、+2秒の計6枚を保存します。AIへ渡すのは0秒に対応する event.jpg です。狙った時刻の画像がなければ、実装は近い画像を選び、実際に選んだ時刻を capture_manifest.json に記録します。

RingCamによる画像の蓄積と、AIの実行タイミングは別です。画像を蓄えておき、撮影要求の処理で代表画像を保存した後、AIがその1枚を解析します。

2. モデル本体はGitHubへ入れない

ONNXモデルは数MB〜十数MBあり、学習を繰り返すと履歴も急速に大きくなります。

そのため、保存物を次のように分けました。

保存先 保存するもの
GitHub 推論コード、設定例、学習・変換スクリプト
別のバックアップ best.pt、ONNX、モデルをまとめたZIP

公開可否の判断は#22の説明を参照します。

.gitignore では models/ をGit管理対象外にしています。

実機では、/home/ronv/ronv/ を基準に次の場所へ配置します。

モデル ONNXの配置先
道路 models/road-v02/road-v02.onnx
冠水 models/flood-v05/flood-v05.onnx
煙 models/smoke-v01/smoke-v01.onnx
視界 models/visibility-v02/visibility-v02.onnx

#22補足のモデルZIPは、選択したモデルだけを含みます。道路だけのZIPでは4モデル構成はそろいません。ZIPをPCなどで展開し、表のONNXをPiへコピーします。models/models/... のように二重の階層へ入れないよう確認します。

Piのターミナルで、配置後に確認する例です。

cd /home/ronv/ronv
ls -lh models/road-v02/road-v02.onnx models/flood-v05/flood-v05.onnx models/smoke-v01/smoke-v01.onnx models/visibility-v02/visibility-v02.onnx
sha256sum models/road-v02/road-v02.onnx models/flood-v05/flood-v05.onnx models/smoke-v01/smoke-v01.onnx models/visibility-v02/visibility-v02.onnx

照合値は保存元のONNXのSHA256と見比べます。2台へ置く場合も同じ値かを確認します。

3. Raspberry Pi側はONNX Runtime

RONV1 / RONV2 では Python venv 内に ONNX Runtime を入れています。

確認した構成:

numpy       2.5.3
onnxruntime 1.31.0
Pillow      12.3.0

入力 / 出力 shape は次の通りです。

road-v02
  input  [1, 3, 640, 640]
  output [1, 7, 8400]

flood-v05
  input  [1, 3, 640, 640]
  output [1, 5, 8400]

smoke-v01
  input  [1, 3, 640, 640]
  output [1, 5, 8400]

visibility-v02
  input  [1, 3, 224, 224]
  output [1, 2]

3本の検出モデルと、1本の分類モデルを同じイベント処理へまとめています。

自動実行のserviceが使用するPythonは /home/ronv/.venvs/ronv/bin/python です。別のPythonへインストールしても、このserviceでは使われません。現在の環境を確認する例です。

/home/ronv/.venvs/ronv/bin/python -c "import numpy, onnxruntime, PIL; print('numpy', numpy.__version__); print('onnxruntime', onnxruntime.__version__); print('Pillow', PIL.__version__)"

このimport確認だけではモデルの推論まで確認したことにならないため、次の保存画像での実行へ進みます。

4. event_infer.py で4モデルを順番に実行

実装の中心は次のファイルです。

app/vision/event_infer.py
app/vision/multi_onnx.py
app/vision/road_onnx.py

イベントディレクトリの同じ event.jpg を4モデルへ渡し、
結果を vision_result.json に保存します。

出力例:

{
  "schema": "ronv.vision.event.v2",
  "status": "ok",
  "hazard": false,
  "detection_count": 0,
  "models": {
    "road-v02": {
      "status": "ok"
    },
    "flood-v05": {
      "status": "ok",
      "confidence_threshold": 0.5
    },
    "smoke-v01": {
      "status": "ok"
    },
    "visibility-v02": {
      "status": "ok",
      "top1": "clear"
    }
  }
}

同時に capture_manifest.json へも推論の要約を追記します。上のJSONは主要項目を抜粋した例で、実際の出力には画像パス、推論時間、検出枠、confidenceなども入ります。

保存画像で手動確認する(再確認用の追記)

まず6節の [vision] を設定します。既存の config/ronv.toml の該当部分を編集し、車両・カメラなど他の設定は残します。設定例全体への置き換えや、[vision] の二重追加はしません。

以下はPiのターミナルで実行します。環境変数 RONV_EVENT_DIR の値だけ、event.jpg がある実際のイベントフォルダーへ変更します。この再実行は、同じフォルダーの推論結果と撮影記録の推論要約を更新するため、以前の結果を残す場合は先にコピーします。

cd /home/ronv/ronv
export RONV_CONFIG=/home/ronv/ronv/config/ronv.toml
export RONV_EVENT_DIR='/home/ronv/ronv/data/ringcam/events/ここを実際のevent_uidへ変更'
/home/ronv/.venvs/ronv/bin/python -m app.vision.event_infer --event-dir "$RONV_EVENT_DIR"
/home/ronv/.venvs/ronv/bin/python -m json.tool "$RONV_EVENT_DIR/vision_result.json"
結果 意味・次に確認するもの
status=ok 4モデルすべての推論が成功。各モデルの結果も確認する
status=partial 一部だけ成功。models 内の error を確認する
status=error 全モデルで失敗。モデルのパス、画像、依存ソフトを確認する
VISION: disabled 読み込んだ設定でAIが無効。[vision].enabled と設定ファイルを確認する
hazard=false 成功した検出モデルが閾値以上の候補を返さなかった。道路の安全を保証する値ではない

現在のCLIはモデルの一部が失敗しても終了コード0で終わる場合があります。コマンド終了だけで成功とせず、JSONの status と各モデルの結果を確認します。

5. systemd path unitで撮影要求を拾う

RON-Vでは次を使っています。

unit 役割
ronv-ringcam.service USBカメラの画像をバッファへ蓄える
ronv-camera-trigger.path /home/ronv/ronv/data/camera/triggers に撮影要求が入ったことを検知する
ronv-camera-trigger.service 要求を処理して画像と撮影記録を保存し、その後AIを実行する

AIが専用の常駐serviceとして動く構成ではありません。カメラのイベント処理の中から呼び出します。

確認例:

systemctl is-active ronv-ringcam.service
systemctl is-active ronv-camera-trigger.path

正常時:

active
active

RONV1ではAI展開後、ronv-camera-trigger.path が inactive だったため、

sudo systemctl enable --now ronv-camera-trigger.path

で有効化しました。

手動確認が成功した後、AtomS3RのBUTTON操作で新しいイベントを作り、data/ringcam/events/ に新しいフォルダーができるか確認します。その中の event.jpg / capture_manifest.json / vision_result.json を確認し、続いてSHOCKイベントも確認します。

journalctl -u ronv-camera-trigger.service -n 80 --no-pager

撮影が成功した RINGCAM EVENT OK と、4モデルが成功した VISION EVENT: status=ok は別の確認です。AIで失敗しても撮影画像が残るようにしているため、画像が保存されたことだけで4モデルの成功とは判断しません。

6. RONV2で先に展開

最初に RONV2 へ4モデルを展開しました。

Floodは v02 から v05 へ置き換え、

[vision]
enabled = true

road_model = "/home/ronv/ronv/models/road-v02/road-v02.onnx"
road_confidence = 0.20

flood_model = "/home/ronv/ronv/models/flood-v05/flood-v05.onnx"
flood_confidence = 0.50

smoke_model = "/home/ronv/ronv/models/smoke-v01/smoke-v01.onnx"
smoke_confidence = 0.20

visibility_model = "/home/ronv/ronv/models/visibility-v02/visibility-v02.onnx"

としました。

4モデルの実イベント試験:

VISION EVENT:
status=ok
hazard=False
detections=0
visibility=clear

個別の推論時間は、おおむね detection 1本あたり 160〜200ms 程度でした。

確認時の4モデルの推論処理は約1秒以内でした。これは画像の保存後に測った推論時間です。SHOCKではイベント後の画像がそろうまで待つため、衝撃発生から全ファイルが完成する時間とは分けて考えます。

7. RONV1へ同じ構成を展開

RONV2で確認後、AI展開時点のマイルストーンとしてGitHubへ次のcommitを反映しました。

4ca5b56
feat: deploy flood-v05 hard-negative model profile

これはAI展開時点のcommitであり、現在のGitHub main はその後も更新されています。運用時は固定SHAではなく最新の main とnode auditを基準にします。

RONV1では、

git pull --ff-only origin main

を行い、

4ca5b56 (HEAD -> main, origin/main, origin/HEAD)
Already up to date.

まで確認しました。

モデル本体は runtime ZIP から別途配置しています。

RONV1での実イベント推論:

status       = ok
hazard       = False
detections   = 0
inference_ms = 761.5
ALL FOUR MODELS: OK

8. RONV1 / RONV2を同じbaselineへ

AI展開時点では2台とも同じGitHub baselineへ揃え、その後も共通 main を追従する運用にしました。モデルprofileは現在も次の4本です。

road-v02
flood-v05
smoke-v01
visibility-v02

BUTTON / SHOCK イベントも両機で発生させ、
一晩放置して長時間安定性を確認する段階へ進みました。

9. この構成の狙い

RON-VではAIそのものを主目的にしていません。

主役は、

  • RF
  • LTE
  • GNSS
  • CAN / OBD-II
  • Camera
  • Mesh

などの観測です。

AIは、記録した画像へ「道路障害物らしい」「冠水らしい」「煙らしい」「視界不良らしい」
という追加情報を与える役割です。

通信が使えるときは外部へ送れる。
通信が使えなくても Pi の中には観測結果が残る。

この設計を崩さず、AIもローカルで完結するようにしました。

10. AI結果をL3、根拠画像をL4へ登録する

vision_result.json は推論の出力です。RON-Vでは、そのファイルを作っただけで完了にせず、「どのイベントの、どの画像を、どのモデルで解析したか」を保管・参照できるようにします。

2026年10月19〜20日に使ったコードは cd0a94e です。この版では app/integration/vision_evidence.py がAI結果を読み込み、既存の画像保存・転送処理へ接続しています。

保存・参照先 入るもの 確認する目的
イベントフォルダー event.jpg、capture_manifest.json、vision_result.json 撮影と推論が実行されたか
L3:/var/lib/ronv/l3/vehicle/ AI結果を含む、版を付けたJSON 検出内容・モデル・画像との対応を残す
L4:/var/lib/ronv/l4/vehicle/ 保存した根拠画像 実際に何が写っているか確認する
SQLite:vision_latest 最新のAI要約と画像・結果の転送状態 履歴表示と転送確認に使う
Google Drive L3のJSONとL4の画像ファイル Piの外へ保管する
Google Sheets L3_INDEX / L4_INDEX と転送の記録 保存先と転送結果を探す

L3のAIファイル名には、イベントIDと内容のSHA256を含めます。再解析で結果が変わった場合は別のファイルにし、既に転送した内容を上書きしません。L4は既存の画像を参照し、入力画像の照合値と一致するか確認します。

新しく実行した推論には、入力画像とモデルファイルのSHA256も記録します。過去のJSONにモデルの照合値がなければ、後から現在のモデルの値で埋めません。過去にどのモデルを使ったか分からない状態を、そのまま区別して残します。

更新時に確認したこと

2台とも設定とSQLiteを退避してからGitHubのコードを取得しました。Python依存パッケージは既に導入済みで、今回の更新では pip による入れ直しは必要ありませんでした。Wi-Fiやノード別の設定をサンプル設定で上書きせずに進めています。

推論結果の来歴とL3/L4登録に関する16件のテストが通り、登録前の確認では20件が対象、エラーは0件でした。実際の登録も両機で20件、エラー0件を確認しました。これは登録処理の結果であり、全件のDrive転送完了を表す数字ではありません。

既存のイベントを登録する

以下は、モデル・撮影・推論が既に動き、上記の連携コードを取得済みのPiで使う例です。最初のコマンドは、ファイルと転送要求を作らずに対象を確認します。 表示用のテーブル・ビューの初期化は行われます。

(
    set -e
    cd /home/ronv/ronv
    export RONV_CONFIG=/home/ronv/ronv/config/ronv.toml
    /home/ronv/.venvs/ronv/bin/python - <<'PY'
import json
from app.common.config import load_config
from app.integration.vision_evidence import produce_vision

report = produce_vision(load_config(), max_events=20, dry_run=True)
print(json.dumps(report, ensure_ascii=False, indent=2))
raise SystemExit(1 if report['errors'] else 0)
PY
)

エラーがなければ、同じコードの dry_run=True を dry_run=False に変更して登録します。登録後は、既存の ronv-vehicle-evidence.timer により新しい撮影イベントも処理します。

sudo systemctl enable --now ronv-vehicle-evidence.timer
systemctl is-active ronv-vehicle-evidence.timer

繰り返しの登録で同じ結果の転送要求を増やさない設計です。ただし、破損した画像やJSONなどは errors を確認して対応します。

11. GrafanaでAI結果と根拠画像を参照する

grafana/dashboards/ronv-vision.json に「RON-V 画像AI・根拠画像・転送状況」のダッシュボードを追加しました。vision_latest などを使い、イベント時刻、AIの状態、検出件数、視界分類、AI結果と画像の転送状態を表示する構成です。

既存のGrafana環境へ、JSONを /var/lib/grafana/dashboards/ronv-vision.json として配置しました。画像へのリンクはRON-V Webを参照します。ダッシュボード変数 web_url の既定値はRONV1向けなので、RONV2では http://ronv2.local へ変更したコピーを配置しました。ノード名だけ変えても、Grafanaのデータソースやダッシュボード自動読み込み設定が未導入なら表示できません。

今回確認できたのは、SQLを使うテストと実機へのダッシュボード配置までです。 ブラウザー上での各パネル・画像リンクの最終確認は別に残っています。AI専用の地図や警報まで完成した、という意味ではありません。

挿絵を追加するなら、この位置に「イベント一覧・AI状態・転送状態が同時に見えるGrafana画面」を入れます。未確認の表示を完成画面として掲載しないようにします。

12. Google DriveとSheetsまで確認する

ファイルの登録後、ronv-rclone-delivery が転送し、その結果を ronv-google-sync がSheetsへ記録します。ネットワークが使えない間はPi内に残し、通信の回復後に送る構成です。

今回はDrive認証の更新が必要だった

2台ともDriveへの転送が invalid_grant で止まっていました。AI推論の失敗ではなく、rcloneの認証エラーでした。既存の認証設定を退避し、転送処理を止めてから、各ノードで rclone config reconnect を実行しました。

SSH接続先にブラウザーがないため、WindowsからSSHのポート転送で認証画面を開きました。Googleからのコード受領後、通常の個人用DriveではShared Driveの質問へ n と答え、フォルダーを一覧できることを確認しました。remote名はノードの設定に合わせます。RONV1のremote名をRONV2へそのまま流用しません。

認証手順は rclone公式のリモート認証 と reconnect を参照できます。トークンの実際の値や rclone.conf の内容は記事へ貼りません。

代表イベントで確認した結果

実機・確認日 イベントID 画像の転送 AI結果の転送 Sheetsへの記録時刻(JST)
RONV1・10月19日 171 acked acked 2026-10-19 21:29:58
RONV2・10月20日 131 acked acked 2026-10-20 19:34:31

イベントIDは各Pi内の番号で、同じ番号の画像を新しく作る必要はありません。手元では自分の最新イベントを確認します。

(
    set -e
    cd /home/ronv/ronv
    export RONV_CONFIG=/home/ronv/ronv/config/ronv.toml
    /home/ronv/.venvs/ronv/bin/python - <<'PY'
from app.common.config import load_config
from app.common.db import connect_db

con = connect_db(load_config())
try:
    for row in con.execute('''
        SELECT v.event_id, v.status, v.upload_state,
               v.image_upload_state,
               gx.exported_at_jst AS sheets_recorded_at
        FROM vision_latest v
        JOIN sync_objects o ON o.object_uid=v.object_uid
        LEFT JOIN google_sync_exports gx
          ON gx.stream='L3_INDEX' AND gx.source_id=o.rowid
        ORDER BY v.event_time_jst DESC LIMIT 5
    '''):
        print(dict(row))
finally:
    con.close()
PY
)
表示 読み方
status=ok AIの処理が成功した
upload_state=acked AI結果ファイルのDrive転送が完了した
image_upload_state=acked 根拠画像のDrive転送が完了した
sheets_recorded_at に日時がある AI結果のL3_INDEXをSheetsへ記録した
pending / retry 未転送または再試行待ち。AI成功とは別に確認する

新しいイベントが次々に作られると、最新の行はまだ pending の場合があります。表全体が一瞬で acked になることを求めず、同じイベントの状態が進むかを追います。また、SheetsのINDEXはファイルと転送の一覧で、全モデルのconfidenceを並べるAI専用シートではありません。

13. 今回できたことと、残る確認

10月20日のRONV2では、新しいBUTTON撮影の入力画像と、道路・冠水・煙・視界の4モデルすべてに照合値が記録され、全モデルの status=ok を確認しました。RONV1でも前日に同じ項目を確認しています。

実機への接続確認と、道路で正しく検出できるかの評価は分けます。今回の机上試験だけで、道路冠水や障害物の検出精度、走行中の安定性まで確認済みとはしません。残る作業は、Grafana画面の最終確認、未転送データの経過確認、実車・道路画像での評価です。

次の#24では、この処理を動かす実機の構成と、UPSの通信不良への対応、別媒体へ残した復旧用バックアップを扱います。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?