SORACOM Fluxを使って混雑度を可視化する(export画像は保存されないパターン)
やりたいこと
ソラカメで録画した映像を切り抜いてダッシュボードに表示させたいと思います。
ついでに、その画像に対してAIによる解析をかけて数値化した結果も表示してみたいと思います。
こんな感じのものが出来上がります。

準備
必要なもの
- SORACOM アカウント
- ソラカメ関連
- ソラカメ(AtomCam2)
- Wifi環境
- ソラカメライセンス(常時録画ライセンス 7日間プラン)
ソラカメが初めての方はこちら(購入から設置まで)!
SORACOMのアカウント作成など
カバレッジタイプはJPで。
ソラカメの購入〜セットアップまで
実は最近はソラカメのセットアップにアプリを使わなくても良くなっていたりする。
ソラカメの設置
設置に関する知見はここにたくさん溜まっています。
カメラチェック
何はともあれ、カメラが正常に動作しているか確認しましょう。
「ソラコムクラウドカメラサービス」 -> 「デバイス管理」

デバイスの一覧表示で、先ほど登録したカメラがオンラインになっていることを確認します。

images/soracom-flux-croudness-count/image-1.png
ここで重要な情報はデバイスIDになります。
後で使うので、メモをしておくかいつでもここに戻ってこられるようにしておきましょう。
さらに、カメラの名前をクリックすると、カメラの映像が表示されます。

こちら現在20時過ぎのソラコムの赤坂オフィスです。ホワイト企業なので、この時間にオフィスで働いている人はほとんどいません。
SORACOM Fluxの設定
SORACOM Fluxとは
SORACOM Fluxは2024年7月17日のSORACOM Discovery 2024で発表された新サービスで、デバイスから送信されたセンサーデータ、カメラから送信された画像に対して、ルールを適用し、複数のデータソースや生成 AI を組み合わせて分析/判断し、その結果を IoT デバイスの制御に反映させる IoT アプリケーションをローコードで構築できる IoT アプリケーションビルダーです。
サービスの詳細:
Discovery2024での発表の様子:
SORACOM Fluxでやりたいこと
今回は、SORACOM Fluxを使って以下の一連の流れを実装していきます。
- ソラカメの録画データから画像を切り出す(Exportする)
- 切り出した画像のデータを取得する
- 切り出した画像をAIに読み込ませて解析させる
- 解析させた結果(データ)と切り出した画像をSORACOM Harvestに送信する
- SORACOM Harvestに送信されたデータをSORACOM Lagoonで表示する
今まではこれを実装しようと思うとAWS Lambdaをwebhookで呼び出してゴニョゴニョしないと難しかったのですが、2024年10月16日にリリースされた「SORACOM API アクション」を使うと、AWS Lambdaで呼び出していたソラカメAPIなどが呼び出せるようになりました。わーい!
ということでそんな便利な機能を早速使っていきましょう。
Inventoryを使った手順(参考)
その前に
その前にSORACOM Fluxで解析したデータをSORACOM Harvestにいれるための準備をしておきます。
あれ?SORACOM HarvestってSORACOM AirとかSORACOM ArcみたいなSIM的なものが必要なんじゃ・・・と思ったそこのあなた。
こういう場合に使える手段があります。
その名も SORACOM Inventory
いくつかある機能のうちの、デバイスIDとデバイスシークレットを使用した Harvest 連携の機能を使います。
デバイス登録時に払い出されるデバイスIDとデバイスシークレットを使用した HTTP リクエストで > SORACOM Harvest 連携ができます。
SORACOM Air やデバイスエージェントを使用することなく Harvest にデータを蓄積し、可視化することができます。
これは意外と知らない人も多かったのではないかと思いますが、ESP32やM5Stackなどをwifiで利用している皆さんにはとても愛されている方法の一つとなっております。
Inventoryのデバイス追加
ハンバーガーメニューから 「SORACOM INVENTORY」 -> 「Inventory デバイス管理」を探します。

「デバイスを追加」ボタンからデバイスを追加します。

デバイス名やグループ名をきめます。


「追加」ボタンで追加します。

デバイスIDとシークレットが発行されるので大切に取っておきます(後で使います)。

これでHarvest Dataにデータを貯める用の仮想的なデバイスは完成です。
Harvest Data の設定
あとはこのデバイスが所属するグループにHarvest Dataの設定を有効化します。
「Inventory デバイス管理」から先ほど登録したデバイスを探し出して、グループのところをクリックします。

ちょっと下の方にスクロールして、「SORACOM Harvest Data 設定」のトグルスイッチをONして有効化します。

Fluxの設定
それでは慌てず落ち着いていきましょう。
タイマー起動の設定
ハンバーガーメニューから「SORACOM Flux」-> 「Fluxアプリ」

新しいFluxアプリを作成します。

名前をつけましょう(何でもよいです)

こんな感じのチャネルを作成するしかやることがないページに飛びます。
大人しくチャネルを作成します。

今回は5分に1回のタイマーで駆動していきたいので、「タイマー」を選択します。

こんな感じで設定します。

これをこのまま放置するとこのあとの工程をやっている間に意図せず駆動されてしまうので、一旦無効化しておきます。

※急に現れてblinkしている、「変更を保存」ボタンは忘れずに押しましょう
STUDIOの画面上はこんな感じになっています。

ソラカメの録画データから画像を切り出す(Exportする)
「アクション」のタブに移動します。

「アクションを追加」からの「SORACOM APIアクション」をクリックしてOKします。

SoraCam:exportSoraCamDeviceRecordedImage をなんとかして探し出します。

こんな感じの画面になります。

URLのところの{device_id}をソラカメのデバイスIDに置き換えます。
この画面

の右側のやつです。
HTTPボディには、
{
"time":${now()}
}
を入れましょう。
SAM Userは新しく作成します。
名前もデフォルトのままでいきます。
こんな感じになります。

最後にアクションのアウトプットを有効にして別のチャンネルに送信するを有効にします。

こんな画面に遷移すればOKです。

ちゃんと設定できたか試してみる
自分の設定に自身のある方は次に進みましょう。
自身のない方はこのSORACOM APIアクションがちゃんと動くか試してみましょう
「テスト実行」タブに移動して、BODYに{}と空のJSON(?)を入れて、実行をクリックします。

実行結果が下の方に現れてきます。
緑色でCOMPLETEDとなったらOKです。

JSONのレスポンスのフォーマットはこのような感じになっています。

このレスポンスの内容はpayloadというJSONとなって後段のアクションで再利用できます。
切り出した画像のデータを取得する
こんな感じの状態になっているともいます。

画像を切り出すためのリクエストをしただけなので、まだ切り出された画像は取得できていません。
切り出された画像を取得するためのAPIを実行します。
右側に伸びているグレーの土管をクリックして「アクション」タブから「アクションを追加」します。

先ほどと同様に「SORACOM API」を選択して「OK」
なんとかして、SoraCam:getSoraCamDeviceExportedImageを探し出します。

先ほどと同様にURLのプレースホルダーを変えていきます。
{device_id}はソラカメのデバイスIDを直打ちしてもいいのですが、先程実行したAPIのレスポンスのボディに含まれるので今後対象のデバイスを変えたりしたときのために、レスポンスから引くことにします。
{export_id}は先程実行した画像切り出しのリクエストのIDを入れます。
(このAPIは、先程実行したリクエストもう終わっているかどうかをリクエストのIDをキーにして調べるAPIです。)これも先程のレスポンスのボディから引くことができます。
この部分には以下のような呪文をいれるのが正解となります。
/v1/sora_cam/devices/${payload.deviceId}/images/exports/${payload.exportId}
payload.deviceIDで先程のAPIのレスポンスのJSONからdeviceIDを呼び出せます。また、お作法としてこのように、設定パラメータのなかで使う場合は${}で囲うというFluxのローカルルールになっています。
注意点を理解して、新しいSAM Userを作ります。
アウトプットのチャンネルは、一旦無効にして作成し、テストを実行してみます。
一度STUDIOに戻って、左端のタイマーをクリックして、先ほどと同じようにBODYを入れて実行します。

さっきより出力が伸びたと思います。一番下が緑でCOMPLETEDになっていればOKです。

最後の出力を見てみると、statusがprocessingとなっています。

これは、まだ画像が作り終わっていないという意味になります。
おわかりの通り、画像が作り終わるまで作り終わったかどうかを確認する必要がありますね。
プログラミングの知識のある人は、whileループみたいなものを使えばいいという発想になると思いますが、Fluxの場合は次のようにします。
画像のexportが完了するまでデータ取得のAPIを実行し続ける
STUDIOに戻って右端のSORACOM API アクションをクリックします。

アクションの実行条件のところに
payload.status == "initializing" || payload.status == "processing"
を入れます。

これは、一つ前のアクションの実行結果のレスポンスに含まれる、statusがinitializingまたはprocessingのときだけ、このAPIを実行しますよーというWhile文やIf文の条件のようなものになります。
これをwhileループっぽく使いたいので、アクションのアウトプットをこのアクションの入口のチャネルにいれるように設定してループを作ってあげます。
「有効」をONして、「既存のチャンネルを選択する」からのこのアクションの前にあるチャネルをプルダウンから選びます。

更新を押すとこんな感じになっていると思いますが、

心の目で見るとオレンジの線が見えてくるはずです。

これで、どこかで必ずstatusがcompleted(正確にはinitializingとprocessing以外)になってループを抜けれるはずなので、completedになったときのアクションを設定しておきます。
テスト実行するとこんな感じの結果が出ることになります。

切り出した画像をAIに読み込ませて解析させる
ここの土管をクリックします

AIアクションを選択します。

このブロックはさっきのループをcompleteのステータスで抜けて来たときだけ実行したいので、アクションの実行条件に以下を設定します。
payload.status == "completed"
AIモデルはGPT-4o等のマルチモーダルに対応したモデルを選択します。
今回の例では画角の映像から着席している人数と着席割合から混雑度を解析するため、プロンプトを以下のようにしてみました。
自由にいじってみてください。
画像の様子を説明してください。
また、だいたいどのぐらいの人数が着席しているか、正確でなくて良いので教えて下さい。
出力の形式は以下のようなJSON形式でお願いします。
{
"state": 状態を表現する文章
"person_count": 着席している人数
"crowding_level":座席数に対する着席している人数の割合(0-100)
"url":"${payload.url}"
}
AIからの返答をJSON形式にするとAIに画像を読み込ませるにチェックを入れます。
AIに読み込ませる画像のところにはURLを入れますが、completeのstatusで返ってきたexport画像を取得するAPIにはpresigned URLが入っていますのでそこを参照させるように以下を設定します。
${payload.url}
出力は新しいチャネルに出す設定でこのアクションを作成します。

テストはの実施方法は省略しますが、うまくいくと以下のようなレスポンスがAIから返ってくるはずです。

Inventoryを使った手順(参考)
解析させた結果(データ)と切り出した画像をSORACOM Harvestに送信する
STUDIOに戻ると以下のようになっていると思います。

当然心の目で見ると

オレンジの線が見えてきます。
最後のアクションは青い箱から出ている土管に接続します。

今回はwebhookのアクションを使います。

名前をHarvestにデータを送信などとして、アクションの実行条件はそのまま(空白)で、
HTTPメソッド:POST
URL:https://api.soracom.io/v1/devices/デバイスID/publish
HTTPヘッダー:x-device-secret:シークレットキー
URLの中のデバイスIDと、HTTPヘッダーのシークレットキーは後で使うと予告してあった、こちらのものを利用します。

こんな感じで設定したら更新して閉じます。
BODYはAIアクションのレスポンスのボディからoutputを抜き出して設定するので、${payload.output}となります。

テストしてちゃんと送信できたか確認してみましょう。
webhookアクションの実行履歴が緑色でcompletedとなっていればOKですが、念の為Harvest Dataの方でも確認してみます。
「データ収集・蓄積・可視化」から、「SORACOM Harvest Data」を選択します。

Inventoryを使っているので、「デバイス」の中からリソースを指定して、

確認するとちゃんと送られているようです。

解析させた結果(データ)と切り出した画像をSORACOM Harvestに送信する
STUDIOに戻ると以下のようになっていると思います。

当然心の目で見ると

オレンジの線が見えてきます。
最後のアクションは青い箱から出ている土管に接続します。

ここではSORACOM APIアクションを使います。

検索欄にcreateSoraCamDeviceDataEntryと入力し、表示されたパネルをクリックします。

URLのところの{device_id}をソラカメのデバイスIDに置き換えます。
BODYはAIアクションのレスポンスのボディからoutputを抜き出して設定するので、${payload.output}となります。

テストしてちゃんと送信できたか確認してみましょう。
webhookアクションの実行履歴が緑色でcompletedとなっていればOKですが、念の為Harvest Dataの方でも確認してみます。
「データ収集・蓄積・可視化」から、「SORACOM Harvest Data」を選択します。

データソースとしてソラカメを使っているので、「ソラカメ」の中からリソースを指定して、

(Inventoryを使った場合は、「デバイス」を選択してリソースを指定してください。)
確認するとちゃんと送られているようです。

SORACOM Harvestに送信されたデータをSORACOM Lagoonで表示する
Harvest DataまでくればあとはいつもどおりLagoonで可視化するだけです。
「データ収集・蓄積・可視化」から、「SORACOM Lagoon」を選択します。

Lagoonの契約がない場合は、こちらを参考にセットアップしましょう。今回の可視化はフリープランでも実施可能です。
Lagoon3のコンソールにログインします。

こんな感じのダッシュボードを作っていきたいと思います。

一番上に様子(文章)を表示
「add a new panel」します。

- visualizationは「logs」を選択
- Queryは左から、「Device」, 「Inventoryで設定したデバイス名」,「Standard」、「-All-」でOKです

その他はデフォルトのままにします。
左側上段に混雑度と人数を表示
「add a new panel」します。
- Visualizationは「Stat」を選択
- Queryは2系列
- A:「Device」, 「Inventoryで設定したデバイス名」,「Standard」、「crowding_level」
- B:「Device」, 「Inventoryで設定したデバイス名」,「Standard」、「person_count」
左側下段に混雑度と人数(時系列推移)を表示
「add a new panel」します。
- Visualization は「Time series」
- Queryは2系列
- A:「Device」, 「Inventoryで設定したデバイス名」,「Standard」、「crowding_level」
- B:「Device」, 「Inventoryで設定したデバイス名」,「Standard」、「person_count」
- Query optionsを選択して、Relative timeを少し長めに設定(下記の例では6h)。
右側は大きめにソラカメのエクスポート画像を表示
「add a new panel」します。
-
Visualizationは「Soracom Dynamic Image Panel」

-
Queryは「Device」, 「Inventoryで設定したデバイス名」,「Standard」、「-All-」

-
Settings
- Mode:「URL」
- Name:「A-url」
各パネルの設定が完了したら、ダッシュボードのTime Rangeをnow-6m ~ nowとなるように設定します。

(SORACOM Fluxの駆動が5分おきのため、マージンを1分取って6分で設定しています。)
Save current time range as dashboard defaultのチェックを入れて保存したらダッシュボードの完成です。

お疲れ様でした!!
補足
長期間運用していると、どこかのタイミングでソラカメの画質が落ちて戻らなくなることがあります。
これは正常な動作なのですが、精度が劣化することもあるため画質を戻す必要がある場合は以下のブログの対処法を参考にしてください。
Discussion