Launched
交通量計測をAIで自動化するスマホアプリのアイコン

交通量計測をAIで自動化するスマホアプリ

お手持ちのiPhoneやAndroidで道路の自動車や人の通行量を計測できる無料サービスをローンチしました。

Visit Product
プロダクトの感想・意見はこのエリアに表示されます
アイキャッチ画像

🎯 はじめに

スマートフォンのカメラで通行人や車両をリアルタイムにカウントするアプリ「ミチミエール」を個人開発しました。

本記事では、Flutter上でYOLO11による高精度な物体検出と、Kalman Filter + Hungarianアルゴリズムによるトラッキングを実装し、30-60 FPSのリアルタイム処理を実現するまでの技術的な全貌を解説します。

人物トラッキングの様子
キャプションをここに記入

主な実装内容:

  • YOLO11モデル(TensorFlow Lite)のFlutter統合
  • Kalman Filter + Hungarian Algorithmによる高精度トラッキング
  • ライン通過判定による重複なしカウント
  • Firebase連携によるデータ管理
  • 30-60 FPSのリアルタイム処理

技術スタック:

アプリ情報:

  • 公式サイト:
  • App Store: [公開中]
  • Google Play: [公開中]

💡 なぜこのアプリを作ったのか

道路の交通量調査やイベント会場の来場者数カウントは、従来は人手による目視カウント高額な専用機器が必要でした。

「スマートフォン1台で、誰でも簡単に通行量を計測できるアプリがあれば便利なのでは?」

この素朴な疑問から、AIを活用した通行量カウントアプリの開発をスタートしました。

主な用途:

  • 道路の交通量調査
  • 歩行者通行量の計測
  • イベント会場の来場者数カウント
  • 商業施設の入退場管理
  • 都市計画のためのデータ収集

🏗️ アーキテクチャ全体像


🤖 YOLO11のモバイル実装

1. モデル選定とサイズ比較

YOLO11には複数のモデルサイズがあり、精度と速度のトレードオフを考慮して選択する必要があります。

モデルサイズ推論速度 (iPhone 14 Pro)精度 (mAP)
yolo11n5.8 MB~60 FPS普通
yolo11s21 MB30-45 FPS良好
yolo11m49 MB20-30 FPS高精度

本アプリでは、**yolo11s(スモール)を標準モデルとして採用。ユーザーは設定からyolo11m(ミディアム)**に切り替えて高精度モードも利用可能です。

2. TensorFlow Liteへの変換

YOLOモデルをモバイルで動かすには、TensorFlow Lite形式への変換が必要です。

モデル変換のコード例:

エクスポートされるファイル:

  • yolo11s.tflite (21 MB)
  • labels.txt (クラスラベル)

これらを assets/ ディレクトリに配置し、pubspec.yamlで登録します。

3. Flutterへの統合

ultralytics_yolo パッケージを使用してYOLOを統合します。

Flutterでの実装例:

4. 検出結果の処理

YOLOからの検出結果を受け取り、トラッカーに渡します。

検出結果の処理コード:

ポイント:

  • 低信頼度の検出でも、既存トラックとIoUが高ければ受け入れる(追跡の継続性)
  • 座標変換:カメラ座標系 → 画面座標系の変換が必要

🎯 高精度トラッキングの実装

物体検出だけでは、フレーム間でIDが変わってしまい、同一の人物を追跡できません。ここでトラッキングが必要になります。

トラッキングの目的

  1. IDの安定化:同一物体に一意のIDを割り当て続ける
  2. オクルージョン対応:一時的に見えなくなっても追跡を継続
  3. 重複排除:同じ物体を複数回カウントしない

アルゴリズム選定:Kalman Filter + Hungarian Algorithm

DeepSORT風のアルゴリズムを採用しました(外観特徴なし)。

構成要素:

  • Kalman Filter:物体の運動モデルで次フレームの位置を予測
  • Hungarian Algorithm:検出とトラックの最適割り当て
  • トラックライフサイクル:Tentative → Confirmed → Deleted

実装の全貌

以下、トラッカーの核心部分の実装です。

1. Kalman Filterによる予測

実装コード:

2. マハラノビス距離によるゲーティング

検出とトラックの関連付けの前に、マハラノビス距離でフィルタリングします。

ゲーティングの実装:

マハラノビス距離が閾値(デフォルト: 9.4877)を超える場合、その検出は候補から除外されます。

3. コスト行列の構築

コスト計算の実装:

パラメータ:

  • lambdaCost = 0.7:IoUを重視(0に近いと運動モデル重視)
  • iouMin = 0.01:最小IoU閾値
  • gateThreshold = 9.4877:カイ二乗分布の5%点(自由度4)

4. Hungarian Algorithm(ムンクレス法)

コスト行列から最適割り当てを求めます。

Hungarianアルゴリズムの実装:

Hungarian Algorithmにより、グローバル最適な割り当てが得られます。

5. トラックのライフサイクル管理

パラメータ(15fps環境に最適化):

  • minHits = 1:確定に必要な連続ヒット数
  • maxAge = 90:見失い許容フレーム数(6秒)

📏 ライン通過判定アルゴリズム

トラッキングだけでは「いつ、どの方向に通過したか」が分かりません。ここでライン通過判定を実装します。

基本原理

画面上に仮想ラインを設定し、トラックの重心がラインを符号変化して通過したときにカウントします。

実装のポイント

1. 符号付き距離の計算

ライン定義のコード:

2. 重心の平滑化(EMA)

フレーム間のノイズを除去するため、**指数移動平均(EMA)**で重心を平滑化します。

EMA平滑化の実装:

smoothingAlpha = 0.7:現在値を70%、過去値を30%の重みで合成

3. 通過判定の条件

符号が変わっただけではカウントしません。以下の条件をすべて満たす場合にカウントします。

通過判定のロジック:

パラメータ(15fps環境に最適化):

  • minDeltaNormalized = 0.003:微小移動フィルタ
  • minCrossingDistanceNormalized = 0.008:最小通過距離
  • cooldownFrames = 20:カウント後のクールダウン(約1.3秒)

4. クールダウン機構

同じトラックが短時間で複数回カウントされるのを防ぐため、カウント後に一定フレーム数のクールダウンを設けます。

クールダウンの実装:


⚡ パフォーマンス最適化

達成した性能指標

項目
FPS30-60(デバイス性能に依存)
平均レイテンシ50-100ms
メモリ使用量200-400MB
モデルサイズyolo11s: 21MB / yolo11m: 49MB

最適化のポイント

1. GPU使用の有効化

GPUを使用することで、推論速度が2-3倍向上します。

2. フレームレートの制限

60fpsで処理してもバッテリーを消費するだけなので、30fpsに制限。

3. 座標変換の最適化

カメラ座標系から画面座標系への変換は毎フレーム実行されるため、計算を最適化します。

座標変換の実装:

4. トラッキングパラメータのチューニング

15fps環境(低速デバイス)でも安定動作するようパラメータを調整しました。

通常のDeepSORT(30fps想定)の値から、フレーム数を倍に調整しています。

5. ステート最小化

不要な状態変更を減らし、Widget再ビルドを最小限に抑えます。


😰 苦労した点と解決策

1. カメラ座標系の不一致

問題: YOLOの検出座標とFlutterの画面座標が一致せず、バウンディングボックスがズレる。

原因:

  • カメラのアスペクト比(4:3)と画面のアスペクト比(9:16)が異なる
  • iOSとAndroidで座標系が異なる
  • 画面の向き(Portrait/Landscape)で変換が変わる

解決策:
normalizedBox(0.0〜1.0の正規化座標)から逆算してカメラ解像度を推定し、Aspect Coverでマッピング。

カメラサイズ推定の実装:

2. トラッキングIDの不安定性

問題: 人が重なったり、一時的に見えなくなると、IDが変わってしまう。

解決策:

  • maxAge = 90(6秒)に設定し、長時間の見失いに対応
  • マハラノビス距離ゲーティングで、運動モデルに基づく予測を活用
  • IoU閾値を0.1まで下げ、大きくズレた検出でもマッチング可能に

3. 重複カウント

問題: 同じ人が複数回カウントされてしまう。

解決策:

  • クールダウン機構:カウント後20フレーム(約1.3秒)は同じIDをカウントしない
  • 通過距離の閾値:ライン法線方向に一定距離以上移動した場合のみカウント
  • 微小移動フィルタ:ノイズによる誤検出を除去

4. モデル切り替え時のクラッシュ

問題: yolo11s ↔ yolo11m を切り替えると、iOSでカメラがクラッシュする。

解決策:
古いコントローラーを完全に停止してから、1秒待ってから新しいコントローラーを起動。

モデル切り替えの実装:

5. バックグラウンド移行時のデータ保存

問題: アプリをバックグラウンドに移行すると、計測データが失われる。

解決策:
WidgetsBindingObserverでライフサイクルを監視し、pauseddetached状態になったら自動保存。

ライフサイクル監視の実装:


📊 実装の成果

定量的な成果

  • FPS: 30-60(デバイス依存)
  • トラッキング精度: IoU 0.7以上で安定追跡
  • カウント精度: 目視カウントとの誤差 5%以内(テスト環境)
  • バッテリー消費: 30分の連続使用で約15%消費(iPhone 14 Pro)

定性的な成果

  • スマホ1台で計測可能:専用機器不要
  • リアルタイムフィードバック:計測中に結果を確認できる
  • 低コスト:アプリは無料、追加費用なし
  • データ保存:Firebase連携で自動保存・複数デバイス同期

ユーザーの声(想定)

「今まで手作業で数えていた通行量調査が、スマホを置くだけで自動化できた!」

「イベント運営で来場者数をリアルタイムに把握できるのが便利」


🎓 学んだこと

技術面

  1. YOLO + トラッキングの深い理解:検出とトラッキングは別物
  2. Kalman Filterの実装:線形代数の実践的応用
  3. Hungarian Algorithm:組合せ最適化の重要性
  4. モバイルAIの制約:メモリ、バッテリー、速度のトレードオフ
  5. 座標変換の難しさ:カメラ座標系の理解が必須

開発面

  1. プロトタイプ → 最適化 → リファクタリングの反復
  2. パラメータチューニングの重要性:デフォルト値の影響は大きい
  3. デバッグログの活用:フレームごとの状態を可視化
  4. テストの自動化:単体テストでバグを早期発見
  5. ドキュメントの整備:未来の自分へのメモ

個人開発

  1. 小さく始めて改善する:完璧を目指さない
  2. ユーザーフィードバックの重要性:想定外の使い方を知る
  3. オープンソース活用:車輪の再発明をしない
  4. コミュニティの力:Flutter/Dartコミュニティに感謝

📚 参考資料

論文・文献

ライブラリ・ツール

コミュニティ


🏁 おわりに

「スマホで通行量をカウントする」というシンプルなアイデアから始まった開発でしたが、YOLO11の統合、Kalman Filterによるトラッキング、ライン通過判定など、多くの技術的挑戦がありました。

個人開発ならではの自由さで、試行錯誤を繰り返しながら、30-60 FPSのリアルタイム処理を実現できたことは大きな達成感です。

この記事が、FlutterでAIを活用したアプリを開発したい方や、物体検出・トラッキングに興味がある方の参考になれば幸いです。

ミチミエールは現在、App Store / Google Playで無料公開中です。ぜひダウンロードして、実際に使ってみてください!


アプリのダウンロード

  • 📱 App Store: ミチミエールで検索
  • 🤖 Google Play: ミチミエールで検索
  • 🌐 公式サイト:

お問い合わせ・フィードバック

公式サイト(https://michimieru.com)のお問い合わせフォームからご連絡ください。

ご意見・ご感想・バグ報告など、お気軽にお寄せください!


📌 関連記事(今後執筆予定)

  • FlutterでFirebase連携 - リアルタイムデータ同期の実装
  • TestFlightへの自動デプロイ - CI/CD構築ガイド
  • Riverpodによる状態管理 - ベストプラクティス
  • モバイルAIアプリのパフォーマンスチューニング

#Flutter #YOLO #AI #機械学習 #物体検出 #個人開発 #アプリ開発 #Firebase #モバイルアプリ #技術記事 #iOS #Android #TensorFlow #画像認識 #ディープラーニング #Dart #リアルタイム処理 #Kalman Filter #トラッキング #コンピュータビジョン

Explore More
関連記事はありません。