作ったサービス
「店に行きたいけど、待ちたくはない」
「病院行きたいけど、空いてる時に行きたい」
「あそこは今、混んでるやろか」
そんな思いから、"混雑度をいつでもどこでも確認できるサービスを"というコンセプトで開発しました。
サービスの簡単な流れは以下の通りです。
具体的な実装を以下に示します。
混雑度の算出方法
- 誰もいない状態の画像をあらかじめ撮影しておきます。
- 人がいる状態の画像を撮影します。
- この画像について、初めの画像と差分をとります。
- この差分の大きさを、混雑度としています。
画像認識で人を識別し人数から混雑度を求めたり、人感センサを用いたりする方法も検討しましたが、
今回はシンプルに差分だけにしました。(いつか改良したい。)
環境
- Raspberry Pi 3 Model B
- OS : Raspbian Jessie
- opencv : 3.4.2
まず無人の部屋を1024*768の画素数で撮影し、initial.jpgという名前で保存しておきます。
これが比較される基準の画像になります。
import time
import picamera
FILEPATH = '/home/pi/picture/'
with picamera.PiCamera() as camera:
camera.resolution = (1024, 768)
camera.start_preview()
time.sleep(2)
camera.capture(FILEPATH+'initial.jpg')
次に混雑度を求めます。
# -*- coding: UTF-8 -*-
import cv2
import numpy as np
import time
import picamera
FILEPATH = '/home/pi/picture/'
with picamera.PiCamera() as camera:
camera.resolution = (1024, 768)
time.sleep(2)
camera.capture(FILEPATH+'now.jpg')
img_src1 = cv2.imread("picture/initial.jpg", 0)
img_src2 = cv2.imread("picture/now.jpg", 0)
img_diff = cv2.absdiff(img_src2, img_src1)
img_diffm = cv2.threshold(img_diff, 20, 255, cv2.THRESH_BINARY)[1]
kernel = np.ones((5,5), np.uint8)
opening = cv2.morphologyEx(img_diffm, cv2.MORPH_OPEN, kernel)
diff_sum = np.sum(opening)
congestion = diff_sum/(1024*768*255)
このスクリプトではまず、画素数指定(1024*768)で画像を撮影し、now.jpg
という名前で保存しています。
(画像の大きさを指定せずに撮影するとこの後の処理が重くなるので指定したように記憶してます。)
次に、初めに撮った無人状態initial.jpg
と比較し、差分を出します。これがimg_diff
です。
opening処理でノイズを消して、変化のあったピクセルを足しこみます。これがdiff_sum
です。
最後に、diff_sum を全画素数で割り算して混雑度を求めています。
以上がラズパイ上で混雑度を出す一連の処理です。
後者のスクリプトを cron で定期的に実行することで、混雑度は更新されます。
ラズパイ上で求めた混雑度をサーバへ送信
サーバ側の実装は [[Python] Djangoチュートリアル - WebAPIサーバの簡単構築方法] (https://qiita.com/okoppe8/items/1c66a7f57d855f3f9b02) を参考にさせていただきました。
この記事の中にあるdata_post.py
のtemperature
に先ほど求めた混雑度congestion
を渡します。
すると混雑度はサーバにPOSTされます。
[Django] Heroku デプロイ方法 2018年版 を参考にデプロイしました。
サーバと LINE bot の連携
Django+HerokuでLINE Messaging APIのおそ松botを作るまで を参考に、 LINE bot を動かします。
これによりまずは定型文を返してくれるようになります。
botには最新の混雑度を返すようにしてほしいので、reply_text
を以下のように書き換えます。
def reply_text(reply_token, text):
# reply = random.choice(serif)
data = Log.objects.values('created_at','temperature')
newest_data = str(int(data.last()['temperature']))
newest_time_utc = data.last()['created_at']
newest_time = timezone.localtime(newest_time_utc).strftime("%Y{0}%m{1}%d{2}%H:%M:%S").format(*'年月日')
reply = newest_time + 'の時点では' + newest_data + '%だワン'
payload = {
"replyToken": reply_token,
"messages":[
{
"type":"text",
"text":reply
}
]
}
requests.post(REPLY_ENDPOINT, headers=HEADER, data=json.dumps(payload))
return reply
データベース内のデータをdata
に格納し、last()
を用いて最新の値にアクセスしています。
その値を、reply
に入れてやります。
こんな風に教えてくれます。
最後に
ラズパイも opencv も LINE API も初めてで、何も知識がない状態から一つのシステムを作るのは苦労しました。
精度ガバガバなこのシステムをそっくりそのまま使いたい方はいないと思いますが、部分的にでも誰かのお役に立てば幸いです。
全体のコードを整理して、githubに上げたら追記します。
最後まで読んでいただき、ありがとうございました。