DeepLens + Rekognition + Raspberry Pi で会議室を自動解錠する


2018年6月、弊社にて「OpenSesameプロジェクト」が立ち上がりました。
目的はその名の通り、「開けゴマ」です。
DeepLensを使用して会議室のドアを自動で解錠します。

今回は自動解錠システムの実装として
・Rekognition API での顔認証
・Raspbery Pi のGPIO制御
をメインにお話します。

DeepLensの基本事項や設定方法については過去記事で紹介しておりますのでそちらをご覧ください。

過去記事
AWS DeepLensを購入しました〜セットアップ編〜
AWS DeepLensを購入しました〜映像をみる、SSH接続編〜
AWS DeepLensを購入しました〜サンプルプロジェクトデプロイ編〜
AWS DeepLensを購入しました〜コミュニティプロジェクトデプロイ編〜

構成

まず、大まかなシステム構成は以下の通りです。

①DeepLens内のLambda functionとModelで人の顔を検知
②検知した顔データをAWS上のRekognitionに送信
③Rekognitionが「受け取った顔データ」と「登録済みの顔データ」を比較検証
④検証結果をDeepLensに送信
⑤DeepLens内のLambdaが結果を受け取り、Raspbery Piに転送
⑥Raspbery Piが会議室の電気錠に信号を送って解錠
※DeepLensとRaspbery PiはGreenGrass Groupによって通信する

では、具体的な実装について紹介していきます。

事前準備

Rekognitionに顔コレクションを作成しておきます。
顔コレクションとは、顔のデータを集めて保存しておくコンテナのことです。
今回は、作成したコレクションに社員の顔データをあらかじめ保存しておきましょう。
参考:コレクションの作成

また、DeepLensデバイスとRaspbery Piが通信できるように、
DeepLensのGreenGrassグループにRaspbery Piをデバイスとして登録しておきます。
GreenGrassについては知らない方も多いかとは思いますが、
ここでは「IoTデバイス間でのローカル通信を実現するサービス」ぐらいの認識で大丈夫です。
参考:GreenGrassでのデバイスの操作

DeepLensプロジェクトの構築

人の顔を検知するフェーズです。映像の中から人の顔をキャプチャします。

顔を検知するサンプルプロジェクトがAWS側で用意されているので、こちらを利用しました。
プロジェクトモデル: deeplens-face-detection
プロジェクト関数: deeplens-face-detection

ただ、このプロジェクトは「人の顔を検知する」ことはできますが、
「誰の顔なのかを識別する」機能は含まれていません。
そこで、「顔を識別する」役割はRekognitionに任せることにします。

Rekognitionを使用した顔の識別

検知した顔が誰なのか識別するフェーズです。
Rekognitionの顔コレクションから検索することで実現します。

ここで、Rekognition APIを使用するために、deeplens-face-detection関数に手を加えます。

import boto3
rekognition = boto3.client('rekognition', region_name='ap-northeast-1')

# Send to Rekognition by image byte
response = None
faceMatches = ''
try:
    response = rekognition.search_faces_by_image(
        CollectionId = collectionId,
        Image = {'Bytes': frame_crop},
        FaceMatchThreshold = 70,
        MaxFaces = 1)
except:
    pass

CollectionId = 社員の顔データを保存したコレクションのARN
Image = 映像から検知した顔データ
FaceMatchThreshold = 顔の識別のしきい値
MaxFaces = Rekognitionに送信する顔の数

SearchFacesByImage オペレーションを使用することで、
コレクション内の顔データと一致するものがあるかどうか検索することができます。
しきい値以上で一致する顔データがコレクション内に見つかった場合は、
json形式のレスポンスが変数responseに格納されます。
参考:イメージを使用した顔の検索

Raspbery Piに検証結果を転送

Raspbery Piに解錠を依頼するフェーズです。

greengrasssdkパッケージを使用し、Raspbery PiにMQTTメッセージを送信します。
14行目では、10秒以内に同一人物がキャプチャされた場合は判定しないように設定しています。
last_recognized_faceとlast_recognized_timeは、映像をキャプチャするループの前に定義しておきましょう。

import greengrasssdk
client = greengrasssdk.client('iot-data')
iotTopic = 'Message'
last_recognized_face = ""
last_recognized_time = ""

if response is not None:
    faceMatches = response['FaceMatches']
    current_time = datetime.now()

    if faceMatches:
        name = faceMatches[0]['Face']['ExternalImageId']
        similarity = faceMatches[0]['Similarity']
        if name == last_recognized_face and (current_time-last_recognized_time).seconds<10;
            continue
        last_recognized_face = name
        last_recognized_time = current_time
        message = {'time': str(current_time.isoformat()), 
                   'name' : name, 
                   'similarity' : similarity}
        client.publish(topic = iotTopic, payload = json.dumps(message))
    else:
        pass

レスポンスには多くの情報が含まれるので、
「時刻(time)、一致した顔データ名(name)、一致度(similarity)」
だけを抽出して送っています。

Raspbery Pi でメッセージを処理

Raspbery Piが解錠処理を行うフェーズです。

事前準備でRaspbery Piをデバイスとして登録できていれば、
Raspbery Pi上でbasicDiscovery.pyが稼働しています。
basicDiscovery.pyは、デバイスから送られてきたメッセージを処理するスクリプトです。

このスクリプトはデフォルトでは受け取ったメッセージを表示するだけなので、
ドアを解錠するための電気信号を送るように修正します。

import RPi.GPIO as IO
import AWSIoTPythonSDK.MQTTLib
from datetime import datetime

IO.setmode(IO.BOARD)
IO.setup(40,IO.OUT, initial=IO.LOW)

# General message notification callback
def customOnMessage:
    json_message=json.loads(message.payload)
    print "current time: "+str(datetime.now().isoformat())

    IO.output(40,1)
    time.sleep(0.01)
    IO.output(40,0)

    print('Received message on topic %s: %s\n' % (message.topic, message.payload))

RPi.GPIOパッケージは、Raspbery PiのGPIOを操作するのに使います。
IO.setupで40番ピンを出力に設定し、
メッセージを受け取った時に0.01秒だけ出力します。

ドア解錠!

会議室ドアの電気錠に信号を送って解錠するフェーズです。
Raspbey Piと電気錠はリレーモジュールで接続します。

GPIOについては
・4番ピンが5V電圧
・6番ピンがGround
・40番ピンが出力
という形で使用しています。

リレーの反対側は会議室の電気錠に接続します。
業者さんに工事してもらって接続用の配線を一本出していただきました。
(見えにくくてすいません、、、)

あとはDeepLensを設置すれば完了です

DeepLensのカメラと本体はUSBで繋がっているので、
長めのUSBケーブルで接続し直すことでカメラと本体を分離しました。
DeepLens本体とRsapbery Piは天井裏に設置しています。

ここまでが全てうまく接続できていれば、
DeepLensが社員の顔をキャプチャした際にRaspbery Piにメッセージが送られ、
ドアが解錠されるようになります。

「DeepLens→Rekognition→Raspbery Pi→リレーモジュール →電気錠」
の間の通信が全て適切に繋がっていないと解錠されないので、
接続にはけっこう時間がかかりました。

もしチャレンジする方がいらっしゃれば、
一箇所ずつ接続確認しながら進めることをお勧めします。

工夫した点

①プロジェクトストリームの削除
DeepLensには2つの出力ストリームがあります。
・デバイスストリーム:カメラで捉えたままの映像
・プロジェクトストリーム:モデルによって処理された映像
今回はプロジェクトストリームを出力しないことで、処理の負荷を軽減しています。

②S3にアップロードしない
DeepLensでキャプチャした画像をS3にアップロードし、
そのS3バケットを指定した上でRekognitionに検証してもらう方法もありますが、
今回はRekognition APIに直接画像を送信しています。
この方が処理速度が向上します。

③リージョンの設定
Rekognitionはデフォルトのバージニアリージョンではなく、
東京リージョンに設定した方がレスポンスが早くなりました。

課題

①DeepLensとRaspbery Pi 間のローカル通信が不安定
0.1秒かからないこともあれば、20秒近く要することもありました。
原因はまだ特定できておらず。

②DeepLensが最新映像をキャプチャできていない
awscamモジュールのgetLastFrame()によって最新の映像を捉えるはずなのですが、
どうやら2〜3秒前の映像を捉えている様子、、、
設定が悪かったのか?DeepLensの性能限界なのか?
こちらもまだ調査中。

③熱い
常時稼働させておくとDeepLensが熱くなります。めちゃくちゃ熱いです。
本体と一緒に冷却用のファンなどを置いておく必要がありそうです。

おわりに

リンクバルでは、一緒に働くエンジニアを募集しています。

・技術によって日本をよくしていきたい
・人と人との出会いを生み出していきたい
そんな方はまずこちらをご覧ください。