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
を使用するので、それらのアクセス許可のポリシーをアタッチしてください。
詳しい方法についてはドキュメントを参照してください。
aws s3のバケットを作成
続いて、s3
にアクセスしてバケットを作成します。
やることとしては、任意のバケット名をつけることと、リージョンを設定するだけです。
詳しくはドキュメントを参照してください。
環境変数を設定
先程保管しておいたしておいた、Access key ID
とSecret access key
を環境変数に設定します。
export AWS_ACCESS_KEY_ID=<取得したAccess key ID> export AWS_SECRET_ACCESS_KEY=<取得したSecret access key>
ここまでの設定が終わったら、本題の画像検索機能を実装していきます。
Gemのインストール
まずは下記のgemをインストールします。
gem 'aws-sdk-s3' gem 'aws-sdk-rekognition' gem 'aws-sdk-translate'
画像のアップロード画面を作成
続いて画像入力のビューを作ります。
※active storage使ってます
<%= form_with url: search_images_path, scope: :search, method: :post, local: true do |f| %> <div class="form-group mb-3"> <%= f.label :search_image %> <%= f.file_field :image, class: 'form-control' %> </div> <div class="actions mb-3"> <%= f.submit '検索する', class: 'btn btn-primary' %> </div> <% end %>
画像をs3に保存する
続いて、取得した画像をs3に保存します。
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 = 'article-image-search' key = "article_image" s3_client.put_object({ body: body, bucket: bucket, key: key }) put_labels(key, s3_client) end
upload_image
というメソッドを作って、画像のデータを引数として受け取ります。
credentials = Aws::Credentials.new( ENV['AWS_ACCESS_KEY_ID'], ENV['AWS_SECRET_ACCESS_KEY'] ) region = 'us-east-1'
credentials
とregion
を定義します。
region
はs3
のバケットを作成したときに設定したものを指定します。
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
というメソッドを呼び出します。
画像のラベルを検出する
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)
rekogniton_client = Aws::Rekognition::Client.new(credentials: credentials) attrs = { image: { s3_object: { bucket: bucket, name: key }, }, max_labels: 10 }
max_labels
で検出するラベルの上限を指定できます。
response = rekogniton_client.detect_labels attrs label_list = [] response.labels.each do |label| label_list << label.name.downcase
detect_labels
を使って画像を解析したデータを取得して、
ラベル名だけを配列に入れます。
このとき、ラベル名の頭文字が大文字になっていることがあるので、
downcase
メソッドを使ってすべて小文字に変換します。
これをしないと、次で説明する日本語翻訳がうまくいきません。
s3_client.delete_objects({ bucket: bucket, delete: { objects: [ { key: key }, ], quiet: false } }) translate_label(label_list)
delete_objects
メソッドを使って、画像認識に使用した画像を削除します。
最後に次で作成するtranslate_label
という日本語翻訳するためのメソッドを呼び出します。
ラベル名を翻訳する
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
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
に代入して値を返します。
あいまい検索のメソッドを作成
続いて、先程翻訳したラベルの配列を使って、あいまい検索をします。
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!
メソッドを使って重複したレコードを削除します。
コントローラを作成
続いて、コントローラに下記のコードを追加します。
class SearchImagesController < ApplicationController include SearchImagesHelper def new; end def create data = params[:search][: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 'bugs/index' end end
先程のヘルパーをインクルードします。
data = params[:search][:image] label_list = upload_image(data)
paramsで画像データを取得してdata変数に代入し、それを引数にして先程作成したupload_image
メソッドを呼び出します。
帰ってきたラベルの配列データをlabel_list
に代入します。
bug_array = label_search(label_list)
先程作成したlabel_search
を使って、レコードの配列を取得します。
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
クラスに変更させます。
最後に
もっといい方法があればコメント等ください!