はじめに
はじめて外部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
を環境変数に設定します。
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: 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に保存します。
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
というメソッドを作って、画像のデータを引数として受け取ります。
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 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
先程のヘルパーをインクルードします。
if params[:image]
label_list = upload_image(data)
paramsで画像データを取得して、それを引数にして先程作成した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
クラスに変更させます。
最後に
もっといい方法があればコメントで教えていただければ幸いです!
参考
[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)