Skip to the content.

ステップ3. 顔認証のWeb APIを作成する(SORACOM回線を使ったスマートフォンとAWSサービスを用いた画像認識サービスを構築する)

当コンテンツは、エッジデバイスとしてスマートフォン、クラウドサービスとしてAWSを利用し、エッジデバイスとクラウド間とのデータ連携とAWSサービスを利用した画像認識を体験し、IoT/画像認識システムの基礎的な技術の習得を目指す方向けのハンズオン(体験学習)コンテンツ「SORACOM回線を使ったスマートフォンとAWSサービスを用いた画像認識サービスを構築する」の一部です。

ステップ3. 顔認証のWeb APIを作成する

ステップ3アーキテクチャ図

ステップ3では、アップロードされた画像を分析し、「事前登録済みの人物が写っているか、写っている場合は誰かを判定する」機能を持ったWeb APIを作成し、デバイス側からAPIを呼び出す仕組みを構築します。

まずは、「Amazon Rekognition(以下、Rekognition)」を利用して「コレクション」を作成し、認識対象となる顔を登録します。 次に、作成したコレクションを使って顔の認識を行う「AWS Lambda(以下、Lambda)」を作成します。 さいごに、作成したLambdaにHTTPでアクセスするために「Amazon API Gateway(以下、API Gateway)」を作成します。


目的

概要


<Web APIとは?>

HTTPプロトコルを利用してインターネットを介したアプリケーション間のやりとりを行うためのインターフェースです。 Web APIの代表的な実装方式として、RESTとSOAPが存在しており、今回作成するWeb APIはREST APIとなります。

様々な公開Web APIが提供されていることや、わかりやすいデータのやり取りから、Web APIを利用してモノリシックなシステムをマイクロサービス化することが現在の潮流となっています。

<AWS Lambdaとは?>

AWS Lambdaは、サーバーをプロビジョニングしたり管理する必要なくコードを実行できるサーバーレスコンピューティングサービスです。これは他のAWSサービスをトリガーとして実行することができ、実際に処理にかかった時間やリクエスト数に対して従量課金されます。Lambda関数はJavaやPythonをはじめとした一般的なプログラミング言語で作成することができます。

より詳しく知りたい場合は公式サイトをご確認ください。

<Amazon API Gatewayとは?>

Amazon API Gatewayは、開発者があらゆる規模でAPIの公開、保守、モニタリング、セキュリティ保護、運用を簡単に行えるフルマネージドサービスです。 セキュアで信頼性の高いAPIを大規模に稼働させるために、差別化にはつながらない面倒な作業を処理する従量制のサービスです。 AWS、または他のウェブサービス、AWSクラウドに保存されているデータにアクセスするAPIを作成できます。ユースケースとしては、モバイルアプリやWebアプリケーションのバックエンドAPIとして、API GatewayとLambdaを連携させて、サーバレスなREST API環境を構築するケースでよく使われます。

より詳しく知りたい場合は公式サイトをご確認ください。

<APIキー認証とは?>

API Gatewayがサポートする認証方式の1つで、所定のキー(文字列)をHTTPヘッダ(x-api-keyヘッダ)に含めたリクエストであればアクセスを許可、無ければアクセスを拒否する機能です。APIキーの発行・管理は、API Gateway側で行われます。 また、ユーザごとにAPIキーを発行し、ユーザごとに呼び出し回数に制限をかけるというような使用法も可能です。 ただし、APIキーはヘッダー解析などをされることで漏洩する可能性がありますので、認証の唯一の手段としてAPIキーを使用することはベストプラクティスではありません。 認証機能が必要な場合は、IAMロール、Lambdaオーソライザー、または Amazon Cognitoユーザープールを使用するようにしてください。 https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/api-gateway-api-usage-plans.html

3-1. Lambdaを作成

ここでは、スマホからAPI Gatewayを通して受け取った顔画像が、ステップ2で作成したRekognitionのコレクションに登録済みの人物とマッチするかどうか、マッチする場合は誰であるかリターン値として返すLambdaファンクションを作成します。

3-1-1. Lambda関数を作成する

「基本的なLambdaアクセス権限」を指定することで、Lambda実行時のログをCloudWatch Logsにアップロードするためのロールが自動的に付与されます。 次のステップで、Lambdaのソースコードから使用するAWSサービスに対する必要な権限をカスタムで追加します。

3-1-2. Lambdaに必要な権限を付与する

LambdaからRekognitionのAPIにアクセスができるように、以下の手順で権限をインラインポリシーとして追加してください。

3-1-3. Pythonの関数コードを作成する

Lambdaが実行するPythonコードを作成します。

  # Rekognitionで作成したコレクション名を入れてください
  COLLECTION_ID = '{collection_id}'
  # Rekognitionで作成したコレクション名を入れてください
  COLLECTION_ID = 'yamada-authentication-collection'

3-1-4. Lambdaのタイムアウト時間の設定を行う

今回のLambdaの関数コードでは、パラメータで受け取った画像データをRekognitionに渡して画像認識を実行します。
画像のサイズによっては処理時間がかかることを想定し、タイムアウト設定をデフォルトの3秒から20秒に変更します。

3-1-5. Lambdaのテストを実行する

Lambdaの関数コードを保存したら、API Gatewayから渡されてくる想定のイベントデータを用意し、Lambdaに渡して、実際の動きをテストしてみましょう。

3-1-6. 作成プログラムの解説

今回使用するLambdaコードの解説となります。

  import json                      # JSONフォーマットのデータを扱うために利用します
  import boto3                     # AWSをPythonから操作するためのSDKのライブラリです
  from typing import Union, Tuple  # 変数や引数・返り値などの型定義に利用します
  from datetime import datetime    # この関数を動かした(=顔の認証を行なった)時間をtimestampとして作成するのに利用します
  import botocore.exceptions       # boto3の実行時に発生する例外クラスです
  import base64                    # base64フォーマットの画像データをRekognitionが認識できる形に変換するのに利用します
  # 画像データから不要な値を抜き出す(コンマがない場合は不要な値がないとし、全文字列を利用)
  image_base64str = image_base64str[image_base64str.find(',') + 1 :]
  return base64.b64decode(image_base64str)
  # RekognitionのClientを生成
  REKOGNITION_CLIENT = boto3.client('rekognition')
  # 画像とマッチする人物を特定する
  search_result = REKOGNITION_CLIENT.search_faces_by_image(
      CollectionId=self.collection_id,
      Image={
            'Bytes': image_binary
      },
      MaxFaces=self.max_faces,
      FaceMatchThreshold=threshold
  )
  # タイムスタンプ生成
  date_now = datetime.now()
  # 辞書型の変数定義
  payloads: dict = {}
  # タイムスタンプを設定
  payloads['timestamp'] = str(date_now.strftime('%Y-%m-%d %H:%M:%S'))
  # search_faces_by_imageの戻り値を設定
  payloads.update(search_result)
  # レスポンス用のBodyを生成
  body = json.dumps({'msg': msg, 'payloads': payloads})

3-2. API Gatewayを作成する

デバイス側から作成したLambdaを使用するために、API GatewayでREST APIを作成します。

3-2-1. APIを作成する

3-2-2. APIにリソースを追加する

作成直後のAPIには、具体的なアクセス先のリソースが存在していない状態です。 APIにリソースを追加し、ステップ3-1で作成したLambdaを呼び出すAPIリソースを作成します。


<リソースとは?>

Amazon API Gatewayでは、API Gatewayリソースと呼ばれるプログラム可能なエンティティのコレクションとして REST API を構築できます。各 Resource エンティティは、Method リソースを 1 つ以上持つことができます。リクエストパラメーターと本文で表された Method は、クライアントが公開された Resource にアクセスするためのアプリケーションプログラミングインターフェイスを定義し、クライアントによって送信された受信リクエストを表します。 https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/how-to-create-api.html

<CORSとは?>

Cross-origin resource sharing (CORS) は、ブラウザで実行されているスクリプトから開始されるクロスオリジン HTTP リクエストを制限するブラウザのセキュリティ機能です。 REST API のリソースが非シンプルクロスオリジンの HTTP リクエストを受け取る場合、CORS サポートを有効にする必要があります。 Web APIは、元来パブリックに公開されず、リソースのオリジンが共有された一つのサービスの内部機能として利用されていました。 そのことから、オリジンが異なるリソースからのアクセスを受けた際、デフォルトではアクセスを拒否してしまいます。 CORSを設定することで、どのアクセス元からのアクセスを許可するかを制御することができるようになります。 https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/how-to-cors.html


3-2-3. APIにメソッドを追加する

APIにはHTTPメソッドが必要です。 ステップ3-1-3で作成したLambda実行コードに必要なデータをAPI経由で渡す必要があるため、レスポンスボディが使用できるPOSTにします。

3-2-4. APIの統合先をセットする

APIメソッドを設定したら、バックエンドのエンドポイントに統合する必要があります。 バックエンドのエンドポイントは、統合エンドポイントとも呼ばれ、Lambda関数、HTTPウェブページ、または AWSのサービスアクションとして使用できます。 今回は、前ステップで作成したLambdaと統合しましょう。

<Lambdaプロキシ統合とは?>

Amazon API Gateway Lambdaプロキシ統合は、単一のAPIメソッドのセットアップでAPIを構築するシンプル、強力、高速なメカニズムです。 Lambdaプロキシ統合は、クライアントが単一のLambda関数をバックエンドで呼び出すことを可能にします。 クライアントがAPIリクエストを送信すると、API Gatewayは、統合されたLambda関数にリクエストをそのまま渡します。 このリクエストデータには、リクエストヘッダー、クエリ文字列パラメータ、URLパス変数、ペイロード、および API設定データが含まれます。 バックエンドLambda関数では、受信リクエストデータを解析して、返すレスポンスを決定します。

詳細は公式ドキュメントをご確認ください。

3-2-5. APIへのアクセスにAPIキーを必要とする設定にする

APIを公開すると、エンドポイントURLを知っている人は誰でもアクセス可能となります。 今回作成するAPIには、簡易な認証機能としてAPIキーによる認証を追加しましょう。

OPTIONSメソッドの「APIキーの必要性」は trueにする必要はありません。

<OPTIONSメソッドとは?>

OPTIONSメソッドは、リソース作成時にCORSを有効にすることで自動的に作成されます。 このメソッドは、リソースに対するアクセスの際に、必ず最初に経由されます。 これにより、受け取ったリクエストのオリジンをOPTIONSのものと置き換えることで、オリジンの違いによるアクセス拒否を回避しています。

3-2-6. APIをデプロイする

作成したAPIは、デプロイすることで外部からアクセスすることが可能となります。 作成したAPIをデプロイしてみましょう。

ステージは、APIを公開後も安定してAPIのエンドポイントを供給しながら開発を行う上で必須の機能です。

<API Gatewayのステージとは?>

ステージは、デプロイに対して名前を付けたAPIのスナップショットとなります。 API Gatewayでは、ステージごとに別の設定やステージごとの変数を定義することが出来ます。 また、APIにアクセスするエンドポイントURLにはステージ名が含まれますので、例えば「v1.0」「v2.0」というステージを用意して 過去バージョンを保証したデプロイや、ステージを利用したカナリアリリースが可能となります。 詳細は公式サイトをご確認ください。

3-2-7. APIの使用量プランとAPIキーを作成する

APIキーに対して使用量プランを設定しましょう。 以下の説明を参考に紐付けるAPIキーでのアクセスの頻度を想定して、使用量を設定してください。


<使用量プランとは?>

API Gatewayでは、使用量プランに紐付けたAPIキーの利用頻度を測定・制限することが可能です。 APIキーは使用できる回数分だけ「トークンバケット」に補給され、ここにある分だけがAPIへのアクセスに利用できます。 ページ内項目の「スロットリング」の「レート」は1秒ごとにトークンバケットに登録されるAPIキーを、「バースト」はバケットに入るトークンの最大数を表しています。 「クォータ」はある期間中にAPIキーが利用できる回数を示します。 この他にも様々な方法でAPIの利用を監視・制限できます。 詳細は公式ドキュメントをご確認ください。


3-2-8. cURLでAPIをテストする

APIGAtewayが正常に稼働することを確認するために、cURLの curlコマンドを利用して、前のステップで作成したAPIにアクセスしてみましょう。

今回のケースでは、画像のBase64形式データの文字数が多く、コマンドラインからの直接入力は大変なため、shellファイルを修正してコマンドラインから実行します。

3-2-8-1. Shellファイルを編集する

<curlコマンドの解説>

echoコマンドについて

curlコマンドについて

API IDの確認方法

APIキーの確認方法

AWSのコンソールで、ステップ3-2-7で作成したAPIキーを[表示]します。

3-2-8-1_2

{"msg": "[SUCCEEDED]Rekognition done", "payloads": {"timestamp": "2021-01-21 00:30:17", 
"SearchedFaceBoundingBox": {"Width": 0.38512182235717773, "Height": 0.4992198646068573, 
"Left": 0.34464046359062195, "Top": 0.27895471453666687}, "SearchedFaceConfidence": 99.99970245361328, 
"FaceMatches": [], "FaceModelVersion": "5.0", "ResponseMetadata": {"RequestId": "241e83cb-57c3-478b-a57b-b408aa04b5eb", 
"HTTPStatusCode": 200, "HTTPHeaders": {"content-type": "application/x-amz-json-1.1", 
"date": "Thu, 21 Jan 2021 00:30:16 GMT", "x-amzn-requestid": "241e83cb-57c3-478b-a57b-b408aa04b5eb", 
"content-length": "223", "connection": "keep-alive"}, "RetryAttempts": 0}}

<cURLとは?>

URLの書き方でファイルの送受信が行えるオープンソースのコマンドラインツールです。 WebサイトやAPIサーバーのポート疎通確認によく利用され、HTTP/HTTPSの他にも様々なプロトコルに対応しています。コマンドラインで curlコマンドとして利用します。

<Base64とは?>

Base64とは、データを64種類の印字可能な英数字のみを用いて、それ以外の文字を扱うことの出来ない通信環境にてマルチバイト文字やバイナリデータを扱うためのエンコード方式です。 具体的には、A–Z, a–z, 0–9 までの62文字と、記号2つ (+, /)、さらにパディング(余った部分を詰める)のための記号として = が用いられる。


3-3. 【参考】 Web APIを利用するプログラム

3-3-1. 顔認証を行うWeb APIにアクセスするプログラムを作成する

今回はWebアプリケーションは構築済みですので、参考情報としてどのようにWebアプリケーション(Vue.jsプログラム)側を実装するのかをご紹介します。

デプロイしたWebアプリケーションにはすでにAPIにアクセスするためのプログラムが組み込まれていますので、こちらの章ではコードを適宜引用しながら、APIにアクセスしている部分の解説を行います。

APIアクセス用のプログラムの解説

SORACOMメタデータを使用する箇所の解説は、次のステップで行いますので、こちらでは省略します。

<template></template>タグ内の要素について

<script></script>タグ内の要素について

  import axios from 'axios';
APIへのアクセスに必要なパラメータを準備する

リクエストヘッダー情報

APIキーを、'x-api-key': 'xxxxxxxxxxxxxxxxxxxxxxxx'という形式で、ヘッダー情報に組み込みます。

また、やり取りするデータ形式を指定するため 'Content-type': 'application/json'も入れましょう。

this.apiKeyで、このコンポーネントが持つapiKeyプロパティの値を取得できます。ここでは、コードの27行目で画面から入力された値が最新値として代入されます。

this.apiKeyには画面生成時にCofigファイル(Mixins)の値を初期値として設定しています。 https://github.com/IoTkyoto/soracom-ug-reko-handson/blob/master/webapp/src/mixins/ConfigMixin.js

// 画面作成時にコンポーネント内で利用する変数の初期値を設定する
created() {
  ・・・(省略)・・・
  this.apiKey = this.config.searchConfig.apiKey
  ・・・(省略)・・・
},

// ヘッダー情報を設定する
const config = {headers: {
  'Content-Type': 'application/json',
  'x-api-key': this.apiKey,
}};

リクエストボディ情報

ステップ3-1-3で作成したLambda関数コードが想定しているパラメータデータの値を用意します。

base64dataには、 createImage()関数内の FileReaderメソッドの実行結果として画像ファイルのbase64データ(data URI scheme形式)を利用します。

thresholdでは、this.thresholdで、コードの31行目で画面で選択された値を最新値として利用します。

// コンポーネント内で利用する変数の初期値を設定する
created() {
  ・・・(省略)・・・
  this.threshold = this.config.searchConfig.threshold
},

// リクエストボディ情報を作成する
const querydata = {
  'image_base64str': this.uploadedImage,
  'threshold': this.threshold,
};

// 入力されたファイルをFileReaderでbase64(data URI scheme)にエンコードする
createImage(file) {
  const reader = new FileReader();
  
  reader.onload = e => {
    // ここにファイルの読み込み処理(readAsDataURL())実行後の処理を記述
    // 以下は画面表示・APIアクセスに利用します
    this.uploadedImage = e.target.result;
  };
  // data URI scheme形式でファイルを読み込む
  reader.readAsDataURL(file);
},

最後に、axiosを利用してリクエストを投げます。

POSTするURLは、this.apiEndpointで、コードの26行目で画面に入力された値を最新値として利用します。

リクエストボディのデータは JSON.stringify()メソッドでJSON形式に変換しましょう。

// API呼び出し
axios
  .post(this.apiEndpoint, JSON.stringify(querydata), config)
  .then(response => {
      const faceMatches = response.data.payloads.FaceMatches;
      if (faceMatches.length == 0) {
        this.noTarget = '人物を特定できませんでした'
      } else {
        this.createAuthenticationData(faceMatches[0]);
      }
      this.overlay = false;
  })
  .catch(error => {
    this.errorMessage = error;
    this.overlay = false;
  });