Splunkで画像解析データを分析（Amazon Rekognition連携）

# はじめに

Splunkでデータ集約、分析する中で、画像や動画データも対象にしたいという場合のお話です。
※例えばコミュニティサイトを運用している場合に変な画像をアップロードされていないかとか、ビジネス分析用途で、インスタグラムとかで自社の製品とかサービスに関連する画像でどんなものがアップされてるかとか

残念ながらSplunk自身では画像、動画データを取り込むことはできません。
じゃあ他の偉大なサービスの力を借りて実現しようということで、AmazonのRekognitionで解析して解析データを取り込んでみましたのでメモです。

# Amazon Rekognitionとは

[公式](https://aws.amazon.com/jp/rekognition/?blog-cards.sort-by=item.additionalFields.createdDate&blog-cards.sort-order=desc)からコピペ。

>Amazon Rekognition を使用すると、画像と動画の物体、人物、テキスト、シーン、活動を特定し、不適切なコンテンツを検出できます。
>Amazon Rekognition は、非常に正確な顔分析および顔検索機能も備えています。これを使用して、さまざまなユーザー検証、人数のカウント、および公共安全のユースケースで顔を検出、分析、比較できます。

面白そうな関数はこの辺りです。恐ろしい時代です。

* [DetectLables](https://docs.aws.amazon.com/rekognition/latest/dg/API_DetectLabels.html)：エンティティを検出してラベル付け
* [GetFaceDetection](https://docs.aws.amazon.com/rekognition/latest/dg/API_GetFaceDetection.html)：人物の年齢、性別、眼鏡をかけているか、感情などを推定
* [CompareFaces](https://docs.aws.amazon.com/rekognition/latest/dg/API_CompareFaces.html)：元となる顔画像と見比べ、その人物かを判別
* [GetSegmentDetection](https://docs.aws.amazon.com/rekognition/latest/dg/API_GetSegmentDetection.html)：表情から感情分析
* [DetectText](https://docs.aws.amazon.com/rekognition/latest/dg/API_DetectText.html)：画像に写っているテキストを抽出
* [GetContentModeration](https://docs.aws.amazon.com/rekognition/latest/dg/API_GetContentModeration.html)：コンテンツが健全かどうか判別



# 環境

* Splunk Enterprise (HEC使うのでAWS上にあるもの) or Splunk Cloud
* AWS Lambda
* Amazon S3
* Amazon Rekognition

こんなフローです。
S3へのアップロードをトリガーにLambdaを起動し、S3の情報をLambdaからRekognitionに渡して画像解析、結果をSplunkのHECに送信します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1218403/7461ed65-8f4b-3336-f44d-93d0bcd85ec4.png)

それじゃやっていきましょう。


# 事前準備


## IAMの設定

LambdaからS3、RekognitionにアクセスするためのIAMロールを作成します。
ユースケースはLambdaを選択します。
ポリシーで必要なのは以下です。

* AmazonS3ReadOnlyAccess
* AmazonRekognitionFullAccess
* AWSLambdaBasicExecutionRole　※CloudWatchへのログ出力

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1218403/c2493365-1de4-d68e-0b11-a514150d9681.png)


## S3 Bucketの準備

画像アップロードするS3 Bucketも作っておきましょう。
既存のものがあればそれでも結構です。

テスト用の画像をアップロードしておきます。
「情報量の多い画像」で有名なミシン紳士をお借りしますね。
![test_image.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1218403/e5ba5a73-f7b3-a22d-5c4c-a77b51b75d5c.jpeg)


# Rekognitionのデータを取り込んでみよう


## Lambda関数の作成

Lambdaの関数を作成します。
「一から作成」でランタイムはPython、実行ロールは先ほど作ったロールを選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1218403/2f92c146-e34e-c33a-87b9-89607038ef95.png)

画像データが大きい場合に時間を要する場合があります。
関数作成後、設定 > 一般設定からタイムアウト値を増やしておきます。
※デフォルト3秒から10秒に変更しておきました

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1218403/ef7ee426-3ded-c06e-b09f-5a2e9bf4d745.png)




## Rekognitionで解析

さて、Pythonのスクリプト作っていきましょう。
とは言ってもめちゃ簡単です。

画像に何が含まれているかを抽出してくれる`DetectLabels`で試します。


```python:lambda_function.py
import json
import boto3

def lambda_handler(event, context):
    
    bucket = "symmr.rekognition" # S3 Bucket名
    key = "test_image.jpg" # 画像ファイル名
    
    client = boto3.client("rekognition")
    response = client.detect_labels(Image = {"S3Object": {"Bucket": bucket, "Name": key}}, MaxLabels=20, MinConfidence=70)
    
    print(response)
    
    # TODO implement
    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }
```

boto3を使い、S3からデータ取得する場合はdetect_labelsにS3の情報と`MaxLabels`で取得ラベル数（デフォルト無制限）、`MinConfidence`で最小confidence（デフォルト55%）を指定するだけです。

※[APIリファレンスはこちら](https://docs.aws.amazon.com/ja_jp/rekognition/latest/dg/API_DetectLabels.html)
※S3ではなく画像データを直接解析する場合は`Image.Bytes`にbase64にエンコードしたデータをセットすればOKです


ではテストしてみましょう。

```text:テスト結果
{
  "statusCode": 200,
  "body": "\"Hello from Lambda!\""
}

Function Logs
START RequestId: cf212be4-c922-41b8-ab91-7c90c3a285ec Version: $LATEST
{'Labels': [{'Name': 'Person', 'Confidence': 99.77276611328125, 'Instances': [{'BoundingBox': {'Width': 0.07846866548061371, 'Height': 0.3057587146759033, 'Left': 0.13179878890514374, 'Top': 0.15125896036624908}, 'Confidence': 99.77276611328125}, {'BoundingBox': {'Width': 0.0827648788690567, 'Height': 0.20582953095436096, 'Left': 0.25745800137519836, 'Top': 0.1811434030532837}, 'Confidence': 99.7685546875}, {'BoundingBox': {'Width': 0.06163841113448143, 'Height': 0.2630268633365631, 'Left': 0.07908330857753754, 'Top': 0.16579791903495789}, 'Confidence': 99.69869995117188}, {'BoundingBox': {'Width': 0.042920779436826706, 'Height': 0.26353126764297485, 'Left': 0.18419542908668518, 'Top': 0.15169677138328552}, 'Confidence': 99.55378723144531}], 'Parents': []}, {'Name': 'Human', 'Confidence': 99.77276611328125, 'Instances': [], 'Parents': []}, {'Name': 'Suit', 'Confidence': 99.72770690917969, 'Instances': [], 'Parents': [{'Name': 'Overcoat'}, {'Name': 'Coat'}, {'Name': 'Clothing'}]}, {'Name': 'Clothing', 'Confidence': 99.72770690917969, 'Instances': [], 'Parents': []}, {'Name': 'Overcoat', 'Confidence': 99.72770690917969, 'Instances': [], 'Parents': [{'Name': 'Coat'}, {'Name': 'Clothing'}]}, {'Name': 'Coat', 'Confidence': 99.72770690917969, 'Instances': [], 'Parents': [{'Name': 'Clothing'}]}, {'Name': 'Apparel', 'Confidence': 99.72770690917969, 'Instances': [], 'Parents': []}, {'Name': 'Tuxedo', 'Confidence': 98.28694152832031, 'Instances': [], 'Parents': [{'Name': 'Suit'}, {'Name': 'Overcoat'}, {'Name': 'Coat'}, {'Name': 'Clothing'}]}, {'Name': 'Bush', 'Confidence': 88.96171569824219, 'Instances': [], 'Parents': [{'Name': 'Vegetation'}, {'Name': 'Plant'}]}, {'Name': 'Plant', 'Confidence': 88.96171569824219, 'Instances': [], 'Parents': []}, {'Name': 'Vegetation', 'Confidence': 88.96171569824219, 'Instances': [], 'Parents': [{'Name': 'Plant'}]}, {'Name': 'Blazer', 'Confidence': 71.90851593017578, 'Instances': [], 'Parents': [{'Name': 'Jacket'}, {'Name': 'Coat'}, {'Name': 'Clothing'}]}, {'Name': 'Jacket', 'Confidence': 71.90851593017578, 'Instances': [], 'Parents': [{'Name': 'Coat'}, {'Name': 'Clothing'}]}, {'Name': 'Housing', 'Confidence': 70.93521881103516, 'Instances': [], 'Parents': [{'Name': 'Building'}]}, {'Name': 'Building', 'Confidence': 70.93521881103516, 'Instances': [], 'Parents': []}], 'LabelModelVersion': '2.0', 'ResponseMetadata': {'RequestId': '786baec7-a946-4391-b728-4288d53a96e0', 'HTTPStatusCode': 200, 'HTTPHeaders': {'content-type': 'application/x-amz-json-1.1', 'date': 'Sat, 03 Apr 2021 14:43:01 GMT', 'x-amzn-requestid': '786baec7-a946-4391-b728-4288d53a96e0', 'content-length': '2166', 'connection': 'keep-alive'}, 'RetryAttempts': 0}}
END RequestId: cf212be4-c922-41b8-ab91-7c90c3a285ec
REPORT RequestId: cf212be4-c922-41b8-ab91-7c90c3a285ec	Duration: 1706.71 ms	Billed Duration: 1707 ms	Memory Size: 128 MB	Max Memory Used: 73 MB	Init Duration: 255.29 ms

Request ID
cf212be4-c922-41b8-ab91-7c90c3a285ec
```

Labelsに取得したラベルが入ってきます。ちょっと見にくいので抽出すると

```text:ラベルと自信度
Name, Confidence
Person, 99.77276611328125
Human, 99.77276611328125
Suit, 99.72770690917969
Clothing, 99.72770690917969
Overcoat, 99.72770690917969
Coat, 99.72770690917969
Apparel, 99.72770690917969
Tuxedo, 98.28694152832031
Bush, 88.96171569824219
Plant, 88.96171569824219
Vegetation, 88.96171569824219
Blazer, 71.90851593017578
Jacket, 71.90851593017578
Housing, 70.93521881103516
Building, 70.93521881103516
```

すげぇや。
ちょっと違うかな？と思ったラベルについてはConfidenceもしっかり低めに出ています。
他にもそれぞれのラベルの親子関係や、対する画像内の位置情報も含まれています。


## SplunkのHECに送信してみる

この結果をSplunkに送信しましょう。
準備など詳細は以下をご参照ください。
[AWS LambdaでRESTでデータ取得してSplunkに送信してみる (Python編)](https://qiita.com/symmr/items/074be71bdcc31d3e6079)

それで、できたスクリプトがこちらです。
※HECのパラメータは`SPLUNK_HEC_TOKEN`、`SPLUNK_HEC_URL`で環境変数化しています
※エラー処理は省いています

```python:lambda_function.py
from splunk_http_event_collector import http_event_collector
import json
import boto3
import os
import logging
import sys

def lambda_handler(event, context):
    
    targetBucket = "symmr.rekognition"
    targetKey = "test_image.jpg"
    
    client = boto3.client("rekognition")
    response = client.detect_labels(Image = {"S3Object": {"Bucket": targetBucket, "Name": targetKey}}, MaxLabels=20, MinConfidence=70)
    
    print(response)
    
    # init logging config, this would be job of your main code using this class.
    logging.basicConfig(format='%(asctime)s %(name)s %(levelname)s %(message)s', datefmt='%Y-%m-%d %H:%M:%S %z')
    
    # Create event collector object, default SSL and HTTP Event Collector Port
    http_event_collector_key = os.environ["SPLUNK_HEC_TOKEN"]
    http_event_collector_host = os.environ["SPLUNK_HEC_URL"]
    
    event_to_send = http_event_collector(http_event_collector_key, http_event_collector_host)

    # perform a HEC reachable check
    hec_reachable = event_to_send.check_connectivity()
    if not hec_reachable:
        sys.exit(1)
    
    # Set to pop null fields.  Always a good idea
    event_to_send.popNullFields = True
    # set logging to DEBUG for example
    event_to_send.log.setLevel(logging.DEBUG)
    
    # Start event payload and add the metadata information
    payload = {}
    payload.update({"index":"aws_lambda"})
    payload.update({"sourcetype":"rekognition"})
    payload.update({"source":context.function_name})
    payload.update({"host":"lambda"})
    
    payload.update({"event":response})
    event_to_send.sendEvent(payload)
    
    # TODO implement
    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }
```

テストして、Splunkで見てみましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1218403/12be1f9d-a98e-e76f-6142-950f1feeecb8.png)

やったね！

## S3へのファイルアップロードをトリガーに設定

Rekognition→Splunk送信がうまくいったので、次はS3にファイルアップロードされたらLambdaが自動実行されるようにしましょう。

[トリガーを追加] で...

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1218403/7befd832-81e4-e2bd-3e8b-bcc8b257fb67.png)

S3 Bucketを選択してトリガー作成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1218403/b41f3461-727d-a36c-0dda-b33e4e3fd672.png)

コードも若干修正します。
S3からのトリガー時には、対象のファイル情報は`event`に入ってきます。BucketとKey情報を渡すようにします。
※[S3通知イベントの詳細はこちら](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/with-s3.html)

```python:lambda_function.py
from splunk_http_event_collector import http_event_collector
import json
import boto3
import os
import logging
import sys

def lambda_handler(event, context):

    # アップロードされたファイル情報
    bucket = event['Records'][0]['s3']['bucket']['name']
    key = event['Records'][0]['s3']['object']['key']
    
    client = boto3.client("rekognition")
    response = client.detect_labels(Image = {"S3Object": {"Bucket": bucket, "Name": key}}, MaxLabels=20, MinConfidence=70)
    
    # 解析結果にファイル情報を結合
    response.update(event)
    
    print(response)
    
    # init logging config, this would be job of your main code using this class.
    logging.basicConfig(format='%(asctime)s %(name)s %(levelname)s %(message)s', datefmt='%Y-%m-%d %H:%M:%S %z')
    
    # Create event collector object, default SSL and HTTP Event Collector Port
    http_event_collector_key = os.environ["SPLUNK_HEC_TOKEN"]
    http_event_collector_host = os.environ["SPLUNK_HEC_URL"]
    
    event_to_send = http_event_collector(http_event_collector_key, http_event_collector_host)

    # perform a HEC reachable check
    hec_reachable = event_to_send.check_connectivity()
    if not hec_reachable:
        sys.exit(1)
    
    # Set to pop null fields.  Always a good idea
    event_to_send.popNullFields = True
    # set logging to DEBUG for example
    event_to_send.log.setLevel(logging.DEBUG)
    
    # Start event payload and add the metadata information
    payload = {}
    payload.update({"index":"aws_lambda"})
    payload.update({"sourcetype":"rekognition"})
    payload.update({"source":context.function_name})
    payload.update({"host":"lambda"})
    
    payload.update({"event":response})
    event_to_send.sendEvent(payload)
    
    # TODO implement
    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }
```

今度は写真じゃなくて絵をS3にアップしてみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1218403/64c90a0e-2aff-31e0-f0e6-0a1e6f10c7c9.png)

すると自動でLambdaが動作して...

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1218403/f1abe8fb-1201-e787-4195-637521964f78.png)

おｋ


## どんどんアップロードしてみる

解析してみたい画像をどんどんアップロードしましょう。
※課金に注意


## Splunkで分析 

とりあえず扱いやすいように整形。

```go:SPL
index=aws_lambda sourcetype=rekognition
| rename Labels{}.Name as label
| table _time, label, Records{}.s3.object.key
| mvexpand label
```

後はいかようにでも。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1218403/1d2fc4d8-ee9a-e571-58c6-773dc82c36c3.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1218403/502b341b-8d06-9441-3c08-a08054820392.png)


# まとめ

Amazon Rekognitionを使い解析した画像（動画も）の結果をSplunkでお手軽に分析できるようになりました。
色々なサービスと組み合わせると面白いことができますね。