はじめに
2020年は新型コロナウィルスの影響で生活が一変しました。特にリスクが高まることが判明している、密閉空間/密集場所/密接場面の「3密」を避けることの重要性が定着し、3密は2020年の流行語大賞にもなりました。
今回Ciscoメンバー有志で、レストランやカフェなど、物理的に人が集まって食事をする場所を要する商業施設やイベント会場に向けて、「Merakiカメラで3密を避けるソリューション」を考案しましたので、その方法を説明します。
本記事は**「Merakiカメラで3密を避けるシリーズ」**第二弾です。第一弾はこちらです。
Cisco MerakiカメラのAPIで遊ぶ 〜 COVID-19 対策, カメラのAPIを活用して楽しく部屋の3密を回避しよう 〜
※アドベントカレンダー後半に第三弾も予定しています、乞うご期待!
#今回やりたいこと & デモ動画
今回は、人の流れをコントロールして、人の少ない飲食店に来場者を誘導することで、お店が密になるのを避ける仕組みを考えました。
施設案内のWebサイトを来場者が見たときに、飲食店が混んでいるかどうかひと目で確認できるようにします。また、すいている飲食店にだけ割引クーポンを提示します。割引クーポンに引き寄せられて、来場者は人の少ない飲食店に誘導される、ことを期待しています
イベント会場を例にしたデモ動画(30秒/無音)
来場したお客様が会場のフリーWiFiに接続すると、施設案内マップが表示されます。混んでるレストランはオレンジのアイコン、すいているレストランは緑のアイコンが表示されています。
アイコンをクリックすると、Meraki Cameraから、レストランの今の様子が見え、また人数もわかります。
そして緑の、空いているレストランにだけ、10%オフクーポンが動的に表示されます。これにより、今空いているレストランにだけ人を呼び込むことができます。
#仕組み
Merakiカメラでは、カメラに映っている人の人数を検知し、Merakiのクラウド上の管理コントローラ(=Merakiダッシュボード、 Merakiカメラはコントローラで集中管理されます)からAPI経由で情報を取得することができます。今回は、カメラに写っている人数をカウントするAPI(MV Sense API)と、カメラ画像のスナップショットを撮るAPI(Snapshot API)を活用します。
- Cisco Mearkiカメラで、施設内のレストランやカフェなど飲食店の状況を監視し、MV Sense APIで定期的に人数をカウントする
- 施設案内のWebサイトで混雑状況をGoogle Mapに色分け表示し、各店の現在の様子をSnapshot APIで表示する
- 飲食店内の人数が増え定員オーバーを検知したら、人が少ない飲食店にだけ、施設案内のWebサイトで「割引クーポン」を表示する
- 施設へ来場したゲストはクーポンにつられて人の少ない飲食店に誘導されることで、施設全体の密を回避する。
このソリューションでは、Google Maps API、Meraki MV Sense API、Snapshot APIの3つのAPIを使用しています。イベントWebサーバーはPython とFlaskのWebフレームワークで構築しています。
WebサーバーはGoogle Maps APIでサイトマップを取得し、マップ上にレストランのアイコンを配置します。
サーバーは、カメラで検知した人数をMeraki MV Sense APIで取得し、レストランの混雑度に基づいて価格やクーポンを判定しページ上に表示します。
また、Meraki Snapshot APIでリアルタイムの店内画像を取得し、ページ上に表示します。
#Meraki API解説 ~Meraki Snapshot API~
今回は、Google Mapにリアルタイムの店内画像を表示するのに利用している、Meraki Snapshot APIについて解説します。
Merakiカメラで人数をカウントする方法(MV Sense API)については「3密を避けるシリーズ」第一弾の解説が詳しいので、そちらをご参照ください
Meraki Snapshot APIは、リクエストを送ると、Meraki カメラの画像(スナップショット)を生成しその画像のURLを返します。リクエストは日時を指定することもできますし、指定しない場合は現在の画像を返します。また第二世代以降のMerakiカメラでは、返す画像の解像度を調整することも出来ます。
###STEP0-2: APIの有効化
お手持ちのMerakiカメラでAPIを利用するために、まずは下準備が必要です。
(1)STEP1: Meraki API 有効化と API key 取得
(2)STEP2: Merakiカメラの MV Sense API 有効化
それぞれを有効化する方法について詳しくは、「Merakiカメラで3密を避けるシリーズ」第一弾の記事をご覧ください。
###STEP3:Merakiカメラの画像を返すSnapshot APIの動作確認
DevNetのAPI解説ページでは、簡単に動作確認やサンプルコードの取得が可能です。
今回利用するMeraki Snapshot APIについては、こちらを試してみます。
https://developer.cisco.com/meraki/api-v1/#!generate-device-camera-snapshot
まずは、先程の下準備STEP1で取得したAPI Keyと、Merakiカメラのシリアル番号を手元に準備しましょう。
API Key(STEP1で取得)
Merakiカメラのシリアル番号は、カメラ本体が手元になくてもMerakiダッシュボード [Camera(カメラ) > Monitor(監視) > Camera(カメラ)]から確認できます。
もしデフォルトで表示されていない場合は、表の右端にある青いToolアイコンをクリックし、表示する項目から「シリアル番号」を選択してください。
準備ができたら、DevNetサイトでSnapshot APIの動作を確認しましょう。
Generate Device Camera Snapshotにアクセスします。
サイト上部にMerakiカメラのシリアル番号を入力し、
Headers情報にAPI Keyを入力し、 [Run]ボタンを押します。
すると、URLが返されます(下図左)。返ってきたURLを開いてみると、指定した日時にMerakiカメラに写っていたスナップショットが確認できました(下図右)。
#Google Mapでの混雑状況表示と動的なクーポン提示
今回は、地図上で施設の混雑状況を表示するために、Google Map上に施設のアイコン設置し、
そのアイコンの色を Meraki Camera のMV Sense APIで検知した人数に応じて変化させています。
Webページ上へのGoogle Mapの表示はflask_googlemapsモジュールを利用します。
取得した地図上に緯度経度を指定して飲食店のアイコンを設置しています。
(参考URL)
https://pypi.org/project/flask-googlemaps/
また飲食店のアイコンをクリックしたら、Merakiカメラのスナップショット画像を表示しています。
#実行コード
Snapshotを取得するpythonスクリプト
import requests
import json
import time
from config import settings
def snapshot():
camlist = settings.camlist
snap_url=[]
for sn in camlist:
meraki_snapshot_url='https://api.meraki.com/api/v0/networks/' + settings.network_id + '/cameras/' + sn + '/snapshot'
meraki_headers = {'X-Cisco-Meraki-API-Key': settings.apikey}
meraki_snapshot_response = requests.post(meraki_snapshot_url, headers=meraki_headers)
meraki_snapshot_response_json=json.loads(meraki_snapshot_response.text)
snapshot_url=meraki_snapshot_response_json['url']
snap_url.append(snapshot_url)
return snap_url
if __name__ == "__main__":
snap = snapshot()
print(snap)
Merakiカメラで検知された人数を取得するpythonスクリプト
import requests
import json
def personcount():
camlist = ['<camera serial number>']
num_of_person=[]
apikey = '<meraki api key>'
for sn in camlist:
headers = {'X-Cisco-Meraki-API-Key': apikey}
mv_live_url = 'https://api.meraki.com/api/v1/devices/'+sn+'/camera/analytics/live'
mv_live_response = requests.get(mv_live_url, headers=headers)
mv_live_response_json=json.loads(mv_live_response.text)
num_of_person_detected=mv_live_response_json['zones']['0']['person']
num_of_person.append(num_of_person_detected)
return num_of_person
Google Mapに混雑状況のアイコンと、人数やスナップショット画像を表示するpythonスクリプト
from flask import Flask, render_template, request
from flask_googlemaps import GoogleMaps, Map
#from dynaconf import FlaskDynaconf
from detect import personcount
from snapshot import snapshot
import requests
import json
import time
from collections import Counter
from config import settings
app = Flask(__name__, template_folder="templates")
#FlaskDynaconf(app)
GoogleMaps(
app,
key = settings.maps_key
)
@app.route("/")
def fullmap():
img_url = snapshot()
num_of_person = personcount()
if num_of_person[0] < 10:
icon_i = 'static/images/p1.png'
discount_i = '<a href=http://127.0.0.1:5000/coupon>You can get a discount coupon now!!!</a>'
else:
icon_i = 'static/images/p2.png'
discount_i = 'Come here now!'
if num_of_person[1] < 10:
icon_s = 'static/images/p1.png'
discount_s = '<a href=http://127.0.0.1:5000/coupon>You can get a discount coupon now!!!</a>'
else:
icon_s = 'static/images/p2.png'
discount_s = 'Come here now!'
fullmap = Map(
identifier="fullmap",
varname="fullmap",
style=(
"height:60%;"
"width:90%;"
"top:10;"
"left:60;"
"position:absolute;"
"z-index:200;"
),
lat=35.6295,
lng=139.7942,
markers=[
{
"icon": icon_i,
"title": str(num_of_person[0]) + ' people here',
"lat": 35.6293,
"lng": 139.7942,
"infobox": (
'\"<h1>Italian Restaurant <p> (' + str(num_of_person[0]) + ' people here!)</p></h1>\"'
'\"<h2>' + discount_i + '</h2>\"'
'\"<img src=\"' + img_url[0] + '\" width=\"50%\" height=\"50%\">\"'
),
},
{
"icon": icon_s,
"title": str(num_of_person[1]) + ' people here',
"lat": 35.6294,
"lng": 139.7942,
"infobox": (
'\"<h1>Sushi Tokyo <p> (' + str(num_of_person[1]) + ' people here!)</p></h1>\"'
'\"<h2>' + discount_s + '</h2>\"'
'\"<img src=\"' + img_url[1] + '\" width=\"50%\" height=\"50%\">\"'
),
},
],
# maptype = "HYBRID",
zoom="20"
)
return render_template(
"map.html",
fullmap=fullmap,
)
@app.route('/coupon')
def coupon():
#今回はデモ用にクーポンを1種類用意しました。
return render_template("shotcoupon.html")
if __name__ == "__main__":
app.run(debug=True, use_reloader=True)
施設案内(Google Map)を表示するWebサイトのHTML
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<link rel="stylesheet" href="">
{{fullmap.js}}
</head>
<body>
<hr>
<div align="center"><img width=80% height=auto src="static/images/top1.png"></div>
<div align="center"><img width=80% height=auto src="static/images/top2.png"></div>
<hr>
<h1>Site Map</h1>
<h2>You can get dynamic coupons for Restaurants!!!</h2>
<h3>Click a face icon. (Orange=Congestion, Green=Empty)</h3>
{{ fullmap.html }}
</body>
</html>
#おすすめ:DevNet Code Exchange のコードが参考になります
最後に、Cisco製品のAPIを試してみたい方に参考になる、「Cisco DevNet Code Exchange」を紹介します。
https://developer.cisco.com/codeexchange/
Cisco DevNet Code Exchangeとは、様々なCiscoソリューションのAPIを使ったコードが公開されているコミュニティです。
Cisco機器のAPIをなにか試してみたいな〜というときに、製品名などで検索もでき、様々なコードを参考にすることができます。
今回の「Merakiカメラで3密を避けるシリーズ」の解説もこちらに掲載しています。解説ページ右上からはGitHubに飛ぶことができますので、参考にしていただければ幸いです
Avoid-the-3Cs_via_Meraki-Camera
#終わりに
以上により、Merakiカメラを使って飲食店で密を避け、人が少ないレストランにだけ新たに人を呼び込むことができました
またアドベントカレンダー後半には、同じくMerakiカメラを使って3密を避け、今度は人が多くなった飲食店から人を追い出すソリューションの記事が登場予定です。是非ご期待ください!!
※この記事は、Cisco Systems Japan Advent Calendar 2020年版 1枚目 / 2020年版 2枚目に参加しています。他の記事もどうぞご覧ください!
#免責事項
本サイトおよび対応するコメントにおいて表明される意見は、投稿者本人の個人的意見であり、シスコの意見ではありません。本サイトの内容は、情報の提供のみを目的として掲載されており、シスコや他の関係者による推奨や表明を目的としたものではありません。各利用者は、本Webサイトへの掲載により、投稿、リンクその他の方法でアップロードした全ての情報の内容に対して全責任を負い、本Web サイトの利用に関するあらゆる責任からシスコを免責することに同意したものとします。