お問い合わせ
トップ » SORACOM IoT DIY レシピ » AIカメラで来客分析

SORACOM IoT レシピ:AIカメラで来客分析

AIカメラ「S+ Camera Basic」とAWSで作る来客分析システム

公開日: 2021年4月

レシピ難易度: ★★★★☆

このレシピでは、AIカメラ「S+ Camera Basic」の顔認識アルゴリズムを用いて「来客判定」を行い、その時の人物画像をクラウドストレージサービス「Amazon S3」に保存します。また、保存した画像を、機械学習による画像分析サービス「Amazon Rekognition」にて「個の特定」をして、データ分析・可視化サービス「Amazon Elasticsearch Service」で来客者の分析を実現します。

このレシピを通して、IoTデバイスからクラウドストレージへセキュアにデータをアップロードして、アップロードされたデータをクラウドサービスで解析、可視化するという、一連のIoTシステム構築について体験することが可能です。

Amazon Rekognition連携

使用する SORACOM サービス

本レシピを行うのに必要な時間、概算費用

本レシピは以下の通りです。

  • 必要な時間: 約150分
  • 概算費用: 約55,000円 + 月額費用 約2,255円

※ 概算費用: SORACOM Air, SORACOM Inventory, SORACOM Mosaic、SORACOM Harvestの各種サービスの概ねの費用 (送料などの付帯費用や無料枠適用は考慮しないものとしています)を含みます。

このコンテンツの進め方

上から内容を読み進みながら作業を行なっていきます。また左サイドに追従する目次からページ内の移動が可能です。

本コンテンツは現状のままで提供され、株式会社ソラコムは、誤りがないことの保証を含め、明示であると黙示であるとを問わず、本コンテンツの記載内容につき、いかなる種類の表明も保証も行いません。

掲載情報の閲覧及び利用により、利用者自身、もしくは第三者が被った損害に対して、直接的、間接的を問わず、株式会社ソラコムは責任を負いかねます。

本コンテンツを実践する中で用意された機器、利用されたサービスについてのご質問は、それぞれの機器やサービスの提供元にお問い合わせをお願いします。機器やサービスの仕様は、本コンテンツ作成当時のものです。

株式会社ソラコムが提供する機器・サービスについてのご質問はフォームで受け付けております。機器・サービスご利用前の導入相談は https://soracom.jp/contact/ に、機器・サービスご利用開始後のサポートは、SORACOMユーザーコンソール内のサポートサイトから「リクエストを送信」(要ログイン)にてお問い合わせください。

Copyright (c) 2023 SORACOM, INC.

準備

本レシピを行うためには、以下のものをご用意ください。

品名数量価格備考
S+ Camera Basic154,780円中には以下のものが含まれています。
・S+ Camera Basic 本体 x 1台
・専用 AC アダプタ x 1個
・汎用マグネットマウント x 1個
SORACOM 特定地域向け IoT SIM (plan-D マイクロサイズ) x 1
パソコン1インターネット接続が可能でサイトへの接続が自由であること。Google Chrome 等の最新ブラウザーが利用可能な事。

ご購入について

ハードウェアは以下よりご購入いただけます。

その他必要なもの

必要なもの費用作成方法など
SORACOM アカウント無料※SORACOM アカウントの作成 (JP)
AWS アカウント無料※AWS アカウントの作成 (JP)

※ アカウント作成・維持の費用の料金です。

あると便利なもの

本レシピでは追加で設置用に以下のものを利用しています。必須ではありませんが、ご参考にお使いください。

品名数量参考価格備考
三脚11/4インチネジが取り付けられる事、300g以上の重量に耐えられる事
1/4インチネジ カメラアダプタ1750円GoPro™マウント互換の1/4インチネジ用アダプタ
例: https://www.amazon.co.jp/dp/B01KO65AFO/

すでに AWS アカウント持っている場合の確認事項

  • ルートアカウントを利用する場合:特に確認すべき事項はありません。先に進んでください。
  • IAM アカウントを利用する場合:東京リージョン(ap-northeast-1)内のサービスへの権限の有無を確認してください。

本資料では ap-northeast-1 を使用します。また、必要権限の解説およびサポートは致しかねますが、主にAmazon S3、AWS Lambda、 Amazon SNS、 Amazon Rekognition、Amazon ElasticsearchService といったサービスへの権限、ならびに AWS IAM におけるロールの作成といった権限になります。
AdministratorAccess ポリシーが割り当てられていれば完遂可能です (同ポリシーを割り当てたことによる影響については IAM アカウント管理者にご相談ください)

S+ Camera Basic が届いたら行う事

S+ Camera Basic にはSIM(SORACOM 特定地域向け IoT SIM / plan-D)が内蔵されています。そのため、最初にデバイスを利用する場合、SORACOM ユーザーコンソールへログインして SIM の登録を行います。

SIMの受け取り

登録の方法は発注済みの SIM を登録する(JP)をご覧ください。約5分で完了します。
登録が完了すると SIM 管理の一覧に表示されますので、確認ください。

S+ Camera Basic用のグループを作成とSIMへの割り当て

S+ Camera Basicはエッジデバイス統合管理サービス「SORACOM Mosaic」を始めとして、様々なSORACOMサービスを組み合わせて動作します。ここでは、S+ Camera Basicで利用するSORACOMサービスの機能をONにしていきます。

まずはグループの作成と、作成したグループへ SIM を所属させる事から始めます。

グループとは?
SORACOM サービスのほとんどが、SIMに直接設定するのではなく、グループという単位に対して設定するようになっています。そして、SIM をグループに所属させることで SORACOM サービスが利用できるという間接的な仕組みです。

グループを作成してから SIM を所属させる方法の他、グループの作成と SIM の所属を同時に行う方法もあります。本レシピでは後者の「同時に行う」手順で進めていきます。

[Menu]>[SIM 管理]とクリックして SIM 管理画面を開きます

SORACOM Harvest Data でデータの収集を行いたい SIM (S+ Camera Basic) にチェックを付け、[操作]>[所属グループ変更]とクリックします

SORACOM の便利な使い方: 複数の SIM を同時に扱う
チェックをつける対象を複数にすれば、一度の複数の SIM を対象に操作が可能です。

「新しい所属グループ」のプルダウンボックスをクリックした後、[新しいグループを作成…]をクリックします

「グループ作成」のグループ名を入力して[グループ作成]をクリックします。

項目備考
グループ名S+ Camera Basic自由に入力可能です。スペースや記号、日本語も設定可能です。

新しい所属グループが先ほど作成したグループになっていることを確認したら[グループ変更]をクリックします。

自動的に SIM 管理画面に戻ります。
SIM の「グループ」に先ほど作ったグループが設定されていることを確認してください

グループの設定を行う

グループに、S+ Camera Basicで利用する SORACOM サービスを有効にします。有効にするサービスは以下の通りです。

SIM 管理画面から、S+ Camera Basicに割り当てたグループ名をクリックします

グループの設定画面内で、それぞれの設定を行います

設定内容は以下の通りです。

項目設定値備考
メタデータサービスONスイッチはクリックすることで OFF から ON に切り替えることができます。
SORACOM Harvest Data 設定ONスイッチはクリックすることで OFF から ON に切り替えることができます。
SORACOM Harvest Data 設定ONスイッチはクリックすることで OFF から ON に切り替えることができます。
他の入力欄は空白になっていることを確認してください。

S+ Camera BasicのSIMの通信速度を変更します
[Menu]>[SIM 管理]とクリックして SIM 管理画面を開きます

S+ Camera BasicのSIMの「速度クラス」を s1.fast に変更します

画像の送信に時間がかかるとエラーとなる可能性があるため、s1.fast (2Mbps) に設定してください。

これでS+ Camera Basicを利用する準備が完了しました。

S+ Camera Basic を三脚に取り付ける

来客分析を行う場合、お客様の顔が撮影できる必要があります。そのため今回は三脚を用いて、S+ Camera Basicを設置できるようにしました。

三脚の1/4インチネジにカメラアダプタを取り付けます。

続いて、S+ Camera Basicの背面にカメラアダプタを取り付けます。

先に S+ Camera Basicとカメラアダプタを取り付けてしまうと、ねじ止めの際に電源ケーブルが絡む場合があります。取り付け順序にはご注意ください。

最終的には以下のように三脚に取り付けることが可能です。

S+ Camera Basic の電源を入れる

電源をいれて青いLEDが点滅すると、SORACOM Mosaic ConsoleにS+ Camera Basicが登録され、自動的にオンラインになります。

SORACOM Mosaicコンソールにアクセスしてカメラデバイスの状態を確認しましょう。SORACOMコンソールのグローバルメニューからアクセスできます。

前述のとおり、S+ Cameraの電源を入れると自動的にデバイス登録が実行され、リストに表示されます。最初はSIMのIMSIからデバイスを見分けるのがいいでしょう。(最初は一台だけから始められると思うので、このサンプル画面のように迷うことはないかもしれませんが)

リストに自分のカメラが表示されたら、DEVICE IDのカラムに表示されているIDのリンクをクリックすると下記のようなデバイスの詳細画面が表示されます。

S+ Camera Basic の動作を確認する

Device ID一覧から対象のデバイスをクリックすると、デバイスの詳細画面に移動します。現在カメラに写っている画像は、デバイス詳細画面のCAMERAメニューから確認することができます。

Updateボタンをクリックすると現在のカメラの画像が表示されます。

S+ Camera Basicでは暗い場所でも映像を取得するため、Non-IRカメラを採用しているため日中は赤味がかった画像になります。

以上でS+ Camera Basicが正常に稼働していることが確認できました。
この時点で画像が確認できない場合は、これまでの設定を確認してください。

Amazon S3での作業:画像保存用のバケットの作成

AWSの各種サービスで画像が利用できるように、Amazon S3に画像データを蓄積します。ここでは蓄積先のバケットを作成します。

本レシピではAWSリージョンを ap-northeast-1 (東京) として利用します。Amazon S3 でbacketを作成する前に、リージョンを確認し、必要であれば ap-northeast-1 へ変更してから、次の作業へ進んでください。

AWSのマネージメントコンソールから Amazon S3 を選択したあと バケットを作成 (バケットを作成)をクリックします。

「バケットを作成」では以下のように設定します。

設定名設定時の備考や注意
バケット名iot-recipe-cameradata-biuz9ar6名称自体は任意です。世界で唯一である必要があるため、末尾にランダムな文字列を付与しています。ランダム文字列生成等を利用すると素早く作成できます。
この名称は後ほど利用するため、メモしておいてください。
AWS リージョンアジアパシフィック (東京) ap-northeast-1ap-northeast-1 を選択してください。
パブリックアクセスを全てブロックONデフォルトで ON となっていると思いますが、確認してください。
バケットのバージョニング無効にするデフォルトで無効にするになっていると思いますが、確認してください。また、特に有効化する必要はありません。
タグ(操作の必要なし)特に操作する事はありません。
サーバー側の暗号化無効にするデフォルトで無効にするになっていると思いますが、確認してください。また、特に有効化する必要はありません。
詳細設定(操作の必要なし)特に操作する事はありません。

以上でAmazon S3 のバケット作成は終了です。

AWS Lambdaでの作業:Presigned URLを取得するLambda関数の作成

先ほど作成したAmazon S3 バケットはセキュアに運用するため、パブリックアクセスをOFFにしています。そのバケットへデータをアップロードする方法はいくつか方法がありますが、今回はアップロード専用の一時URLを利用するPresigned URL(署名付き URL)を利用します。Presigned URLの詳細な仕組みについてはこちらをご覧ください。

Presigned URLの発行にはAWS Lambdaを利用します。Presigned URLは、最終的にはIoTデバイス(S+ Camera Basic)に引き渡す必要があります。その方法は、SORACOM Funkを通じてAWS Lambdaを実行し、発行されたPresigned URLをIoTデバイスに返却します。そして、IoTデバイスはPresigned URLに対してアップロードを行います。

AWSのマネージメントコンソールから AWS Lambda を選択したあと関数の作成をクリックします。

「関数の作成」では、以下のように設定します。

設定名設定時の備考や注意
関数名iot-recipe-cameradata-presigned-url名称自体は任意です。後ほど利用するためメモしておいてください。
ランタイムPython 3.8
《これら以外の設定》(操作の必要なし)特に操作する事はありません。

コードの入力

まず実行のメインとなるコード(プログラム)を入力します。

コードソースの “lambda_function.py” をダブルクリック後、右側のコードエディタに以下のコードを入力します。初期から入力されている内容は、一度削除してから入力してください。

import os
import boto3

BUCKET_NAME = os.environ['BUCKET_NAME']
EXPIRATION = os.environ['EXPIRATION']

def get_presigned_url(method_name, bucket_name, object_name, expiration):
    presigned_url = boto3.client('s3').generate_presigned_url(
        ClientMethod = method_name,
        Params = {'Bucket': bucket_name, 'Key': object_name},
        ExpiresIn = expiration,
        HttpMethod = "PUT"
    )
    return presigned_url

def lambda_handler(event, context):
    print(event)
    try:
        print(context.client_context.custom)
        imsi = context.client_context.custom['imsi']
    except AttributeError:
        print("Not exists `context.client_context.custom` means in test mode on Lambda. Using _imsi_ instead.")
        imsi = event['_imsi_']
    uploading_object_name = "{}/{}".format(imsi, event['object_name'])
    presigned_url = get_presigned_url("put_object", BUCKET_NAME, uploading_object_name, EXPIRATION)
    return {"url": presigned_url}

入力できたら保存します。コードソースの [File] > [Save] をしてください。

環境変数の設定

先ほど入力したコードは汎用性を高めるために、アップロード先のバケット名等のパラメータを環境変数で設定できるようにしています。そのため、環境変数を設定していきます。

設定タブをクリックした後 [環境変数] > [編集] と進みます。

その後環境変数の追加をクリックし、以下2つの設定を行います。

キー備考
BUCKET_NAMEiot-recipe-cameradata-biuz9ar6Amazon S3で作成したバケット名を入力してください。
EXPIRATION30Presigned URL の有効期間を秒で指定します。ここでは30秒としました。

入力できたら [保存] をクリックします。

アクセス権限の設定

このコードから作成したバケットへアクセスできるように権限を付与します。

設定タブをクリックした後 [アクセス権限] をクリックし、”ロール名” をクリックします。

別のタブでIdentity and Access Management (AWS IAM)の管理画面が開きます。インラインポリシーの追加をクリックした後、以下の設定を行います。

設定名内容設定時の備考や注意
サービスS3フィルタアクションに “s3” と入力すると探しやすいです。
アクションPutObjectフィルタアクションに “putobject” と入力すると探しやすいです。
リソースarn:aws:s3:::iot-recipe-cameradata-biuz9ar6/*「ARN の追加」をクリックした後、Bucket name にAmazon S3で作成したバケット名を入力し、Object nameは「すべて」にチェックすると設定できます。

入力が終わったらポリシーの確認をクリックします。
確認の画面では、名前を S3PutObjectForPresignedURL (名前は任意です)と設定してからポリシーの作成をクリックします。

以上で権限設定ができたので、AWS IAM管理画面のタブは閉じてAWS Lambdaの管理画面に戻ります。

AWS Lambdaのテストを行う

ここまでの設定をテストします。テスト用データを作成してからdeployをしてテストを実施します。

テストタブをクリックした後、以下の入力を行います。

設定名内容設定時の備考や注意
テンプレート(操作の必要なし)特に操作する事はありません。
名前payload名前は任意です。

編集エリアには以下の内容を入力してください。初期から入力されている内容は、一度削除してから入力してください。

{
    "object_name": "TestFromLambda",
    "_imsi_": "440109999999996"
}

すべての入力が終わったら変更を保存をクリックします。

続いて、コードタブをクリックしたあとdeployをクリックします。右にあるステータスがChanges deployedと表示されたら、続いてTestをクリックします。

コードの入力部分に “Execution results” というタブが新しく表示され、”Response” に "url": "https://...." と表示されたら、AWS Lambdaは正常に動作しています。

発行されたURLの検証
AWS Lambda の実行結果で表示されたURLは、EXPIRATION で指定された秒数間以内であれば、どこからでもS3にアップロードが可能です。curlコマンドで行う場合は以下の通りです。
$ curl -X PUT --upload-file FILENAME 'https://....'

関数のARNを入手する

ここまでの動作が確認出来たら、最後にLambda 関数の ARN(Amazon Resource Name) を入手します。このARNは後ほど SORACOM Funk で使用するため、必ず入手してください。

Lambda 関数管理画面の上部にある ARN をメモしてください。(書類マークでコピーできます)

以上で AWS Lambda の設定は終了です。

SORACOM Funkの設定

S+ Camera Basicから先ほど作成したAWS Lambda関数を実行するために、SORACOM Funkを利用します。

まずAWS IAMにてLambda関数を実行できるIAMロールを作成します。その後SORACOM Funkへ、実行するLambda関数と、その時に使うIAMロールを指定します。これでSORACOM FunkからIAMロールを利用してLambda関数を呼び出し、Presigned URLを得ることができます。

AWS IAMでの作業:IAMロールの作成

SORACOM Funk から先ほど作成した Lambda 関数を呼び出せるように、専用の認証情報をAWSで作成します。

AWS マネジメントコンソールを開き AWS IAM管理画面 へと進み、[ロール] をクリックした後ロールの作成をクリックします。

「ロールの作成」では、まず “別のAWSアカウント” を選択した後、以下のように設定します。

設定名内容設定時の備考や注意
アカウント ID762707677580値は日本カバレッジの場合です。グローバルカバレッジで設定する場合は 950858143650 を指定してください。
外部IDが必要ON
外部IDiot-recipe-cameradata-presigned-url-haejasaed5Noo6Ij外部IDが必要をONにすると表示されます。
外部IDは任意の文字列ですが、SORACOMとの認証を確立する文字列であるため、推測を困難にするため末尾にランダム文字を割り当てています。ランダム文字列生成等を利用すると素早く生成できます。

入力ができたら [次のステップ: アクセス権限] をクリックします。

アクセス許可の設定画面では、ポリシーの一覧の中から “AWSLambdaRole” にチェックを付け、[次のステップ: タグ]をクリックします
※数が多いため、フィルタに lambdarole と入力すると素早く見つけることができるでしょう。

AWSLambdaRoleについて
AWSLambdaRoleはLambda関数であればどれでも実行できる権限を持つロールです。本番運用に向けては、実行可能なLambda関数をポリシーで指定することで、不要な実行を避けることが可能となりますので検討してください。

タグの追加画面では特に作業はありません。[次のステップ: 確認]をクリックします。

確認の画面では、ロール名を iot-recipe-cameradata-presigned-url-invoker-by-soracom (名前は任意です)と設定して、ポリシーに AWSLambdaRole が設定されていることを確認したら、[ロールの作成]をクリックします。

ロールのARNを入手する

ここまでの作業を終えたら、最後にロールのARNを入手します。このARNは後ほど SORACOM Funk で使用するため、必ず入手してください。

ロール一覧から先ほど作成したロールをクリックします。そこに表示されているロール ARNをメモしてください。(書類マークでコピーできます)

以上で AWS IAM の設定は終了です。

SORACOM Funkでの作業:呼び出すAWS Lambda関数と呼び出し時の認証情報の指定

SORACOM Funkの設定は「グループ」で行います。

SORACOMユーザーコンソールのSIM 管理画面から、S+ Camera Basicに割り当てたグループ名をクリックします

グループの設定画面で “SORACOM Funk” を探してクリックします。すると SORACOM Funk の設定画面が開きます。

SORACOM Funk の設定では、まず “OFF” となっている部分をクリックします。すると “ON” に切り替わり、設定ができるようになります。その後、先に設定できる以下3項目を設定します。(認証情報は後ほど設定します)

設定名内容設定時の備考や注意
サービスAWS Lambda
関数のARN(AWS Lambda管理画面でメモをした関数のARN)
送信データ形式JSON

3つの項目の入力が完了したら、認証情報のプラス文字をクリックします。

認証情報を登録する画面では以下の通りに設定した後、[登録]をクリックします。

設定名内容設定時の備考や注意
認証情報 IDiot-recipe-cameradata-presigned-url-invoker任意です。後で見返した時にわかりやすくしておくと良いでしょう。
種別AWS IAM ロール認証情報
ロール ARN(AWS IAM管理画面でメモをしたロールARN)
外部ID(AWS IAM管理画面で設定をした外部ID)外部IDはAWS IAMの管理画面からロールを表示し “信頼関係” タブで確認できます。

SORACOM Funk 設定画面に戻ってきたら、 [保存] をクリックします。

Unified Endpoint 設定

Unified Endpointとは、送信されたデータを SORACOM Beam / Funk / Funnel / Harvest Data に一括送信します。これにより、デバイスからのデータ送信先は単一のURLで済むようになる機能です。今回はSORACOM Funkから返却されたデータを優先表示するように設定します。

グループの設定画面内で “Unified Endpoint 設定” を探してクリックします。以下のように設定します。

設定名内容設定時の備考や注意
フォーマットSORACOM Funk

設定が終わったら [保存] をクリックします。
以上でSORACOM Funkの設定は終了です。

テストをする

S+ Camera Basicにログインをして、SORACOM Funk経由での Presigned URL の入手と、アップロードの確認を行います。

[Menu] から [データ収集・蓄積・可視化] > [SORACOM Mosaic Console] を開きます。
※直接 MOSAIC CONSOLE を開く方法もあります。

MOSAIC CONSOLEのデバイス一覧から S+ Camera Basic を開き、Remote Accessをクリックします。

「On-deman Remote Access」では、特に設定の変更をせずにUpdateをクリックします。すると、リモートアクセスに必要な情報が表示されます。この中で SSH の情報を使用してSSHアクセスをしてください。

SSHによるアクセス
Windows 10の最新版であればコマンドラインで、またmacOSであればTerminal.appでSSHアクセスが可能です。
また、ログイン時のパスワードはこちらから設定することができます。

SSHによるログインができたら、以下のコマンドにて Presigned URL の発行を確認します。

curl -w "\n" -H "Content-Type: application/json" -d '{"object_name":"test_upload.txt"}' uni.soracom.io

この時、結果が以下のように {"url": "https://..."} から始まる内容であれば成功です。

{"url": "https://iot-recipe-cameradata-biuz9ar6.s3.amazonaws.com/440103217330759/test_upload.txt?AWSAccessKeyId=ASIA53DDNU3CPNULO6IG&Signature=uZiNASMRuBHEIPAQYcH5IcnboCs%3D&x-amz-security-token=IQoJb3JpZ2luX2VjEJ%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FwEaDmFwLW5vcnRoZWFzdC0xIkcwRQIgZfZLtEpsQspadvR0g%2BVI4lWiDI%2BkYfNIPq7nsBwi2S4CIQCLTRNbFDBLEaugUH8u1qKU4FrFxAc%2BTH762oYDxO%2F0ByriAQgYEAIaDDk1MTU0Mzc2ODc3MiIMhowD%2FYXuOjxLGv97Kr8BN23TVOsByx46bmyi1Njrq6qlKxJ5Caog%2FbF4oFtuGHNKjsBnfD8ORV8Pr0EZ0UGBmdVsJtNwukT4AxmhZDxryaqEzkvnma15rxvQ45yzvMXL7TRPfI%2Bv%2FfipTKlJPEJaw41r4Xx8c64mCLTUweCVxrd0%2FW8IhEQqv4YRZWjgWUtH2hbAIYhMbmIj3XcwUkoanM9DSLgt71K8VOuVH%2FIV8rm49b48FAAad7z3F3zfjW3%2FfyU8ObRhtkbAyD%2BK%2BcUw2oSWhAY64AFmh%2FmZB1lcm7OFA72oBWcG8h16hxYwl%2BbZ6MieJb83WvAZECDY3FYla4bpNJSmnv%2Bs6lIOVUOR%2FDmLb3lk9IaTOzPZsi1ujClBJiRIKEuZdMYTykrCyNCXFu9cZtuqa839CC5hto3B2mj0pNftqv8GYitSwhk58fmXbJ5CscXON35XGU0PRoNRaU%2B3j%2F5hdRPlLB4OH51bCPCNTTNRTbxArp4dw5NVIY%2BHD5bE2xMyibOYl16HZNF0H3PM65BknqiEBCQ%2F5DioscMcRSz7UQrNEVVh%2BNtIlZC3FFaSqdBQpw%3D%3D&Expires=1619362753"}

上記のように表示されなかった場合は、以下を確認してみてください。

  • {"result": ... から始まる場合 : Unified Endpoint の設定を見直してください。
  • AccessDeniedException となった場合 : AWS IAM のIAMロールに割り当てられているポリシーが AWSLambdaRole となっているか、また、外部IDが一致しているか確認してください。
  • ResourceNotFoundException となった場合 : AWS Lambda 関数が存在しているか確認してください。

Presigned URL が得られたら、次はアップロードのテストです。以下のコマンドにて Presigned URL を得つつ、curl でファイルをアップロードします。
OSのバージョンが書かれているファイルを、Amazon S3に “test_upload.txt” というファイル名でアップロードします。

curl -X PUT --upload-file /etc/debian_version $(curl -s -H "Content-Type: application/json" -d '{"object_name":"test_upload.txt"}' uni.soracom.io | jq -r .url)

実行結果が何も表示されなければ成功です。Amazon S3 の管理画面から、バケットの内容を表示すると IMSI番号/test_upload.txt というオブジェクトが作成されている事が確認できます。ダウンロードして中身を表示すると “10.3” というバージョン番号が確認できるでしょう。
この test_upload.txt は、いつでも削除可能です。

curlの実行結果でなんらかの表示がされた場合は、以下を確認してみてください。

  • AccessDenied となった場合 : Lambda関数作成におけるアクセス権限の設定を見直してください。Amazon S3 の当該バケットに対する PutObject 権限が必要です。

ここまでの設定で、特にAWSに関するログはCloudWatch Logsでも確認可能です。

Presigned URLの取得とアップロードが成功したら、この項目は終了です。

コラム: S+ Camera Basicのリクエスト内容を確認する

今回はデータ送信にUnified Endpointを利用しています。これにより、SORACOM Funkと同時にデータ蓄積・収集サービスSORACOM Harvest Dataへも、データが送信されている状態です。そのため、SORACOM Funkならびにその先のAWS Lambdaの設定が未完了でも、S+ Camera Basicが送信したリクエストの中身を調べることができます。

SORACOM Havest Dataでデータを確認するには、MOSAIC CONSOLEのデバイス詳細画面内のHarvest Dataをクリックします。

表示された画面の下部にデータテーブルがあり、そこで S+ Camera Basic からのデータを確認いただけます。

S+ Camera Basicの設定

S+ Camera Basicでは、顔検出及び顔の部分を切り出してAmazon S3へ画像をアップロードさせます。顔を検出する部分にはface recognitionを利用しています。この部分については、こちらで予め用意したファイルがあるため、SORACOM MosaicのCustom Algorithmへ次のURL指定してアルゴリズムをインストールしてください。

http://public.harvest-files.soracom.io/io.soracom.mosaic/sample/CameraApp0_Face/CameraApp0.tgz
顔検出アルゴリズムのインストール

インストールが完了すると、次の様なアルゴリズムの設定画面が表示されます。今回は特に値を設定せずに進めます。

インストール完了画面

S+ Camera Basic内にインストールしたアルゴリズムの解説については、Appendix(巻末)をご覧ください。

テスト:顔写真のアップロード

顔の撮影

S+ Camera Basicで撮影した画像がS3にアップロードされることをテストします。S+ Camera Basicの前にこの様な写真をおいて、しばらくするとS3に顔写真が送られていることが確認できると思います。

Amazon S3に送信されたファイル

ファイルがうまく作成されない場合には次の部分をチェックしてください。

  • S+ Camera Basicのログにエラーが表示されていないかを確認
  • AWS Lambdaのログにエラーが表示されていないかを確認
    • ログは、AWS Lambda->Monitor->View logs in CloudWatchから確認することができます。

オプション:Amazon Rekognition連携

Amazon SNSを利用する事でS3にファイルがアップロードされた場合に、AWSの他のサービスを呼び出すことができます。この機能を利用してアップロードしたファイルをAmazon Rekoginitionで分析してみましょう。次のAWS SNSに次のAWS Lambda関数を登録してください。尚、顔の画像データのプライバシーやセキュリティなどには、十分にご注意の上ご実装ください。

import json
import logging
import os
from datetime import datetime as dtdt
import time
import boto3
import elasticsearch


logger = logging.getLogger()
level_name = os.environ.get('LOGLEVEL', 'INFO')
level = logging.getLevelName(level_name)
logger.setLevel(level)

ES_URL = os.environ.get('ES_URL')
ES_INDEX_PREFIX = os.environ.get('ES_INDEX_PREFIX')
elasticsearch = elasticsearch.Elasticsearch([{'host': ES_URL}])
s3 = boto3.client('s3')
rekognition = boto3.client('rekognition')

def is_bad(detect_result):
    faces = detect_result['FaceDetails']
    if len(faces) == 0:
        return 'no face detected'
    if len(faces) != 1:
        return 'multiple faces'
    if abs(faces[0]['Pose']['Yaw']) > 40:
        return 'not a front face'
    return None


def send_es(face_props, image_name):
    # TODO: use the time that the image was taken, rather than the current time
    timestamp = int(time.time() * 1000)
    face_props['timestamp'] = timestamp
    face_props['hostname'] = str(os.uname().nodename)
    ar = face_props['AgeRange']
    face_props['AgeRange']['Mean'] = int((ar['Low'] + ar['High']) // 2)
    index = ES_INDEX_PREFIX + dtdt.now().strftime('%Y%m%d')
    doc_id = '%s_%s' % (timestamp, image_name)
    elasticsearch.index(
        index=index, doc_type='_doc',
        id=doc_id, body=face_props)

def delete_image(photo, bucket):
    try:
        response = s3.delete_object(
            Bucket=bucket,
            Key=photo
        )
    except Exception as error:
        logger.error(error)
        return None
    return response

def detect_faces(photo, bucket):
    detect_result = rekognition.detect_faces(
        Image={'S3Object': {'Bucket': bucket, 'Name': photo}}, Attributes=['ALL'])
    logger.info('Detected faces for %s', photo)
    logger.info('detect_result: %s', detect_result)
    if detect_result is None:
        logger.error('failed to detect_faces')
        return None
    bad_reason = is_bad(detect_result)
    if bad_reason:
        logger.info('not good for identification (%s)', bad_reason)
        return None
    face_props = detect_result['FaceDetails'][0]
    return face_props


def lambda_handler(event, context):
    message = json.loads(event['Records'][0]['Sns']['Message'])
    logger.info('message: %s', message)
    bucket_name = message['Records'][0]['s3']['bucket']['name']
    key = message['Records'][0]['s3']["object"]['key']
    logger.info('bucket_name: %s', bucket_name)
    logger.info('key: %s', key)

    # detect face using face rekognition
    response = detect_faces(key, bucket_name)

    # upload result to elasticsearch
    if response:
        logger.info(response)
        #response = send_es(response, key)
    #response = delete_image(key, bucket_name)

75-76行目でS3にアップロードされた画像のbucketとobject名を取得し、57行目でRekognitionを用いて顔の解析を行います。AWS Lambdaに対しては予め、Rekognitionの実行権限とS3に対するアクセス権限を追加してください。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": "rekognition:DetectFaces",
            "Resource": "*"
        }
    ]
}
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "s3:*"
            ],
            "Resource": "arn:aws:s3:::presigned-url-test/*"
        }
    ]
}

先ほどと同様、S+ Camera Basicで顔写真を撮影して、S3にアップロードすると、AWS Lambdaの実行ログに次の様にRekognitionの結果が表示されると思います。

必要に応じて、結果の視覚化のためにAWS Elasticsearch Serviceへ結果を送信したり(86行目)、解析した画像を削除したり(87行目)することが可能です。

AWS Elasticsearch Serviceを用いた視覚化

参考資料

あとかたづけと注意事項

本レシピでは継続的に費用がかかるサービスとして、SORACOM Mosaic及び、AWSサービスを利用しています。今後作成したシステムを利用しない場合には、設定したファイルやログなどを削除してください。

Appendix: CameraApp0.tgz 解説

CameraApp0.tgzはS+ Camera Basicにおけるアルゴリズムのパッケージングフォーマットです。パッケージングはtar+gzipを使用しているため、どのような環境でも作成いただけます。

今回配布している CameraApp0.tgz は以下のようになっています。

CameraApp0.tgz
├── CameraApp.py
├── CameraApp0
├── PreSetup
├── Resources
│   ├── shape_predictor_68_face_landmarks.dat.bz2.md5
│   ├── shape_predictor_68_face_landmarks.dat.md5
│   ├── soracom_face_mask_detection.tflite.bz2.md5
│   └── soracom_face_mask_detection.tflite.md5
├── __init__.py
├── fisheye_correction.py
├── info.json
├── soracom_device_interface.py
├── soracom_harvest_files.py
└── soracom_image_analize.py

このうち S+ Camera Basicより実行されるエントリーポイントとなるファイルは CameraApp0 ですが、CameraApp0 は CameraApp0.py をimportして実行するだけのブリッジスクリプトであるため、実質のメインは CameraApp0.pyとなります。

以下 CameraApp0.py の全貌です。

#!/opt/soracom/python/bin/python

# ======================================================
# Project Name    : SORACOM Mosaic CameraApp0 Sample
# File Name       : CameraApp0.py
# Copyright       : 2015-2021 SORACOM, INC.
# ======================================================

import os
from time import sleep
from datetime import datetime
import json
import re
import logging
from logging import getLogger
import traceback
import requests
import cv2
import numpy as np
from PIL import Image
import face_recognition
from soracom_device_interface import SoracomDeviceInterface

# log settings
FORMAT = '%(levelname)s %(asctime)s %(funcName)s %(filename)s %(lineno)d %(message)s'
logging.basicConfig(level=logging.DEBUG, format=FORMAT)
logger = getLogger(__name__)

DEVICE_INTERFACE_URI = os.getenv('DEVICE_INTERFACE_URI', None)
ENDPOINT = os.getenv('SORACOM_ENV_ENDPOINT', None)
UPLOAD_IMAGE_QUALITY = 100

# set DEVICE_INTERFACE_URI, if it is not set.
if DEVICE_INTERFACE_URI is None:
    DEVICE_INTERFACE_URI = 'http://127.0.0.1:8080'

# image upload interval. too short interval increases upload data volume.
WAIT = int(os.getenv('SORACOM_ENV_WAIT', '60'))

if WAIT < 3:
    WAIT = 3

# set debug mode
if ENDPOINT is None:
    DEBUG_MODE = True
    ENDPOINT = 'urn:dev:ops:SORACOM-Mosaic-' + os.uname()[1]

logger.info('DEVICE_INTERFACE_URI = %s', DEVICE_INTERFACE_URI)
logger.info('ENDPOINT = %s', ENDPOINT)
logger.info('WAIT = %d', WAIT)
logger.info('UPLOAD_IMAGE_QUALITY = %d', UPLOAD_IMAGE_QUALITY)

def get_s3_presignedurl(file_name):
    "get presigned url from funk"

    endpoint = 'http://unified.soracom.io'
    payload = json.dumps({
        "object_name": file_name
        })
    try:
        response = requests.post(endpoint, json=payload)
        logger.debug("response: ", response.json())
    except Exception as error:
        logger.error("can't get signed url.")
        logger.error(error)
        return ""
    return response.json()

def upload_s3(file_name, upload_data):
    "upload data to S3"
    response = get_s3_presignedurl(file_name)
    presigned_url = response.get('url', None)
    if presigned_url:
        try:
            _ = requests.put(presigned_url, data=upload_data)
        except Exception as error:
            logger.error("can't upload data.")
            logger.error(error)

def upload_image(image_list):
    "create data for upload"

    now = datetime.now()
    dir_suffix = now.strftime('%Y/%m/%d')

    logger.info('imencode start')
    name_extension = ""
    index = 0
    for image in image_list:
        file_prefix = now.strftime('%Y-%m-%dT%H-%M-%S') + \
            "-" + str(now.microsecond)
        # convert to jpeg
        logger.info('imencode start')
        encode_param = [cv2.IMWRITE_JPEG_QUALITY, UPLOAD_IMAGE_QUALITY]
        _, im_data = cv2.imencode('.jpeg', image, encode_param)
        im_data_byte = im_data.tobytes()
        name_extension = "jpeg"
        logger.info('imencode end')

        hostname = re.sub('^SORACOM-Mosaic-', '', ENDPOINT.split(':')[3])
        name_prefix = '%s/%s/' % (hostname, dir_suffix)

        basename = '%s_%s_%02d.%s' % (
            hostname, file_prefix, index, name_extension)
        file_name = name_prefix + basename
        logger.info('image name = %s', file_name)
        upload_s3(file_name, im_data_byte)

def find_face_locations(image_bgr, expansion=1.1):
    "return face image and locations"

    info = {}
    face_image_list = []
    if image_bgr is None:
        return info
    logger.debug('faceLocations start')
    base_height, base_width = image_bgr.shape[0:2]
    face_locations = face_recognition.face_locations(image_bgr, model='cnn')
    for location in face_locations:
        try:
            top, right, bottom, left = location
            face_width = int((right - left) * expansion)
            face_height = int((bottom - top) * expansion)
            width = face_width if face_width < base_width else base_width
            height = face_height if face_height < base_height else base_height
            side = max([width, height])
            x_p = left + (width - side) // 2
            y_p = top + (height - side) // 2
            face_image = image_bgr[y_p:y_p+side, x_p:x_p+side]
            face_image_list.append(face_image)
        except Exception as error:
            logger.error("can't get face location by %s", error)
    logger.debug('faceLocations end')
    return face_image_list

def Interval():
    "find face and upload to s3 periodically"

    try:
        # create SoracomDeviceInterface
        device_interface = SoracomDeviceInterface()
        #deviceInterface.setCaptureSize(DEVICE_INTERFACE_URI, 1640, 1232, 1)
        #deviceInterface.setCaptureSize(DEVICE_INTERFACE_URI, 1640, 922, 1)

        # set capture size to 1280x720
        device_interface.setCaptureSize(DEVICE_INTERFACE_URI, 1280, 720, 2)

        # set capture size to 640x480
        #deviceInterface.setCaptureSize(DEVICE_INTERFACE_URI, 640, 480, 1)

        # initial startup delay
        sleep(5)

        while True:
            image_bgr = device_interface.getCapture(False)
            if image_bgr is None:
                sleep(WAIT)
            face_image_list = find_face_locations(image_bgr)
            upload_image(face_image_list)
            sleep(WAIT)
    except Exception as error:
        traceback.print_exc()
        logger.error(error)
        logger.info('sleep: %d', WAIT)
        sleep(WAIT)

def main():
    "main function"
    Interval()

if __name__ == '__main__':
    logger.info('main')
    main()
  • Interval()内のwhileループにて動き続けるように構成されています。
  • get_s3_presignedurl()にてUnified Endpointにリクエストを投げることで、SORACOM FunkとAWS Lambdaを通じてpresigned URLを取得します。引数として渡したfile_nameがAmazon S3に画像が保存された際のファイル名となります。
  • 17行目のupload_s3()では、得られたpresigned URLに対して、画像(upload_data)をputしています。
SORACOM IoT DIY レシピ »

レシピの達成、おめでとうございます!

達成したことをTwitterで呟く

普段の生活やビジネスに役立つ #IoTレシピ 「AIカメラで来客分析」 を達成しました!
@SORACOM_PR https://soracom.jp/recipes_index/8025


ご質問などはこちらよりお問い合わせください。