ソラカメを使ったデモアプリを作ってみた②後編 Rekognitionによる魚検知

<h1>やったこと

環境や全体アーキテクチャについては[前編]("https://qiita.com/ys-betis/items/ad56ae5a5560877fd38c")を参照。
ソラカメを使ったお魚検知デモアプリにおける、魚の検知部分について記載する。

![スクリーンショット 2023-12-30 14.17.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68086/32727262-ba79-00da-b72d-c60d6b5a9bc9.png)

<h1>画像からAWS Rekognitionのラベル検知にて魚を検知させる

S3に保存したソラカメの画像に魚が何匹いるかを数えるため、以下のようなフローを当初検討した。
ただRekognitionのカスタムラベルを使うためにモデルを作る時間もリソースもなかったので、とりあえずRekognitionのデフォルトのラベル検知機能に水槽画像を食わせてみたところ、割といい感じに魚（Fishラベル）が出てきていたので、そのまま使うことにした。
![スクリーンショット 2023-12-30 17.05.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68086/12d2424b-a697-8cd0-254b-11e7bc642489.png)

実際に今回処理させた関数のコードは以下。
やってることは、
* S3からファイルをDLし、Rekognitionにてラベル検知を実施
* 検知結果のレスポンスであるJSONファイルから、ラベル名が「Fish」が1つ以上検出された画像を抽出
* 抽出した画像にBoudingBoxを描画し再度S3へアップロード

```
def detect_and_draw_image(bucket_name, object_key):
    # Rekognition Clientの設定                                                                                                     
    print(object_key)
    rekognition = boto3.client('rekognition', region_name=AWS_REGION)

    # S3 Clientの設定                                                                                                              
    s3 = boto3.client("s3")

    # ラベル検出呼び出し                                                                                                           
    response = rekognition.detect_labels(
        Image={
            'S3Object': {
                'Bucket': bucket_name,
                'Name': object_key
            }
        }
    )
    #ラベル検出結果のJSONファイル書き出し             
    json_filename = object_key.rsplit('/', 1)[-1] + '_labels.json'
    detect_file_name = object_key.rsplit('/', 1)[-1]
    fish_count = 0
    for label in response['Labels']:
        if label['Name'] == 'Fish':
            instances = label.get('Instances', [])
            if instances:
                # オリジナル画像をダウンロード                                             
                original_image = s3.get_object(Bucket=bucket_name, Key=object_key)
                image_bytes = original_image['Body'].read()
                image = Image.open(io.BytesIO(image_bytes))
                # バウンディングボックスの描画                                              
                draw = ImageDraw.Draw(image)
                for instance in instances:                                         
                    bbox = instance.get('BoundingBox', {})
                    if bbox:
                        img_width, img_height = image.size
                        left = img_width * bbox['Left']
                        top = img_height * bbox['Top']
                        width = img_width * bbox['Width']
                        height = img_height * bbox['Height']  
                        draw.rectangle([left, top, left + width, top + height], outline='red')
                        fish_count += 1

                # 描画した画像をバイナリデータに変換                  
                img_byte_array = io.BytesIO()
                image.save(img_byte_array, format='PNG')
```
この部分に関するTips。
* 前編でも書いたけど、ソラカメモーション検知画像はデフォルトで検知した部分に緑枠が付いてしまうのでOFFにしておく必要があった
* ラベル検知結果のJSONファイルには検知したのもしてないのもラベル名として入っている。座標情報が入っているものだけが実際にそのラベル名として検知したものになる

特に二つ目について最初はFishがあれば検知だと思って全部拾ってしまっていたが、BoudingBoxが描画できていないことで気づいた。。

このコードをLambdaにデプロイし、後半の関数も完了。デモのメインである、「ソラカメから画像とってくる」→「画像から魚を検知する」ができた。

<h1>その他システム全体について

今回初めてLambdaにコードをデプロイしてみたのだが、開発環境（今回はCloud9）で書いたコードとLambda関数にしたコードをどう管理すればいいのかまだわかっていない。。二つ用意してしまっている感じ。
それ以外にも、
* Lambda関数からRDSへ接続させるために途中からVPC Lambdaに変更したけど、サブネット設定で少しハマってしまった
* WebUIからAPI Gatewayへデータ渡す時にクロスオリジンリソース共有 (CORS)の設定が漏れててうまくいかなかった

等々いくつか勉強になったこともあるが、その辺はまた別途。