はじめに
はじめて外部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)