前から気になっていた Cloud Vision API を触ってみたのでメモ。
事前準備
Cloud Vision APIを使うには、Google Cloud Platformへ登録が必要である。未登録の場合、Cloud Vision APIの「無料トライアル」ボタンを押して登録しよう。
登録したら、APIキー(サーバーキー)の払い出しをして、APIを叩けるようにしよう。
Cloud Vision APIの使い方まとめ (サンプルコード付き)が参考になるので、困ったらこちらで。
実装
ローカルマシンにあるファイルで試してみる。やることは大きく3つ。
- 画像をbase64にエンコードする
- エンコード済の画像を含めて、APIリクエストパラメータを組み立てる
- Cloud Vision APIにリクエストを投げる
というわけでサンプルコードを晒しておこう。
require 'base64'
require 'json'
require 'net/https'
IMAGE_FILE = './sample.jpg'
API_KEY = 'xxxxxxxx'
API_URL = "https://vision.googleapis.com/v1/images:annotate?key=#{API_KEY}"
# 画像をbase64にエンコード
base64_image = Base64.strict_encode64(File.new(IMAGE_FILE, 'rb').read)
# APIリクエスト用のJSONパラメータの組み立て
body = {
requests: [{
image: {
content: base64_image
},
features: [
{
type: 'LABEL_DETECTION',
maxResults: 5
}
]
}]
}.to_json
# Google Cloud Vision APIにリクエスト投げる
uri = URI.parse(API_URL)
https = Net::HTTP.new(uri.host, uri.port)
https.use_ssl = true
request = Net::HTTP::Post.new(uri.request_uri)
request["Content-Type"] = "application/json"
response = https.request(request, body)
# APIレスポンス出力
puts response.body
gemを使わずに実装したので、リクエストを投げるトコロが若干雑然としているが、Cloud Vision API 自体は非常にシンプルに使うことができる。
検証
JSONパラメータを構築している箇所ではLABEL_DETECTION
と指定しているが、ココを変更すると、物体認識以外も行える。主な機能は以下のとおり。
- 物体認識 : LABEL_DETECTION
- 顔認識 : FACE_DETECTION
- ロゴ認識 : LOGO_DETECTION
- ランドマーク認識 : LANDMARK_DETECTION
- テキスト認識 : TEXT_DETECTION
- エログロ認識 : SAFE_SEARCH_DETECTION
いくつか試してみよう。
LABEL_DETECTION
とりあえず、平和にネコ画像で試してみる。レスポンスを見るとちゃんとネコって認識された!
リクエスト画像
https://www.pakutaso.com/20160344074post-7250.html
レスポンス
{
"responses": [
{
"labelAnnotations": [
{
"mid": "/m/01l7qd",
"description": "whiskers",
"score": 0.97826087
},
{
"mid": "/m/0jbk",
"description": "animal",
"score": 0.93817031
},
{
"mid": "/m/04rky",
"description": "mammal",
"score": 0.92028791
},
{
"mid": "/m/01yrx",
"description": "cat",
"score": 0.88385665
},
{
"mid": "/m/0hjzp",
"description": "kitten",
"score": 0.8793053
}
]
}
]
}
FACE_DETECTION
なんだか怪しげな男性の画像を投げてみた。目や唇などのかなり細かい情報が返ってきている。
joyLikelihood が LIKELY
になっていて、感情認識の結果は「楽しい」ということらしい。まぁ、楽しいってよりは怪しいだけど。
リクエスト画像
https://www.pakutaso.com/201410082832-1.html
レスポンス
{
"responses": [
{
"faceAnnotations": [
{
"boundingPoly": {
"vertices": [
{
"x": 148,
"y": 29
},
{
"x": 270,
"y": 29
},
{
"x": 270,
"y": 171
},
{
"x": 148,
"y": 171
}
]
},
"fdBoundingPoly": {
"vertices": [
{
"x": 161,
"y": 71
},
{
"x": 247,
"y": 71
},
{
"x": 247,
"y": 157
},
{
"x": 161,
"y": 157
}
]
},
"landmarks": [
{
"type": "LEFT_EYE",
"position": {
"x": 181.09821,
"y": 99.2435,
"z": 0.0002638482
}
},
{
"type": "RIGHT_EYE",
"position": {
"x": 214.13016,
"y": 97.099365,
"z": -12.981007
}
},
{
"type": "LEFT_OF_LEFT_EYEBROW",
"position": {
"x": 167.40213,
"y": 91.100555,
"z": 7.2265382
}
},
{
"type": "RIGHT_OF_LEFT_EYEBROW",
"position": {
"x": 184.34164,
"y": 91.6631,
"z": -10.791
}
},
{
"type": "LEFT_OF_RIGHT_EYEBROW",
"position": {
"x": 201.12782,
"y": 90.614441,
"z": -17.404898
}
},
{
"type": "RIGHT_OF_RIGHT_EYEBROW",
"position": {
"x": 225.91609,
"y": 88.156143,
"z": -15.385879
}
},
{
"type": "MIDPOINT_BETWEEN_EYES",
"position": {
"x": 193.25211,
"y": 98.665451,
"z": -13.574019
}
},
{
"type": "NOSE_TIP",
"position": {
"x": 190.78255,
"y": 123.63931,
"z": -20.968615
}
},
{
"type": "UPPER_LIP",
"position": {
"x": 196.33002,
"y": 135.32417,
"z": -10.72403
}
},
{
"type": "LOWER_LIP",
"position": {
"x": 198.57047,
"y": 145.97295,
"z": -6.6631284
}
},
{
"type": "MOUTH_LEFT",
"position": {
"x": 186.68929,
"y": 138.16307,
"z": 4.5638247
}
},
{
"type": "MOUTH_RIGHT",
"position": {
"x": 216.51965,
"y": 134.52066,
"z": -5.9020534
}
},
{
"type": "MOUTH_CENTER",
"position": {
"x": 197.80579,
"y": 139.89142,
"z": -7.4092579
}
},
{
"type": "NOSE_BOTTOM_RIGHT",
"position": {
"x": 206.1855,
"y": 124.34725,
"z": -10.612206
}
},
{
"type": "NOSE_BOTTOM_LEFT",
"position": {
"x": 187.03342,
"y": 124.05929,
"z": -3.0776432
}
},
{
"type": "NOSE_BOTTOM_CENTER",
"position": {
"x": 195.05676,
"y": 128.69412,
"z": -11.879737
}
},
{
"type": "LEFT_EYE_TOP_BOUNDARY",
"position": {
"x": 177.71968,
"y": 97.635468,
"z": -1.9731181
}
},
{
"type": "LEFT_EYE_RIGHT_CORNER",
"position": {
"x": 186.35037,
"y": 99.970093,
"z": -1.8754189
}
},
{
"type": "LEFT_EYE_BOTTOM_BOUNDARY",
"position": {
"x": 179.83739,
"y": 102.04105,
"z": 0.64033604
}
},
{
"type": "LEFT_EYE_LEFT_CORNER",
"position": {
"x": 173.11966,
"y": 99.7238,
"z": 6.581109
}
},
{
"type": "LEFT_EYE_PUPIL",
"position": {
"x": 177.93286,
"y": 100.0177,
"z": 0.06591022
}
},
{
"type": "RIGHT_EYE_TOP_BOUNDARY",
"position": {
"x": 212.77274,
"y": 95.898186,
"z": -15.496446
}
},
{
"type": "RIGHT_EYE_RIGHT_CORNER",
"position": {
"x": 221.93607,
"y": 97.035385,
"z": -12.34076
}
},
{
"type": "RIGHT_EYE_BOTTOM_BOUNDARY",
"position": {
"x": 214.28592,
"y": 99.752068,
"z": -12.837732
}
},
{
"type": "RIGHT_EYE_LEFT_CORNER",
"position": {
"x": 206.82005,
"y": 98.930138,
"z": -9.9026051
}
},
{
"type": "RIGHT_EYE_PUPIL",
"position": {
"x": 213.9201,
"y": 98.2251,
"z": -13.893643
}
},
{
"type": "LEFT_EYEBROW_UPPER_MIDPOINT",
"position": {
"x": 174.36987,
"y": 87.284866,
"z": -4.6636252
}
},
{
"type": "RIGHT_EYEBROW_UPPER_MIDPOINT",
"position": {
"x": 212.43112,
"y": 85.409851,
"z": -19.328856
}
},
{
"type": "LEFT_EAR_TRAGION",
"position": {
"x": 174.67477,
"y": 111.92154,
"z": 54.401321
}
},
{
"type": "RIGHT_EAR_TRAGION",
"position": {
"x": 251.83156,
"y": 108.63575,
"z": 22.954367
}
},
{
"type": "FOREHEAD_GLABELLA",
"position": {
"x": 192.27304,
"y": 91.03653,
"z": -15.46795
}
},
{
"type": "CHIN_GNATHION",
"position": {
"x": 202.21428,
"y": 161.46428,
"z": 0.617989
}
},
{
"type": "CHIN_LEFT_GONION",
"position": {
"x": 175.51978,
"y": 138.10352,
"z": 41.916679
}
},
{
"type": "CHIN_RIGHT_GONION",
"position": {
"x": 247.22699,
"y": 134.34093,
"z": 14.262062
}
}
],
"rollAngle": -5.8018222,
"panAngle": -21.274296,
"tiltAngle": -7.6548777,
"detectionConfidence": 0.99945271,
"landmarkingConfidence": 0.66470236,
"joyLikelihood": "LIKELY",
"sorrowLikelihood": "VERY_UNLIKELY",
"angerLikelihood": "VERY_UNLIKELY",
"surpriseLikelihood": "VERY_UNLIKELY",
"underExposedLikelihood": "VERY_UNLIKELY",
"blurredLikelihood": "VERY_UNLIKELY",
"headwearLikelihood": "UNLIKELY"
}
]
}
]
}
SAFE_SEARCH_DETECTION
画像は自粛するが、適当に拾ってきたエロ画像で試してみた。ちゃんと、adultがVERY_LIKELY
になっているw
レスポンス
{
"responses": [
{
"safeSearchAnnotation": {
"adult": "VERY_LIKELY",
"spoof": "UNLIKELY",
"medical": "LIKELY",
"violence": "VERY_UNLIKELY"
}
}
]
}
rake タスク
さすがに、実装が雑すぎて検証しづらいのでrakeタスクにしてみた。楽に検証できるよう、ローカルにあるファイルだけじゃなく、Webにある画像を直接指定できるようにしている。
インストール方法
git clone git@github.com:tmknom/ruby-cloud-vision.git && cd ruby-cloud-vision
bundle install --path=vendor/bundle
echo 'export GCP_API_KEY=[your_api_key]' >> .env
使い方
# Display all tasks.
$ bundle exec rake -T
# Run face detection.
$ bundle exec rake vision:face[<path/to/image>]
# Compute a set of properties about the image (such as the image's dominant colors).
$ bundle exec rake vision:image_properties[<path/to/image>]
# Run label detection.
$ bundle exec rake vision:label[<path/to/image>]
# Run landmark detection.
$ bundle exec rake vision:landmark[<path/to/image>]
# Run logo detection.
$ bundle exec rake vision:logo[<path/to/image>]
# Run various computer vision models to compute image safe-search properties.
$ bundle exec rake vision:safe_search[<path/to/image>]
# Run OCR.
$ bundle exec rake vision:text[<path/to/image>]
# Unspecified feature type.
$ bundle exec rake vision:unspecified[<path/to/image>]
実行例
ローカルにある画像
$ bundle exec rake vision:label[/Users/sample_user/sample.jpg]
Web上にある画像
$ bundle exec rake vision:label[http://example.com/sample.jpg]
まとめ
というわけで、Cloud Vision APIを叩いて遊んでみた。
使い方次第で面白いサービスが手軽に作れそうだね。スゴイ時代になったもんだ。