一体サンタクロースはどこからがサンタクロースなのだろうか...???
赤い帽子をかぶって白い髭を生やしていればサンタなのか、、、それともトナカイにソリを引かせていればサンタなのか。。。
クリスマス前日に子供のためにおもちゃを選ぶお父さんはもう立派なサンタさんなんじゃないのか。
そんな議論は今日もつきません。
ならば世界最高峰全知全能のGoogleに一対どこまでがサンタでどこまでがサンタでないのか判定してもらおうではありませんか。
ということで今回はGoogleが提供する画像解析APIのGoogle Cloud Vision APIを使って、
「投稿した画像がサンタなのかそうでないのか世界のGoogleが判定がする」アプリを作りました。
TECH::CAMPアドベントカレンダーの9日目を担当させていただきます!
まずはアプリ紹介から
アプリはこちらで遊べます
【2017/1/25追記】APIの無料トライアル期間が切れてもう画像判定できなくなりました(泣)
小学生の図工みたいなUIの投稿画面で、ファイルを選択ボタンから、
サンタっぽい画像を選んで "This is Santa" ボタンを押すと、、、
**This is Santa!!!**こうなります!
Google Cloud Vision APIが投稿した画像をサンタかどうか解析して、判別してくれます。
ちなみにこの結果画面の画像は "This is Santa" って適当にぐぐってヒットした画像を適当に使ってます。
すごいですね。こんなことができてしまう世の中がくるなんて、、、。
また、サンタっぽくない画像を選んでしまうと、
**This is not Santa!!!**こうなります!
これはGoogle Cloud Vision APIがサンタの画像として認識しなかったようですね。(この画像は逆に "This is not Santa" でぐぐってヒットした画像を使っております。)
あわよくばサンタと混乱しないかなとばかりに、わりとサンタっぽいどっかの学校の校長先生をもってきたんですが、さすがGoogle、騙されませんでした。ファンタスティック。
ちなみにアウトした場合は何と勘違いしているのか気になるので、下に判定結果の一部を表示しています。
ちなみに、サンタコスとかはどうなんでしょうね。ということで橋本環奈のサンタコスをいれてみると、、
。。アウト!アウトでした。。服が白いからですかね?
ではこれはどうでしょう??ちっさい子のサンタコス、今度は服が赤くてよりサンタっぽいですが、サンタさんのチャームポイントの白い髭がないですね。いけますかね。。
おおお!よっしゃ!いけた!判定基準は不明ですが、素晴らしいGoogle Cloud Vision API。。。
ということで、お遊びはここらへんにして、これから本題に移っていきます。
以下、目次となっております。
目次
・ Google Cloud Vision APIとは?
・ 環境
・ Railsの基本実装
・ Google Cloud Vision API周りの実装
・ まとめ
・ 反省
Google Cloud Vision APIとは?
Google Cloud Vision APIとは、Google Cloud Platform(GCP)が提供する機械学習サービスの一種です。パワフルな機械学習モデルにより画像の内容を認識します。
例えば猫の画像を解析すると"cat"という文字列を返してくれたり、人の表情も感知できて、怒っている人の顔を怒っていると感知することもできます。
詳しくは公式ドキュメントで。
精度も上で試してみたとおり素晴らしいです。ちなみに橋本環奈だと「グラビアアイドル」や「日本人アイドル」とか出てきます。おそろしい。
環境
Ruby 2.1.8
Rails 4.2.6
Railsの基本実装
ここらへんはわりとRailsの知識があること前提に。。。
今回のアプリの流れは、
投稿画面(new action)→保存処理(create action)
→createアクションの中でモデルのインスタンスメソッドを呼び出す
→インスタンスメソッド内でGoogle Cloud Visionクラスのインスタンスを生成
→インスタンスメソッド内でGoogle Cloud Vision APIにリクエスト
→取得したデータ結果をビューに表示、でいきます。
Routing
Rails.application.routes.draw do
root 'images#new'
resources :images, only: [:new, :create]
namespace :api do
namespace :v1 do
post 'check_santa' => 'images#check_santa'
end
end
end
Controller
class ImagesController < ApplicationController
protect_from_forgery except: :create
def new
@image = Image.new
end
def create
@image = Image.create(image_params)
result = @image.check_santa
# check_santaメソッドでサンタかどうか判別する。resultにはサンタかどうかと判別結果をハッシュ形式で取得する。
@santa_status = result[:santa_status]
# アップした画像がサンタ判定されたらtrue、されないならfalseをいれます。
@descriptions = result[:descriptions][0..4]
# サンタ判定されなかったものはなんて判定されたか気になるので、判定結果も一応変数に保持しておきます。
end
private
def image_params
params.require(:image).permit(:image)
end
end
Model
今回はimageカラムをもつimagesテーブルとimageモデルを作成しました。imageカラムにはcarrierwaveのuploderクラスを紐付けています。
class Image < ActiveRecord::Base
mount_uploader :image, ImageUploader
# ここは好みでcarrierwaveとか使ってください。画像のパスを生成しやすくするために使っています。
def check_santa
descriptions = GoogleCloudVision.new(self.image.path).request
# APIの処理はそれようにクラスを作ったのでそこのインスタンスを作成します。画像を分析した結果の文字列を返り値として返すのがゴール。
parse_result(descriptions)
end
def parse_result(descriptions)
{
santa_status: fetch_santa_status(descriptions),
descriptions: descriptions
}
end
# ここでサンタの画像から解析した文字列の配列をSettings.santa_wordsに格納されたサンタワードと照合してサンタかどうか確認します。
def fetch_santa_status(descriptions)
Settings.santa_words.each do |santa_word|
descriptions.each do |description|
return true if description.downcase.include?(santa_word)
end
end
return false
end
end
View
ビューは適当です(土下座)
= form_for @image, remote: true do |f|
= f.file_field :image, id: "js-file"
= f.submit "This is Santa", style: "font-size: 16px;"
br
img#js-image style='height: 500px;'
- if @santa
= image_tag("this_is_santa", id: "js-image", style: 'height: 500px;')
h1 style="font-size: 30px;"
| This is Santa!!! Good job!!!
- else
= image_tag("not_santa", style: 'height: 500px;')
div
| This might be
h2
- @descriptions.each do |description|
= description
| , or
| ...
= button_to '戻る', new_image_path, method: 'get'
Carrierwave
デフォルトだと外部API用にうまく画像のパスが生成されない(http://とかlocalhost〜とかつかない)のでちょっといじる必要があります。
CarrierWave.configure do |config|
config.asset_host = Settings.url
end
url: 'http://localhost:3000'
Google Cloud Vision API周りの実装
はい!やっと本題ですね。ここからAPIへのリクエストについて話していきます!
Google Cloud Vision APIで画像解析をするときに必要なことは以下の2つです。
①API Keyの取得
②リクエストの作成
API KEYの取得
APIの取得は、こちらの記事がとても参考になります。
Cloud Vision APIの使い方まとめ (サンプルコード付き)
こちらの記事が死ぬほど参考になるので、ここでは割愛させていただきます。 とにかく上の記事でAPIのキーをゲットしてきてください。検討を祈ります。それ以外いりません。
クレジットカードの登録が必要になりますが、無料期間を超えて勝手に請求される仕様ではないようですね。。60日間は無料利用できるらしいです。
取得したキーはSettings.ymlとか.envファイルとかで定数管理してください。publicなリモートリポジトリにプッシュしないように。
今回はSettings.ymlに定数として値を保持しておきます。
api_key: 'hogehogehogehoge'
santa_words: ['サンタ', 'santa', 'father christmas', '聖誕老人', 'Дед мороз', 'joulupukki', 'christkind', 'julenissen', 'père noël', 'papa noel']
ちなみに先程モデルファイルで使った定数のsanta_wordsには世界中のサンタクロースの呼び名を入れておきます。ここらへんとか自然言語処理のAPIとかいれたらこんなキモいコード書かずにいけそうですね。
リクエストの作成
Google Cloud Vision APIへのリクエストは以下の3点を気をつけてください。
① 画像パスをBase64にエンコードする
② postメソッドでリクエストする
③ jsonデータで送る
今回は 'httpclient' というhttpのリクエストのgemを使って実装します。
class GoogleCloudVision
attr_accessor :endpoint_uri, :file_path
def initialize(file_path)
@endpoint_uri = "https://vision.googleapis.com/v1/images:annotate?key=#{Settings.api_key}"
@file_path = file_path
end
def request
http_client = HTTPClient.new
content = Base64.strict_encode64(File.new(file_path, 'rb').read)
# 画像パスをBase64にエンコード
response = http_client.post_content(endpoint_uri, request_json(content), 'Content-Type' => 'application/json')
# リクエストの作成。request_jsonメソッドでjsonデータを生成
descriptions = fetch_descriptions(response)
# レスポンスから解析結果の文字列を配列として抽出。(["Santa Clause", "Christmas"]とかが理想)
end
def request_json(content)
{
requests: [{
image: {
content: content
# ここにエンコードした画像パスを
},
features: [{
type: "LABEL_DETECTION",
maxResults: 10
# 結果の取得数。なんでも大丈夫です。
}]
}]
}.to_json
end
def fetch_descriptions(response)
result = JSON.parse(response)['responses'].first
result['labelAnnotations'].map{ |label| label['description'] }
end
end
ココらへんの実装は icb54615さんのRailsからGoogle Cloud Vision APIを使ってみる をめちゃくちゃ参考にしました。ありがとうございます。
はい、これで完成したはずです!ぼくのは投稿画面で画像のプレビューができるようになっていますが、そこは割愛させていだきます。
まとめ
どうでしたでしょうか?さすが世界のGoogle。 誰しもがこの最高レベルの技術を簡単にアクセスできる時代になってくるのですね。素晴らしいです。みなさんもこれを機に機械学習系のAPIと遊んでみてはいかがでしょうか??
反省
以下、今回の反省を少し、、
・ 新たな技術的発信というよりは、ネタ記事っぽくなってしまって貢献度の低い記事になってしまった(せめてajax関数とかつかってリクエスト飛ばせばよかった。)
・ 顔の表情とかもスコアリングできるようなので、もっとそこらへんにも触れたかった。
では皆さん、今回はここまでということで。。
良いお年をーー!