AmazonRekognitionを使って画像検索機能を作る

### はじめに
はじめて外部APIを使ってみたのでその内容を書きます。

### 順序

1：画像を送信する。

2：送信された画像をawsの`s3`を使って、`bucket`を指定して保存する。

3：rubyから`Amazon Rekogniton`のAPIを叩いて、先程保存した画像を画像認識して、ラベルを取得する。

4：取得したラベルを`Amazon Translate`を使って日本語翻訳して、そのワードを元にフリーワード検索をしてデータを取得する。


### awsのIAMユーザーの作成

まず、awsの`IAM`にアクセスしてユーザーを作成します。

その際に`Access key ID`と`Secret access key`が作成されるので、保管して置いてください。

また、`Amazon s3`、`Amazon Rekogniton`、`Amazon Translate`を使用するので、それらのアクセス許可のポリシーをアタッチしてください。

詳しい方法についてはドキュメントを参照してください。


[Amazon S3 とは - Amazon Simple Storage] (Service[https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/userguide/Welcome.html:embed:cite)

[Amazon Rekognition とは - Amazon Rekognition]
(https://docs.aws.amazon.com/ja_jp/rekognition/latest/dg/what-is.html:embed:cite)

[Amazon Translate とは - Amazon Translate]
(https://docs.aws.amazon.com/ja_jp/translate/latest/dg/what-is.html:embed:cite)

### aws s3のバケットを作成

続いて、`s3`にアクセスしてバケットを作成します。

やることとしては、任意のバケット名をつけることと、リージョンを設定するだけです。

詳しくはドキュメントを参照してください。

[Amazon S3 バケットの作成、設定、操作 - Amazon Simple Storage Service]
(https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/userguide/creating-buckets-s3.html:embed:cite)



### 環境変数を設定

先程保管しておいたしておいた、`Access key ID`と`Secret access key`を環境変数に設定します。



```:.zprofileまたは.bash_profile
export AWS_ACCESS_KEY_ID=<取得したAccess key ID>
export AWS_SECRET_ACCESS_KEY=<取得したSecret access key>
```

ここまでの設定が終わったら、本題の画像検索機能を実装していきます。

### Gemのインストール

まずは下記のgemをインストールします。

```:Gemfile
gem 'aws-sdk-s3'
gem 'aws-sdk-rekognition'
gem 'aws-sdk-translate'

```

### 画像のアップロード画面を作成

続いて画像入力のビューを作ります。

※active storage使ってます

```ruby:画像のアップロード画面のビュー
              <%= form_with url: image_search_bugs_path, method: :post, local: true do |f| %>
                <div class="form-group mb-3">
                  <p style="color: red;">※ファイル形式は`png`か`jpeg`でお願いします。</p>
                  <%= f.file_field :image, class: 'form-control', id: 'input_image', accept: 'image/png, image/jpeg' %>
                </div>
                <div class="mb-3">
                  <%= image_tag '', id: 'image_preview', style: 'display: none;' %>
                </div>
                <div class="actions mb-3">
                  <%= f.submit '検索する', class: 'btn btn-primary' %>
                </div>
              <% end %>
```

### 画像をs3に保存する

続いて、取得した画像をs3に保存します。

```ruby:search_images_helper.rb
  def upload_image(data)
    credentials = Aws::Credentials.new(
      ENV['AWS_ACCESS_KEY_ID'],
      ENV['AWS_SECRET_ACCESS_KEY']
    )
    region = 'us-east-1'

    # アップロードされた画像をs3に保存
    s3_client = Aws::S3::Client.new(region: region, credentials: credentials)

    body = data
    bucket = '先程作成したバケット名'
    key = "任意の名前"

    s3_client.put_object({
      body: body,
      bucket: bucket,
      key: key
    })

    put_labels(key, s3_client)
  end
```


`upload_image`というメソッドを作って、画像のデータを引数として受け取ります。


```ruby
    credentials = Aws::Credentials.new(
      ENV['AWS_ACCESS_KEY_ID'],
      ENV['AWS_SECRET_ACCESS_KEY']
    )
    region = 'us-east-1'
```

`credentials`と`region`を定義します。

`region`は`s3`のバケットを作成したときに設定したものを指定します。

```ruby
    s3_client = Aws::S3::Client.new(region: region, credentials: credentials)

    body = data
    bucket = 'article-image-search'
    key = "article_image"

    s3_client.put_object({
      body: body,
      bucket: bucket,
      key: key
    })

    put_labels(key, s3_client, credentials, bucket)
```

`body`に引数で受け取った画像データを代入します。

`bucket`には先程作成したバケット名を定義します。

`key`には任意の値をしていします。

`put_object `メソッドを使用して、`s3`に画像を保存します。

最後にこの後作成する`put_labels `というメソッドを呼び出します。

### 画像のラベルを検出する

```ruby:search_images_helper.rb
  def put_labels(key, s3_client, credentials, bucket)
    rekogniton_client   = Aws::Rekognition::Client.new(credentials: credentials)
    attrs = {
      image: {
        s3_object: {
          bucket: bucket,
          name: key
        },
      },
      max_labels: 10
    }

    response = rekogniton_client.detect_labels attrs
    label_list = []
    response.labels.each do |label|
      label_list << label.name.downcase
    end

    s3_client.delete_objects({
      bucket: bucket,
      delete: {
        objects: [
          {
            key: key
          },
        ],
        quiet: false
      }
    })

    translate_label(label_list)
```

```ruby
    rekogniton_client   = Aws::Rekognition::Client.new(credentials: credentials)
    attrs = {
      image: {
        s3_object: {
          bucket: bucket,
          name: key
        },
      },
      max_labels: 10
    }
```

`max_labels`で検出するラベルの上限を指定できます。


```ruby
    response = rekogniton_client.detect_labels attrs
    label_list = []
    response.labels.each do |label|
      label_list << label.name.downcase
```

`detect_labels`を使って画像を解析したデータを取得して、

ラベル名だけを配列に入れます。

このとき、ラベル名の頭文字が大文字になっていることがあるので、

`downcase`メソッドを使ってすべて小文字に変換します。

これをしないと、次で説明する日本語翻訳がうまくいきません。

```ruby
    s3_client.delete_objects({
      bucket: bucket,
      delete: {
        objects: [
          {
            key: key
          },
        ],
        quiet: false
      }
    })

    translate_label(label_list)
```

`delete_objects`メソッドを使って、画像認識に使用した画像を削除します。

最後に次で作成する`translate_label`という日本語翻訳するためのメソッドを呼び出します。

### ラベル名を翻訳する

```ruby:search_images_helper.rb
  def translate_label(label_list)
    region = 'us-east-1'

    # ラベルを翻訳
    translate_client = Aws::Translate::Client.new(
      region: region,
      credentials: credentials,
    )

    label_list_ja = []

    label_list.each do |label|
      res = translate_client.translate_text({
              text: label,
              source_language_code: 'auto',
              target_language_code: 'ja'
            })
      label_list_ja << res.translated_text
    end

    # ラベルのリストを返す
    label_list_ja
  end
```

```ruby
    label_list.each do |label|
      res = translate_client.translate_text({
              text: label,
              source_language_code: 'auto',
              target_language_code: 'ja'
            })
      label_list_ja << res.translated_text
    end
```

`translate_text`メソッドを使用して先程のラベル名を翻訳します。

`text`には翻訳したいテキストを指定します。

`source_language_code`には翻訳前の言語を指定します。

`auto`を指定するとapi側がよしなに指定してくれます。

`target_language_code`には、翻訳後の言語を指定します。

翻訳したラベルのリストを`label_list_ja`に代入して値を返します。

### あいまい検索のメソッドを作成

続いて、先程翻訳したラベルの配列を使って、あいまい検索をします。

```ruby
  def label_search(label_list)
    relation = Bug.distinct

    result = []

    label_list.each do |label|
      relation.where('name LIKE ?', "%#{label}%")
              .or(relation.where('feature LIKE ?', "%#{label}%"))
              .or(relation.where('approach LIKE ?', "%#{label}%"))
              .or(relation.where('prevention LIKE ?', "%#{label}%"))
              .or(relation.where('harm LIKE ?', "%#{label}%"))
              .each { |bug| result << bug }
    end
    result.uniq!
  end
```

`or`メソッドを使ってor検索をしています。

取得したレコードを配列にいれ、`uniq!`メソッドを使って重複したレコードを削除します。

### コントローラを作成

続いて、コントローラに下記のコードを追加します。


```ruby:コントローラ
class BugsController < ApplicationController
  include SearchImagesHelper

  def image_search
    if params[:image]
      label_list = upload_image(data)
      bug_array = label_search(label_list)
      if bug_array.present?
        @bugs = Bug.where(name: bug_array.map(&:name)).page(params[:page])
      else
        @bugs = Bug.none.page(params[:page])
      end
      render :index
    end
    redirect_to bugs_path
  end
end
```

先程のヘルパーをインクルードします。

```ruby
    if params[:image]
      label_list = upload_image(data)
```

paramsで画像データを取得して、それを引数にして先程作成した`upload_image`メソッドを呼び出します。

帰ってきたラベルの配列データを`label_list`に代入します。

```ruby
    bug_array = label_search(label_list)
```
先程作成した`label_search`を使って、レコードの配列を取得します。

```ruby
    if bug_array.present?
      @bugs = Bug.where(name: bug_array.map(&:name)).page(params[:page])
    else
      @bugs = Bug.none.page(params[:page])
    end
    render 'bugs/index'
```

`kaminari`というページネーションのgemで使用できるようになる`page`メソッドを使う際に、

`Array`クラスでは使用できないため、`where`メソッドと`none`メソッドを使って無理やり`ActiveRecord::Relation`クラスに変更させます。

### 最後に
もっといい方法があればコメントで教えていただければ幸いです！


### 参考

[Railsで配列をActive Record Relationに変換したい - Qiita]
(https://qiita.com/fgem28/items/25e25d400f2ce21f4235:embed:cite)


[Amazon S3 とは - Amazon Simple Storage] (Service[https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/userguide/Welcome.html:embed:cite)

[Amazon Rekognition とは - Amazon Rekognition]
(https://docs.aws.amazon.com/ja_jp/rekognition/latest/dg/what-is.html:embed:cite)

[Amazon Translate とは - Amazon Translate]
(https://docs.aws.amazon.com/ja_jp/translate/latest/dg/what-is.html:embed:cite)