Amazon Rekognitionで一人画像バトル（一人決闘）を開催した

# 皆！バトルしようぜ！
カードゲームでバトルした思い出が皆さんあると思いますが如何でしょうか？

○戯王カード、○ケモンカード、マジッ○ザギャザ○ング色々とありますね。
子供心を忘れてしまった人もいるのではないでしょうか。

争いとか嫌い....平和が一番好き☆
一人カラオケ、一人焼肉、一人キャンプ、一人筋トレ、一人迷走、一人サウナ、一人キャンプ
お一人で遊べる遊びも増えましたね。

丁度、一人でも遊べる遊びを思いつきました。

**その名も画像バトラー**

# 使う技術
今回はAmazonRekognitionとlaravelを組み合わせて画像が投稿できる機能？アプリ？を作りました（簡易的なため、まだまだ未完成）

laravel: 8.74.0
AmazonRekognition: DetectLabels

# AmazonRekognitionとは
公式ページから一部引用させてもらいます。
> Amazon Rekognition では、イメージ分析とビデオ分析をアプリケーションに簡単に追加することができます。Amazon Rekognition API にイメージやビデオを指定するだけで、このサービスによってモノ、人物、テキスト、シーン、アクティビティを識別できます。不適切なコンテンツも検出できます。Amazon Rekognition では、高精度な顔分析、顔の比較、および顔の検索機能も備えています。顔の検出、分析、比較は、ユーザー検証、カタログ作成、人数計数、公共安全など、多岐にわたって活用できます。
Amazon Rekognition は、Amazon のコンピュータビジョン科学者が日々何十億ものイメージを分析するために開発したものと同じ、実証済みで高度にスケーラブルな深層学習テクノロジに基づいています。使用する機械学習の専門知識は必要ありません

[AmazonRekognition説明ページ](https://docs.aws.amazon.com/ja_jp/rekognition/latest/dg/what-is.html)

つまり高度な知識がなくても機械学習が使えるということですね。
その中で今回、使ってみるのがActionsのDetectLabelsです。

> 入力として提供されるイメージ (JPEG または PNG) 内の実際のエンティティのインスタンスを検出します。これには、花、木、テーブルなどのオブジェクト、結婚式、卒業式、誕生日パーティーなどのイベント、風景、夕方、自然などの概念が含まれます。

[DetectLabels説明ページ]
(https://docs.aws.amazon.com/ja_jp/rekognition/latest/dg/API_DetectLabels.html)

他にもたくさんのActionsがありますが、今回は上記のlabelを使います。

# リクエスト、レスポンスパラメーター
```
リクエストパラメーター
{
   "Image": { 
      "Bytes": blob,
      "S3Object": { 
         "Bucket": "string",
         "Name": "string",
         "Version": "string"
      }
   },
   "MaxLabels": number,
   "MinConfidence": number
}
```

```
レスポンスパラメーター
"LabelModelVersion": "string",
   "Labels": [ 
      { 
         "Confidence": number,
         "Instances": [ 
            { 
               "BoundingBox": { 
                  "Height": number,
                  "Left": number,
                  "Top": number,
                  "Width": number
               },
               "Confidence": number
            }
         ],
         "Name": "string",
         "Parents": [ 
            { 
               "Name": "string"
            }
         ]
      }
   ],
   "OrientationCorrection": "string"
```

今回のバトル内容はレスポンスパラメーターの 
"Name": "string"
"Confidence": number

Nameが同じ名前でnumberがより高いものが勝利という条件にします。

# AWS準備
IAMサービスからユーザーをクリック
![スクリーンショット 2021-12-12 15.11.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542637/d893dc55-205f-d1cf-9c71-d0268f12b3a8.png)
ページ遷移後にユーザーを追加
![スクリーンショット 2021-12-12 15.12.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542637/0ad3e81f-3f66-682b-6b1c-e8e020b132a6.png)
ユーザー名とlaravelからアクセスするため、「プログラムによるアクセス」にチェック
![スクリーンショット 2021-12-12 15.16.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542637/c6f54fb0-1492-69e9-3644-d1c2bba36925.png)
次の画面に行きグループの作成
![スクリーンショット 2021-12-12 15.16.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542637/03fe3f35-d6a5-643f-ab84-d0b6b2a22eb8.png)
グループ名にrekognitionを入力して検索し「Access to All Amazon Rekognition」の項目にチェック
![スクリーンショット 2021-12-12 15.18.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542637/ed39b141-e34a-d327-eaf7-5c4886a6c59a.png)
成功したら「アクセスキー」と「シークレットアクセスキー」を取得
![スクリーンショット 2021-12-12 15.19.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542637/b688f8b2-887c-6d18-52d8-da30f708ead8.png)
アプリケーションのenvに入力

```
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=ap-northeast-1
```

アプリケーションのターミナルにてAWSにアクセスするためのパッケージをインストール

```
composer require aws/aws-sdk-php
```

これでAWSを使う準備は終わりました

# アプリケーション開発
laravel newをして
PostsTableを作成

```
class CreatePostsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->id();
            $table->unsignedBigInteger('user_id');
            $table->string('title');
            $table->longText('description')->nullable();
            $table->string('image_path')->nullable();
            $table->timestamps();

            $table->foreign('user_id')->references('id')->on('users');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('posts');
    }
```
PostModelを作成

```
class Post extends Model
{
    use HasFactory;

    protected $fillable = [
        'title',
        'description'
    ];

    public function user()
    {
        return $this->belongsTo(User::class);
    }
}
```

Routeを編集

```
Route::get('/', function () {
    return view('welcome');
});

Route::middleware(['auth:sanctum', 'verified'])->get('/dashboard', function () {
    return redirect()->route('posts.index');
})->name('dashboard');

Route::group(['middleware' => 'auth'], function(){

    Route::resource('posts',PostController::class);

});
```

PostControllerを作成し、storeメソッドを編集

```
    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        $this->validate($request, [
            'title' => 'required',
            'image'=> 'nullable|sometimes|file',
            'description' => 'nullable|sometimes'
        ]);

        $newPost = auth()->user()->posts()->create([
            'title' => $request->input('title'),
            'description' => $request->input('description')
        ]);

        if($request->hasFile('image')) {

            $client = new RekognitionClient([
                'region' => env('AWS_DEFAULT_REGION'),
                'version' => 'latest'
            ]);

            $image = fopen($request->file('image')->getPathName(), 'r');
            $bytes = fread($image, $request->file('image')->getSize());

            // rekognitionのメソッド
            $results = $client->DetectLabels([
                'Image' => ['Bytes' => $bytes],
                // 指定された値よりも低い信頼水準のラベルは返却しない
                'MinConfidence' => 0
            ]);

            $resultLabels = $results->get('Labels');

            $imagePath = $request->file('image')->store('public/posts');

            if($imagePath === null) {
              return redirect()->back()->withErrors([
                 'image_upload_files' => '画像アップロードを失敗しました'
              ]);
            }

            $newPost->image_path = $imagePath;
            $newPost->save();
        }

        return redirect()->back();
    }
```

bladeファイルを作成

```
<x-app-layout>
    <x-slot name="header">
        <h2 class="font-semibold text-xl text-gray-800 leading-tight">
            投稿
        </h2>
    </x-slot>

    <div class="py-12">
        <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
            <div>
                @if ($errors->any())
                    <div class="alert alert-danger">
                        <ul>
                            @foreach ($errors->all() as $error)
                                <li>{{ $error }}</li>
                            @endforeach
                        </ul>
                    </div>
                @endif
            </div>
            <div class="bg-white overflow-hidden shadow-xl sm:rounded-lg">
                <form action="{{ route('posts.store') }}" method="post" enctype="multipart/form-data">
                    @csrf
                <div>
                    <label for="title">投稿タイトル</label>
                    <input type="text" id="title" name="title">
                </div>
                <div>
                    <label for="image">画像</label>
                    <input type="file" id="image" name="image">
                </div>
                <div>
                    <label for="description">説明文</label>
                    <textarea name="description"  id="description" cols="30" rows="10"></textarea>
                </div>
                <div>
                    <button type="submit">投稿</button>
                </div>
                </form>
            </div>
        </div>
    </div>
</x-app-layout>
```

すごく簡単な投稿する画面ができました
![スクリーンショット 2021-12-18 16.57.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542637/d049de9e-6316-b733-4e03-74e9a67f78c1.png)

## 本題
### デュエル（一人決闘）の開始です。

非常にお待たせしました。
今回は未完成のためlabelのdd($resultLabels)で値の中身を確認して決闘します。

#ドォオォォロー！
俺の画像は！！！
![ダウンロード (7).jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542637/e210c6b8-a03e-2a62-7e1c-be3a295fd197.jpeg)
__ハッピーバースデーモンスターα！！！！！__

```
"Name" => "Birthday Party"
"Confidence" => 56.966209411621
```

56.966209411621か.....
#俺のターン!ドロー！
![publicdomainq-0016959glt.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542637/fa3aca0d-539f-c54f-5951-42d26ed36fd3.jpeg)
__パリピ！__

```
"Name" => "Birthday Party"
"Confidence" => 65.341262817383
```

__やっぱり、パリピには勝てない...__

#最後に
一つの画像のlabelで以下の画像のような出力になりました（一部抜粋）
![スクリーンショット 2021-12-18 17.24.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542637/f69373e4-4308-09f7-7e6c-617673721721.png)

AWSサービスはたくさんあるので、作ったり試してみて慣れていきたいですね。