AI動画解析サービスを試してみる

Amazonの[Amazon Rekognition Video](http://qiita.comhttps://aws.amazon.com/jp/rekognition)とGoogleの[Video AI(Video Intelligence API)](https://cloud.google.com/video-intelligence?hl=ja)の解析を使用してみて、それぞれどのような結果が得られるのか確認したいと思います。

今回は1回目ということでAmazon Rekognitionを使うまでの設定とPythonスクリプトを使用した解析を実行して結果をJSONで取得してみます。

## Amazon Rekognitionとは？

詳しくは公式Webサイト見ていただくのが一番ですが、簡単に言えばAWSのS3に保存してある画像や動画に対して顔検出、テキスト検出、ラベル検出などの解析が行えるサービスです。

検出について細かいカスタマイズはできませんが、手元に解析するハード・ソフト含めた環境や知識がない場合でも手軽にAI動画解析を試すことができます。

https://aws.amazon.com/jp/rekognition/

## アカウント設定の手順について

基本的には下記のURLの手順に沿って進めれば準備が完了しますが、Pythonのスクリプトで解析の実行と結果の取得をしたいのでPython用のSDKが動くところまでの設定をします。
SDKを使用する場合は下記ページのステップ3～8は必要ありません。
また、以下の説明は下記ページのステップの数字に合わせています。

アカウント設定の参考ページ:  
https://docs.aws.amazon.com/ja_jp/rekognition/latest/dg/api-video-roles.html

### 1. アカウント作成

* [ユーザー][link-user]ページ から「ユーザーの作成」クリック
 ![6adae320-e068-4da8-b0da-1677f102b981.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3791092/a8ac9f63-d996-ad73-307b-84242e05b3dc.png)
* 次のページで 「ユーザー名」を入力し「次へ」  
  今回は「ai-test」というユーザー名で進めます
 ![ffe8b780-0575-46ac-855f-904285dcc7aa.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3791092/7ab9e786-23d3-9513-cbde-8e4c4447a999.png)
* 許可のオプションについて  
  許可についてはステップ3で設定します。
  ここではそのまま進めて「次へ」をクリックします。
  ![d0300f27-9a41-44cf-b95f-77c23e4132e9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3791092/4816e1d1-0dd8-b40d-bb1e-944f78764d89.png)

* 確認画面  
  「ユーザーを作成」をクリックで作成完了です。
  ![cf314305-cae2-4c97-b8b5-a33c9cf7936c.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3791092/c501999c-817b-b376-bdd0-e8ab01f96562.png)

### 2. SDKから使用するアクセスキーの設定

* ユーザーの設定を表示  
  [ユーザー][link-user]ページでユーザー名の「ai-test」をクリックしユーザーのごと設定ページを開きます
 ![6386cf2f-4e1d-4f43-8f31-4ccbb421b286.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3791092/a89bef43-3473-b7af-efb6-77adfdc2859b.png)

* アクセスキーの作成の開始  
  「アクセスキー 1」(末尾の数字は作成したキーの数により変動) の下にある「アクセスキーを作成」をクリックして 「アクセスキーの設定」を表示します
 ![d7ad0613-b342-42fc-a812-41214f952af4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3791092/8063f35c-4e12-98d8-e993-f95651d64fad.png)

* アクセスキーの作成  
  「ユースケース」で「ローカルコード」を選択し、画面下部にチェックボックスにチェックを入れ「次へ」をクリック
![fe792866-d0e7-4fd2-9812-37f8da060ce9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3791092/dce69f7c-0d13-16da-2dee-de0f2990afca.png)

  必要な場合に説明を入れ「アクセスキーを作成」で作成完了します
![31825476-31ac-4334-9fee-a3ed3485882a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3791092/9e4c8151-e003-df11-0b1d-aa88afa9324d.png)

* シークレットキーの保存  
  この画面でシークレットキーを保存する最後のタイミングなので「.csvファイルをダウンロード」でcsvファイルを忘れずにダウンロードしてください
 ![c25f7e61-ee26-4751-8d25-76d2f4a7cd28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3791092/98a1fe3a-b699-d972-add0-d4744007acda.png)
 もし、シークレットキーを保存し忘れた場合は今回作成したアクセスキーを削除し、再度作り直してcsvファイルのダウンロードを忘れずに行ってください。
* シークレットキーの設定  
  各プラットフォームのユーザーのホームディレクトリ直下の「.aws」というディレクトリの「credentials」というファイル名に下記の所定のフォーマットでファイルを作成してください
  * Linux の場合  
    `~/.aws/credentials` 
  * Windows の場合  
    `%HOMEPATH%\.aws\credentials`
  * フォーマット  
    ```
    [default]
    aws_access_key_id = アクセスキー
    aws_secret_access_key = シークレットキー
    ```
    `[default]` はprofile名でSDKやCLIなどでprofileを指定する際に使用する名前です。 `default` は名前の通りデフォルトで使用されるprofile名となります

### 3. ユーザーに許可ポリシーの追加

* [ユーザー][link-user]ページでユーザー名の「ai-test」をクリックしユーザーのごと設定ページを開きます。
 ![27e6c521-c4ee-4967-911e-adf738975aab.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3791092/5ccbc762-1474-da95-349b-6780d0a5ce1d.png)

* 「許可の追加」をクリックし、更に「許可の追加」をクリック
 ![6df632eb-d46f-43b5-a94d-d3e53af7d3ab.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3791092/7888424f-2f43-6439-8fdc-f0ea00e171e0.png)

* 「許可のオプション」から「ポリシーを直接アタッチする」を選択。
  検索欄が出てくるので下記を許可ポリシー検索し、それぞれチェックをして最後に画面下部の「次へ」をクリック
   * AmazonRekognitionFullAccess
   * AmazonSNSFullAccess
   * AmazonSQSFullAccess
   * AmazonS3FullAccess
![8fcb1350-67cc-4ec4-a9b8-ab51dcdca684.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3791092/2b9357b7-4962-f9e3-8ffb-a0f0f68a1f41.png)

* 次に確認画面が出るので「許可を追加」をクリックして完了

  ![a8c103d1-9b74-405d-bab5-bb2e0c68bd54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3791092/b8569adf-d8df-9afc-7036-1df2f6f71d9b.png)

### 4. Amazon SNS の設定

SDKを使用する場合は必要ないのでスキップ

### 5. Amazon SQS の設定

SDKを使用する場合は必要ないのでスキップ

### 6. SNSトピックのサブスクライブ

SDKを使用する場合は必要ないのでスキップ

### 7. SNSトピックからSQSへメッセージを送る許可設定

SDKを使用する場合は必要ないのでスキップ

### 8. Amazon Rekognitionにアクセスするロールを作成

* [ロール][link-role]ページから「ロールを作成」をクリック  
![6a072325-5bef-4471-a55a-292f36cc4f33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3791092/f6d60e36-0991-5fe1-1a16-a6c4117c6b8d.png)

* 「信頼されたエンティティタイプ」で「AWS のサービス」を選択  
![3765fce0-e81a-482d-8f2f-afe11be8afcd.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3791092/f845d7fe-ac03-3d00-27c1-5c41e740626d.png)

* 「ユースケース」から「Rekognition」を検索し選択し「次へ」
![85da3ff4-e72d-4447-9eab-1721b38026bd.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3791092/66b15754-454c-bc4f-f10d-ee64239643d3.png)

* 「許可を追加」のページで、そのまま「次へ」をクリック
![a9473e94-0c98-4ce1-850f-95ffed31e599.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3791092/ba3c4f5e-3258-31b0-3096-f32e1c5af960.png)

* 「ロールの詳細」で「ロール名」を指定します  
今回は「ai-demo-role」としました。  
このロールのARNをステップ9,10と、後ほどのSDKを使用したスクリプトで使用します
![6977798c-15dc-4a47-8308-f296c2412f96.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3791092/aca3ce53-e02c-0ebf-1575-54a71f0f6dd3.png)

* 画面下部の「ロールを作成」をクリックして完了
![c3187137-bf76-427e-9545-05291a492f04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3791092/cd14c137-fecb-a4c1-04fe-73f9d7f96a00.png)

### 9. 上記ロールの信頼関係を編集

* [ロール][link-role]ページからステップ8で作成した「ai-test-role」をクリック  
![8aefae21-73ba-4c0b-b163-ccc986bf7d76.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3791092/0e7cc594-2f9a-31b6-aede-687aed04a945.png)

* 「信頼関係」タブで下記の様にPincipalとConditionに値を追加  
![115d53eb-fe71-4195-8924-74dae3edeebf.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3791092/3cd55aea-7e7e-0c6c-0358-12bd657e12e9.png)

`<user-arn>` は作成されたユーザーのARNです。ARNは [ユーザー][link-user]ページからユーザー名の「ai-test」をクリックしユーザーの詳細ページで確認できます。  
`<user-number>` は、ユーザーのARNにある数値を使用します。  
```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": "rekognition.amazonaws.com",
                "AWS": "<user-arn>"
            },
            "Action": "sts:AssumeRole",
            "Condition": {
                "StringEquals": {
                    "aws:SourceAccount": "<user-number>"
                }
            }
        }
    ]
}
```
### 10. ユーザーにインラインポリシーの追加

* [ユーザー][link-user]ページでユーザー名の「ai-test」をクリックしユーザーのごと設定ページを開きます
![01ff615c-ced4-48a5-aa08-82fa24965e6c.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3791092/65fb795a-7cdf-ea85-8f58-297754348469.png)

* 「許可」タブの「許可を追加」クリックして、「インラインポリシーを作成」をクリック
![85ebb420-bc8a-4a28-b0ba-e4a202373ffe.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3791092/9acd8c37-aa35-e0e6-73c1-2373c7ff1f48.png)

* 「ポリシーエディタ」で「JSON」を選択し、下記のように変更します。
![ad21ca5c-f179-4f62-9fe1-a9b946502802.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3791092/07daf71a-954d-16a7-ca3b-ff3292accd1a.png)

`<user-arn>`をステップ8で追加したロールのARNを使用します。
ARNの確認方法はステップ9を参照してください
```json
{
"Version": "2012-10-17",
"Statement": [
        {
            "Sid": "MySid",
            "Effect": "Allow",
            "Action": "iam:PassRole",
            "Resource": "<user-arn>"
        }
    ]
}
```
:::note info
アカウント設定の参考ページのJSONの例にはSTEP 7のARNと書いてありますがSTEP 8の間違いだと思われます
:::

### 11. 暗号化と復号

今回は暗号化/復号の設定は行いません。

### 12. SDKの準備

* boto3 のインストール
  Python3の実行環境に pip で boto3 をインストールします。
  その他、足りないライブラリがあれば pip install をしてください

  ```shell
  $ pip install boto3
   ```

参考: https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html

## ラベル解析の実行

S3にアップロードした動画ファイルに対して、ラベル解析を行い、その結果をJSONファイルとして保存するPythonスクリプトについて解説します。

### S3に動画ファイルのアップロード

事前にAWSのS3に解析を行う動画をアップロードしておきます。

参考: https://aws.amazon.com/jp/s3/getting-started/?nc=sn&loc=6&dn=1

### Python スクリプト

ここでは複数ある解析の種類からラベルの検出をして、その結果をJSONとしてファイルに保存します。

Amazon Rekognitionで動画の解析を実行するとJobIdが与えられます。今回は実行結果の出力として表示するだけですが、このJobIdを用いて解析を実行すると、解析実行結果が保存されている間は解析の再実行を行わず、解析結果だけを再取得することもできます。

Amazon Rekognitionの解析結果は、解析結果の項目(今回はラベル)が1000件ごとに分割されます。
その場合、結果のJSONに `NextToken` という残りの結果を取得するための値が返ってきます。

今回のスクリプトでは下記の参考ページのスクリプトを元に `NextToken` がある限り繰り返し実行し、`NextToken` がある場合は1000件ごとに分割したJSONファイルをその都度保存し、最後に残りのデータと、全て結果をデータ構造が壊れないように整形しながらまとめたJSONファイルを保存します。

参考: https://docs.aws.amazon.com/ja_jp/rekognition/latest/dg/video-analyzing-with-sqs.html

#### 設定項目

スクリプトファイルの下部にあるmain関数の中に、ユーザーと解析対象の動画ファイルに関する設定項目があります。

* `<profile-name>`  
   デフォルトのままの場合は `default` と指定してください
* `<user-role-arn>`  
  [ロール][link-role]ページからステップ8で作成した「ai-test-role」をクリックして表示されるroleのARNを指定してください
* `<bucket-name>`  
   動画ファイルを保存しているS3のバケット名を指定してください
* `<file-name>`
   上記バケットに保存している動画ファイル名を指定してください

#### 出力結果 (例)

```
$ ./run-detection.py
Start JobId: 99dfef36...snip...757d530485
.........
Matching JobId found: 99dfef36...snip...757d530485
-> Status: SUCCEEDED
ai-test.mp4-label-1.json
ai-test.mp4-label-2.json
ai-test.mp4-label-3.json
ai-test.mp4-label-4.json
ai-test.mp4-label-5.json
ai-test.mp4-label-all.json
```

#### スクリプト全体

以下、スクリプト全体です。
各APIの細かい仕様については以下のBoto3のリファレンスを参照してください。

Boto3 APIリファレンス:  
https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/rekognition.html

```python
import boto3
import json
import sys
import time

class VideoDetect:
  def __init__(self, role, bucket, video, client, rek, sqs, sns):
    self.jobtype = 'label'
    self.roleArn = role
    self.bucket = bucket
    self.video = video
    self.client = client
    self.rek = rek
    self.sqs = sqs
    self.sns = sns

  def GetSQSMessageSuccess(self):
    jobFound = False
    succeeded = False
    dotLine = 0

    while jobFound == False:
      sqsResponse = self.sqs.receive_message(
              QueueUrl=self.sqsQueueUrl,
              MessageAttributeNames=['ALL'],
              MaxNumberOfMessages=10,
              )

      if sqsResponse:
        if 'Messages' not in sqsResponse:
          if dotLine < 40:
            print('.', end='')
            dotLine = dotLine + 1
          else:
            print()
            dotLine = 0
          sys.stdout.flush()
          time.sleep(3)
          continue

        print()
        for message in sqsResponse['Messages']:
          notification = json.loads(message['Body'])
          rekMessage = json.loads(notification['Message'])

          if rekMessage['JobId'] == self.startJobId:
            print('Matching JobId found: ' + rekMessage['JobId'])
            jobFound = True

            if (rekMessage['Status'] == 'SUCCEEDED'):
              print('-> Status: ' + rekMessage['Status'])
              succeeded = True

            self.sqs.delete_message(QueueUrl=self.sqsQueueUrl,
                        ReceiptHandle=message['ReceiptHandle'])
          else:
            print("JobId didn't match: " +
                str(rekMessage['JobId']) + ' : ' + self.startJobId)

          # Delete the unknown message. Consider sending to dead letter queue
          self.sqs.delete_message(QueueUrl=self.sqsQueueUrl,
                      ReceiptHandle=message['ReceiptHandle'])
    return succeeded

  def CreateTopicandQueue(self):
    millis = str(int(round(time.time() * 1000)))

    # Create SNS topic
    snsTopicName = "AmazonRekognitionExample" + millis
    topicResponse = self.sns.create_topic(Name=snsTopicName)
    self.snsTopicArn = topicResponse['TopicArn']

    # create SQS queue
    sqsQueueName = "AmazonRekognitionQueue" + millis
    self.sqs.create_queue(QueueName=sqsQueueName)
    self.sqsQueueUrl = self.sqs.get_queue_url(QueueName=sqsQueueName)['QueueUrl']
    attribs = self.sqs.get_queue_attributes(
          QueueUrl=self.sqsQueueUrl,
          AttributeNames=['QueueArn']
        )['Attributes']
    sqsQueueArn = attribs['QueueArn']

    # Subscribe SQS queue to SNS topic
    self.sns.subscribe(
      TopicArn=self.snsTopicArn,
      Protocol='sqs',
      Endpoint=sqsQueueArn)

    # Authorize SNS to write SQS queue
    policy = """
{{
"Version":"2012-10-17",
"Statement":[{{
"Sid":"MyPolicy",
"Effect":"Allow",
"Principal" : {{"AWS" : "*"}},
"Action":"SQS:SendMessage",
"Resource": "{}",
"Condition":{{
"ArnEquals":{{
"aws:SourceArn": "{}"
}}}}}}]
}}""".format(sqsQueueArn, self.snsTopicArn)

    response = self.sqs.set_queue_attributes(
      QueueUrl=self.sqsQueueUrl,
      Attributes={
        'Policy': policy
      })

  def DeleteTopicandQueue(self):
    self.sqs.delete_queue(QueueUrl=self.sqsQueueUrl)
    self.sns.delete_topic(TopicArn=self.snsTopicArn)

  def WriteJsonFile(self, name, data):
    print(name)
    with open(name, mode="wt", encoding="utf-8") as f:
      json.dump(data, f, ensure_ascii=False, indent=2)

  def StartDetection(self):
    self.StartLabelDetection()

  def GetDetectionResults(self):
    try:
      self.GetLabelDetectionResults()
    except Exception as e:
      print(e)
    return

  # LabelDetection
  def StartLabelDetection(self):
    response = self.rek.start_label_detection(
            Video={'S3Object': {'Bucket': self.bucket, 'Name': self.video}},
            NotificationChannel={'RoleArn': self.roleArn, 'SNSTopicArn': self.snsTopicArn},
            )
    self.startJobId = response['JobId']
    print('Start JobId: ' + self.startJobId)

  def GetLabelDetectionResults(self):
    maxResults = 1000
    paginationToken = ''
    finished = False
    loopCount = 1
    responseAll = None

    while finished == False:
      response = self.rek.get_label_detection(
              JobId=self.startJobId,
              MaxResults=maxResults,
              NextToken=paginationToken,
              SortBy='TIMESTAMP',
              AggregateBy="TIMESTAMPS"
              )

      json_name = '{}-{}-{}.json'.format(self.video, self.jobtype, loopCount)
      self.WriteJsonFile(json_name, response)

      if 'NextToken' in response:
        paginationToken = response['NextToken']
        loopCount += 1
        if responseAll == None:
          responseAll = response
        else:
          responseAll['Labels'] += response['Labels']
      else:
        if responseAll != None:
          responseAll['Labels'] += response['Labels']
        finished = True

    if responseAll != None:
      json_name = '{}-{}-all.json'.format(self.video, self.jobtype)
      responseAll.pop('NextToken')
      self.WriteJsonFile(json_name, responseAll)

def main():
  profile = '<profile-name>'
  role_arn = '<user-role-arn>'
  bucket  = '<bucket-name>'
  video   = '<file-name>'

  session = boto3.Session(profile_name=profile)
  client = session.client('rekognition')
  rek = boto3.client('rekognition')
  sqs = boto3.client('sqs')
  sns = boto3.client('sns')

  analyzer = VideoDetect(role_arn, bucket, video, client, rek, sqs, sns)
  analyzer.CreateTopicandQueue()
  analyzer.StartDetection()

  if analyzer.GetSQSMessageSuccess() == True:
    analyzer.GetDetectionResults()

  analyzer.DeleteTopicandQueue()

if __name__ == "__main__":
  main()
```

[link-user]: https://us-east-1.console.aws.amazon.com/iam/home#/users
[link-role]: https://us-east-1.console.aws.amazon.com/iam/home#/roles