Cloud Vision APIの画像認識精度を試してみた

こんにちは。制作部フロントエンジニアの苅部です。

GoogleからCloud Vision APIの提供が始まっていたので、スマートフォンのカメラから利用できるモック画面を作って、APIの画像認識精度を試してみました。
簡単ではありますが、HerokuでのNode.js利用のおさらいと、実際にいくつかの画像を送信した結果を共有できたらと思います。
(Cloud Vision APIは2/18日に公開ベータになっています)

Cloud Vision APIとは

GoogleフォトやSafeSearchで採用されている、Googleの機械学習の画像認識APIです。
画像を載せてAPIコールすることで以下の情報の取得が可能です。

  • 物体検知
  • OCR
  • 有害コンテンツ検知
  • 顔検知
  • ロゴ検知
  • ランドマーク検知

HerokuでのNode.js利用までの流れ

APIKEY取得からAPIコールおよび画面実装までの流れをご説明します。
今回は以下の要件で実装しています。

  • WEBサーバー: Node.js/Express4(Jade)
  • プラットフォーム: Heroku
  • クライアント: jQuery
  • UIパーツ: OnsenUI

WEBサーバー側もJavaScriptを利用しているので、フロントエンジニアの方ならDOM操作とちょっとしたAPIの仕様把握だけで短時間で実装ができると思います。

1. Developer Consoleへの登録・APIKEYの取得

GoogleのAPIを利用するにあたり、まずはDeveloper登録が必要になります。

2. Herokuへの登録と準備

Node.jsが利用可能で、ドメインも発行されるHerokuをプラットフォームとして選択しました。 MongoDBも簡単に扱う事ができますし、スピード感をもってモック作成するには最適なサービスだと思います。

・ユーザー登録

Herokuのアカウントを持っていなければ登録します。
https://www.heroku.com/

・Heroku Tool Beltをインストール

Herokuをコマンドラインから利用できるようにします。
https://toolbelt.heroku.com/

・Herokuへログイン

$ heroku login

認証を求められるので、Herokuのid/passを入力してログインします。

・Gitリポジトリの作成

$ cd my-project/
$ git init
$ heroku git:remote -a [projectName]

・Procfileの作成

Procfileという名前で、プロセスの定義ファイルをプロジェクトルートに配置します。
今回の場合 Nodeでapp.jsというファイル扱うため、以下のような内容で設定しています。

node app.js

以上でHerokuを利用する準備が整いました。

3. Node.js側の実装

WEBフレームワークはExpress/Jadeを利用しています。
Node.jsのスクリプトは以下のような内容で[app.js]としました。

var express = require('express');
var bodyParser = require('body-parser');
var request = require('request');
var app = express();
var server = app.listen(process.env.PORT || 3000, function () {
    var host = server.address().address;
    var port = server.address().port;
    console.log('Example app listening at http://%s:%s', host, port);
});
app.set('view engine', 'jade');
app.set('views', __dirname + '/views');
app.use(express.static(__dirname + '/public'));
app.use(bodyParser.urlencoded({extended: false, jsonLimit: '3000kb'}));
app.get('/', function (req, res) {
    res.render('index', { title: 'Cloud Vision API Test App' });
});
app.post('/api/cloudvision',function(req,res){
    var image = req.body.image.match(/base64,(.*)$/)[1];
    var reuestOption = {
        uri: 'https://vision.googleapis.com/v1/images:annotate?key=[APIKEY]',
        headers: {'Content-Type': 'application/json'},
        json:{
            "requests":[
                {
                    "image":{"content": image},
                    "features":[
                        {"type": "FACE_DETECTION", "maxResults": 5},
                        {"type": "LABEL_DETECTION", "maxResults": 5},
                        {"type": "TEXT_DETECTION", "maxResults": 5},
                        {"type": "LANDMARK_DETECTION", "maxResults": 5},
                        {"type": "LOGO_DETECTION", "maxResults": 5},
                        {"type": "SAFE_SEARCH_DETECTION", "maxResults": 5}
                    ]
                }
            ]
        }
    };
    request.post(reuestOption, function (error, response, body) {
        if (!error && response.statusCode == 200) {
            res.status(200).json(body.responses)
        }else{
            res.status(200).json({ status: 'err' })
        }
    });
});

— 補足 —

jsonLimit: ‘3000kb’
bodyparserのデフォルト値が1MBのため、3MBへ増やしています。
※ 画像認識の精度を上げるため可能な限り解像度は高い方が良さそうです。
※ GoogleのAPI側では8MB/request,2MB/imageの制限がかかっています。

requestOptions.uri
APIキー[サーバーキー]を含めたGoogle側エンドポイントです。
※限定プレビューと公開ベータではURIが若干異なります。

requestOptions.json.requests.image
base64エンコードした画像です。

requestOptions.json.requests.features
必要になる画像認識機能と件数を任意で設定します。

request.post()
requestモジュールを使いGoogleへPOSTリクエストします。

4. クライアント側の実装

Node.js側で設定したURL[/api/cloudvision]に、画像を載せる形でPOSTリクエストを行い、レスポンスのJSONデータを整えて画面に表示します。
コードの記述は省略しますが、実装の要点は以下のようになります。

1) inputタグのaccept属性にcapture=cameraを追加する事で、モバイルブラウザでもカメラ/カメラロールから画像取り込みができるようになります。

<input type="file" accept="image/*;capture=camera">

2) 取り込まれた画像をリサイズ/圧縮するために(2MB以内に抑えるために)jQueryプラグインを利用しました。
https://github.com/gokercebeci/canvasResize
ユーザーがスライダーUIを操作することで、任意で画質のレベル調整ができます。

$('.input-range').change(function(){
    var file = $('input[type=file]')[0].files[0]
    compressImage(file)
})
function compressImage(file){
    var compressLevel = Number($('.range').val());
    $.canvasResize(file, {
        width: 610,
        height: 0,
        crop: false,
        quality: compressLevel,
        callback: function(data, width, height) {
            $(".image-preview").attr('src', data);
        }
    });
}

3) UIはOnsenUIを利用しています。(ボタンやスライダーの見た目を整えます)

5.アプリケーションのデプロイ

リモート環境にアプリケーションをPUSHして、デプロイします。

$ git add .
$ git commit -am "make it better"
$ git push heroku master

アプリケーションを動かす上で最低限必要なファイルは以下のようになります。
(staticなファイルは除く)

├── Procfile
├── app.js
├── package.json
└── views
    └── index.jade

画面の表示

デプロイが完了したので画面を確認してみます。

iOSではカメラ/カメラロールから添付できることがわかります。

PCであればコマンドラインでブラウザを立ち上げ、アプリケーションを表示する事もできます。

$ heroku open

実際にリクエストを投げてみた結果

では早速手持ちの写真を送信して、Googleが画像をどのように認識しているか試してみます。
※ ここで表示しているのはサムネイルですので、実際に送信している画像とは解像度や画質が異なります。
※ レスポンスのJSONは見やすくするため、一部の値を削除しています。

Face Annotations (顔検知)

被写体がどういった表情をしているか、顔のパーツがどこにあるか、といった情報が取得できます。

ImageResponse
“joyLikelihood”: “VERY_LIKELY”,
“sorrowLikelihood”: “VERY_UNLIKELY”,
“angerLikelihood”: “VERY_UNLIKELY”,
“surpriseLikelihood”: “VERY_UNLIKELY”,
“underExposedLikelihood”:
“VERY_UNLIKELY”,
“blurredLikelihood”: “VERY_UNLIKELY”,
“headwearLikelihood”: “VERY_UNLIKELY”
  • joyLikelihoodが"VERY_LIKELY"で返っていて、[楽しんでいる]という事が認識できています。

Text Annotations (OCR)

画像に写り込んでいる文字情報について、内容を抽出したり言語判定したりすることができます。

ImageResponse
“textAnnotations”: [{
“locale”: “en”,
“description”: “ABOUT LIFE\n
COFFEE BREWERS\n
1-19-8 DOGENZAKA SHIBUYA\n
TOKYO JAPAN 150-0043\n
TEL 070-5587-5342\n
OPEN EVERYDAY 8:30-20:30\n
www.about-life.coffee\n
”}]
  • 誤りの無い文字情報が取得できました。

Label Annotations (物体検知)

物体の情報(ラベル)を、数千ものカテゴリの中から分類ができます。

ImageResponse
“labelAnnotations”: [{
“description”: “auto show”,
“score”: 0.99762589}],
“labelAnnotations”: [{
“description”: “ferrari f430”,
“score”: 0.997009}]
“labelAnnotations”: [{
“description”: “sunflower”,
“score”: 0.97045457}]
“labelAnnotations”:[{
“description”: “hydrangea”,
“score”: 0.97828537}],
“labelAnnotations”: [{
“description”: “cyclo cross”,
“score”: 0.98051214
}, {
“description”: “cyclo cross bicycle”,
“score”: 0.96340382}],
  • 1枚目の写真では自動車に限らず、[auto show]ということまで認識できました。
  • 2枚目の写真はFerrariだけではなくF430という車種まで認識しました。
  • 3,4枚目では花の種類をしっかり認識しています。
  • 5枚目は自転車だけではなく、[シクロクロス]というところまで認識しました。
    この画像でシクロクロスと認識できるのは、なかなか人間の知能に近いように思えます。

Logo Annotations (ロゴ検知)

商品や企業のロゴを検知できます。

ImageResponse
“logoAnnotations”: [{
“description”: “Lamborghini”,
“score”: 0.3002826,
“boundingPoly”: {
“vertices”: [{“x”: 72,“y”: 124},
{“x”: 105,“y”: 124},
{“x”: 105,“y”: 161},
{“x”: 72,“y”: 161}]}
}],
  • 車体の後方に映っているランボルギーニの猛牛のロゴが認識されました。
    ※boundingPolyで位置の特定が可能です。

Landmark Annotations (ランドマーク検知)

自然構造物や位置情報の取得が可能です。
今回は位置情報が返却された画像のみをピックアップしました。

ImageResponse
“description”:“Brooklyn Bridge”,
“locations”: [{
“latLng”: { “latitude”: 40.705726,
“longitude”: -73.996953}}]

GoogleMap
“description”: “Broadway”,
“locations”: [{
“latLng”: { “latitude”: 40.783708,
“longitude”: -73.97948}}]

GoogleMap
“description”: “BFI Southbank”,
“locations”: [{
“latLng”: { “latitude”: 51.507032,
“longitude”: -0.115935}}]

GoogleMap
“description”: “Brick Lane”,
“locations”: [{
“latLng”: { “latitude”: 51.520466,
“longitude”: -0.071496}}]

GoogleMap
“description”: “Camden Lock”,
“locations”: [{
“latLng”: { “latitude”: 51.541274,
“longitude”: -0.145536}}]

GoogleMap
“description”: “Trellick Tower”,
“locations”:[{
“latLng”: { “latitude”: 51.523163,
“longitude”: -0.205928}}]

GoogleMap
“description”: “Nagawado Dam”,
“locations”: [{
“latLng”: { “latitude”: 36.132087,
“longitude”: 137.719245}}]

GoogleMap
“description”:
Hyōgo Prefectural Museum of Art”,
“locations”: [{
“latLng”:
{“latitude”: 34.699125099999996,
“longitude”: 135.218491}}]

GoogleMap
“description”:
Consolidated city-county”,
“locations”: [{
“latLng”: { “latitude”: 35.688879,
“longitude”: 139.69056129455566}}]

GoogleMap
“description”: “Haneda Airport”,
“score”: 0.36270857,
“locations”: [{“latLng”: {
“latitude”: 35.578347,
“longitude”: 139.784348}
}]

GoogleMap
“description”:
Tokyo Bay Aqua-Line”,
“locations”: [{
“latLng”: { “latitude”: 35.463999,
“longitude”: 139.874554}}]

GoogleMap

どの写真でもDescriptionと位置情報が正確に返却されています。
※アップロードした画像には位置情報に関するEXIFは含まれていません。

  • 2枚目の写真はNYのBanksyのグラフィティですが、Googleは位置情報を正確に理解しているようです。 (GoogleMapのストリートビューでもグラフィティが確認できます)
  • 3,4枚目はロンドンのグラフィティになりますが、これだけの写真でも理解できるようです。
  • それ以降の写真でも、無機質なダムの写真だけでも奈川渡ダムと判定されたり、らせん状のコンクリートだけで兵庫県立美術館と認識しました。
  • 飛行機の写真は羽田空港と認識されています。位置情報も実際に撮影した場所から近く、どういったロジックなのかとても不思議です。
  • 最後は、夜間の暗い写真ですがアクアラインとして認識される事が可能でした。

もしかすると、Googleは世の中に散らばっている画像やそれに含まれるEXIFの位置情報を照らし合わせて機械学習しているのかもしれません。

もしそうであれば 同じような構図が多く、位置情報が残るデジカメやスマートフォンで撮られているような写真(構図)が、ランドマーク(位置情報)検知されやすいのかも、、しれません。

まとめ

特徴的なレスポンスが返された画像だけを今回ピックアップしましたが、ここまで正確に、細かく認識できるのは驚きました。 RESTで提供されている画像認識APIは他にもMicrosoftのProject OxfordやIBMのWatosonがありますが、物体検知のカテゴリ数や認識精度で比較すると、今の段階ではGoogleの方が一歩進んでる印象があります。

最近Narrative Clip2を購入したのですが、Cloud Vision APIを使って大量の画像データをフィルターしたりなど、いろいろと妄想が膨らみます。

皆様も是非一度、Googleの画像認識のAPIを試してみて、使い道を検討されてはいかがでしょうか!