最近ではAlphaGoの目覚ましい活躍などで「機械学習」という単語が世間をたびたび賑わせていますが、みなさんは「Google Cloud Vision API」(以降「Vision API」と略称)を知っているでしょうか?
Vision APIとは、Googleの機械学習モデルを使用した画像認識サービスのことです。
公式URL: https://cloud.google.com/vision/
「機械学習」と聞くと深層学習といったような専門的な知識が必要と思われるかもしれませんが、Vision APIを利用する場合はそういった知識は必要ありません。
Vision APIはGoogleが学習させた機械学習モデルを利用するだけなので、ユーザはVision APIのURLに対して、base64エンコードした画像データ(※1)と画像検知の種別(OCRなのか、顔検知なのか等)をPOSTするだけで、画像に写っているモノや状況を分析してくれます。
例えば、人間の顔が写っている画像を顔検知機能で処理した場合、画像内での顔や目鼻口の位置情報や、顔の表情から感情の予測結果を返してくれます。
※1) base64エンコードした画像データとは、64種類の英数字を使って、それ以外の文字を扱うことの出来ない通信環境にてマルチバイト文字やバイナリデータを扱うためのエンコード方式です。
今回は、そんなVision APIを利用できるようになるまでの説明と、誰でも実現可能な、Vision APIとPCのWebカメラを使ったほぼリアルタイムで顔検知するWebアプリを紹介したいと思います。
GCPプロジェクトを作成する
Vision APIはGCPのサービスですので、GCPのプロジェクトが必須です。
まずはVision APIを動かすために、GCPプロジェクトを作成しましょう。
プロジェクトとの作成手順は、「初心者のためのGCPプロジェクト始め方入門」を読んで下さい。
Vision APIのサービスの利用するには課金設定が必要ですので、自己責任の上でプロジェクトに設定して下さい。
また、Vision APIの利用料金についての詳細は公式ページを確認下さい。
※Vision APIは課金設定が必要ですが、2016年7月22日時点ではAPIの利用には一定の無料枠が設けられていますので、無料枠内での利用であれば課金設定をしても利用料金はかかりません。
Vision APIのサービスを有効化する
GCPプロジェクトを作成したら、次はVision APIのサービスを有効化します。
※利用する環境によっては、言語設定が異なる場合がありますが、ボタンの配置などは同じになっています。適宜、ご自身の利用する環境に読み替えて作業を行なって下さい。
プロジェクトのダッシュボード画面左上のメニューアイコンをクリックする
メニューから「API Manager」を選択する
API一覧画面の検索窓に「vision」と入力し、APIを検索する
APIの検索結果から「Google Cloud Vision API」を選択する
Vision APIのサービスを有効化する
Vision APIのサービスが有効になっているか画面から確認する
以上の手順でVision APIのサービスが有効になったかと思います。
Vision APIのAPIキーを作る
Vision APIサービスが有効になったら、次はAPIを利用するのに必要なAPIキーを作りましょう。
「API Manager」の「Credentials」画面へ遷移する
作成する資格情報でAPIキーをクリックする
APIキーの種類を選択する
ほぼリアルタイムで顔検知するWebアプリはVision APIをブラウザのJavascriptから使用するので、「Browser key」を選択します。
APIキーを作成する
「Name」と「Accept requests from these HTTP referrers (web sites)」はデフォルト値のままです。
この2つの項目は後から変更することもできますので、今はデフォルト値のままで良いでしょう。
確認
下記の画像のように「Creadentials」の一覧にName「Browser key 1」が表示されたら、APIキーの作成完了です。
Keyの項目にある値がAPIキーの文字列です。
今後はこのKeyの文字列を使ってVision APIへのリクエストを行ないますので、コピーしておいて下さい。
Webアプリを動かす
ここまででAPIキーの作成まで完了しました。
このAPIキーを使って、まずは今回紹介するアプリがどんなものか、実際にデモ環境で試してみましょう。
下記URLのページにアクセスして下さい。
使い方は、下記画面が表示されたら、「APIキー」の入力欄に「3.Vision APIのAPIキーを作る」で作ったAPIキーの文字列を入力し、「START」ボタンを押すだけです。
※APIキーを作成したプロジェクトのリソースを使用します。無料枠を超えますと、APIキーを作成したプロジェクトに利用料金が発生しますので、使い過ぎにご注意下さい。
Webカメラが起動後にカメラに人の顔が映ると、下記の画像のように各感情と着帽の推測結果を表示し、人の顔の位置を四角い枠で囲うようになります。
このアプリは”ほぼ”リアルタイムなので、人の顔を検知して、四角く囲うまでに多少のタイムラグがあります。
Vision APIの顔検知機能は、画像内の顔や目、鼻などのパーツの座標位置、表情から読み取った感情の情報を返してくれます。
※氏名などの個人情報に属するものは返しませんので、ご注意ください。
返してくれる情報の詳細は公式ページを見たほうが早いかと思いますので、下記ページをご覧ください。
URL: https://cloud.google.com/vision/reference/rest/v1/images/annotate#FaceAnnotation
また、感情の推測結果の%表示は、下記表のようにVision APIの返却値を置き換えて表示しています。
Vision APIからの実際の返却値 | Webアプリ上の表示値 |
UNKNOWN | 測定不能 |
VERY_UNLIKELY | 可能性1% |
UNLIKELY | 可能性25% |
POSSIBLE | 可能性50% |
LIKELY | 可能性75% |
VERY_LIKELY | 可能性99% |
Webアプリのコード解説
解説前に、一点注意事項ですが、今回はコードの全文は解説しません。
主要な処理についてのみ解説します。
コードの全文が知りたい方は、こちらから確認することができます。
※このアプリは、必要なCSSやJavascriptは全てCDN(※2)で読み込んでいるため、HTMLファイル一つで動作していますので、コピペするだけで動きます。
※2) コンテンツデリバリネットワーク(Content Delivery Network)の略称。コンテンツをネット経由で配信するために最適化されたネットワークのことです。
では、次はWebアプリのコードを解説したいと思います。処理は大きく下記4工程に分かれます。
工程1. Webカメラを起動する
工程2. Webカメラの動画のキャプチャ画像を取得する
工程3. キャプチャ画像をbase64エンコードし、Vision APIのURLにPOSTする
工程4. 顔の座標位置を取り出し、画面に描画する
工程1. Webカメラを起動する
「near-realtime-face-detection.html」の46行目~95行目に書いています。
$scope.startFilming関数から処理は始まります。
var HAS_GET_USER_MEDIA = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia; // Webカメラ対応かどうか確認します。
// Webカメラでの撮影開始します。
$scope.localMediaStream = null;
// startFilming関数は「START」ボタンのクリックイベントになっています。
$scope.startFilming = function() {
if (!HAS_GET_USER_MEDIA) {
alert("Webカメラ未対応のブラウザです。");
return;
}
if (!$scope.apiKey) {
alert("APIキーを入力して下さい")
return;
}
if ($scope.filming === true) {
// Webカメラ起動中の場合は処理は行ないません。
return;
}
var webCamera = document.getElementById('webCamera');
window.URL = window.URL || window.webkitURL;
navigator.getUserMedia = HAS_GET_USER_MEDIA;
navigator.getUserMedia({video: true}, function(stream) {
// WebカメラのLocalMediaStreamオブジェクトからURLを作成し、
// videoのソースにそのURLを指定します。
webCamera.src = window.URL.createObjectURL(stream);
$scope.localMediaStream = stream;
$scope.filming = true; // Webカメラ起動中にフラグを立てます。
// STEP2の処理を1000ミリ秒間隔で実行するように設定します。
$scope.captureProcessId = setInterval($scope.capture,1000)
// onFailSoHard関数はWebカメラが起動が起動できなかった場合のエラー処理です。
}, onFailSoHard);
}
// Webカメラ起動エラー時の処理
var onFailSoHard = function(e) {
// Webカメラが起動できなければエラーのアラートを出します。
alert("Webカメラがないか、未対応のブラウザです。\nエラー内容:" + e);
console.log('エラー!', e);
};
// Webカメラの停止
// stopFilming関数は「STOP」ボタンのクリックイベントになっています。
$scope.stopFilming = function() {
$scope.filming = false;
if (!$scope.localMediaStream) {
return;
}
// Webカメラを停止させるためにはvideoエレメントのpause関数を呼びます。
document.getElementById('webCamera').pause()
}
工程2. Webカメラの動画のキャプチャ画像を取得する
「near-realtime-face-detection.html」の98行目~134行目に書いています。
$scope.capture = function() {
if ($scope.filming != true || !$scope.localMediaStream) {
clearInterval($scope.captureProcessId)
return;
}
var webCamera = document.getElementById('webCamera'); // Webカメラの表示エレメントを取得します。
var vw = webCamera.videoWidth // Webカメラのビデオの横幅を取得します。
var hw = webCamera.videoHeight // Webカメラのビデオの縦幅を取得します。
$("#webCameraPic").attr("width", vw);// 顔を四角で囲う枠を表示するcanvasの横幅にWebカメラの横幅を設定します。
$("#webCameraPic").attr("height", hw);// 顔を四角で囲う枠を表示するcanvasの縦幅にWebカメラの縦幅を設定します。
$("#webCameraPicTemp").attr("width", vw);// Webカメラのキャプチャ画像の生成用非表示canvasの横幅にWebカメラの横幅を設定します。
$("#webCameraPicTemp").attr("height", hw);// Webカメラのキャプチャ画像の生成用非表示canvasの縦幅にWebカメラの縦幅を設定します。
var position = $("#webCamera").position();
var scrollTop = $("#result").scrollTop();
$("#webCameraPic").css("top", position.top + scrollTop);
var webCameraPicTemp = document.getElementById('webCameraPicTemp');
var webCameraPicTempCtx = webCameraPicTemp.getContext('2d');
webCameraPicTempCtx.clearRect(0, 0, webCameraPicTemp.width, webCameraPicTemp.height);
if ($scope.localMediaStream) {
// 前回キャプチャした時の四角い枠の画像が残っているので、描画をクリアします。
var webCameraPic = document.getElementById('webCameraPic');
var webCameraPicCtx = webCameraPic.getContext('2d');
webCameraPicCtx.clearRect(0, 0, webCameraPic.width, webCameraPic.height);
// Webカメラのキャプチャ画像を非表示canvasに描画します。
webCameraPicTempCtx.drawImage(webCamera, 0, 0, webCameraPicTemp.width, webCameraPicTemp.height);
var base64FileData = webCameraPicTemp.toDataURL('image/webp');
// キャプチャ画像で顔検知処理に渡します。
$scope.faceDetection('webCameraPic', base64FileData.split("base64,")[1]);
}
}
工程3. キャプチャ画像をbase64エンコードし、Vision APIのURLにPOSTする
「near-realtime-face-detection.html」の141行目~204行目に書いています。
// 顔検知処理
$scope.faceDetection = function(canvasId, base64FileData) {
if (!base64FileData) {
// 画像データが無ければアラートを表示して終了します。
alert("キャプチャデータがありません。")
return;
}
$scope.canvasId = canvasId;
// Vision APIへのリクエストデータを作成。
// imageのcontextにBase64エンコードした画像データを設定します。
// featuresのtypeに顔検知機能の値である'FACE_DETECTION'を設定します。
// typeを変えることによって、ロゴやOCRなどの他の機能を使うことができます。
// featuresのmaxResultsには1以上の数値を設定しましょう。
var data = {
"requests":[
{
"image":{
"content": base64FileData
},
"features":[
{
"type":"FACE_DETECTION",
"maxResults":100
}
]
}
],
}
// Vision APIのエンドポイントにPOSTします。
$http.post(VISION_API_URL + $scope.apiKey, data).success(function(data, status, headers, config) {
console.log(data)
var cvs = document.getElementById($scope.canvasId);
var ctx = cvs.getContext("2d");
var responses = data.responses;
angular.forEach(responses, function(response, key) {
$scope.faceAnnotations = response.faceAnnotations;
if (!$scope.faceAnnotations) {
angular.forEach($scope.faceAnnotations, function(faceAnnotation, key) {
// 頭を含む座標情報を取得する
var boundingPolyVertices = faceAnnotation.boundingPoly.vertices;
// 顔を囲う四角い枠を描画する。
var result = drawLine(boundingPolyVertices, ctx, $scope.colors[key]["VERY_LIKELY"]);
if (result == 1) {
// 頭を含む顔全体を認識できなかったら、頭を含まない顔の部分だけの座標情報を取得します。
var fdBoundingPolyVertices = faceAnnotation.fdBoundingPoly.vertices;
// STEP4の処理になります。
result = drawLine(fdBoundingPolyVertices, ctx, $scope.colors[key]["VERY_LIKELY"]);
}
})
}
})
$scope.canvasId = null;
}).error(function(data, status, headers, config) {
console.log("error")
console.log(data)
$scope.canvasId = null;
});
}
工程4. 顔の座標位置を取り出し、画面に描画する
「near-realtime-face-detection.html」の206行目~238行目に書いています。
function drawLine(vertices, ctx, strokeStyle) {
// 顔を囲む4点座標を取り出します。
var x1 = vertices[0].x;
var y1 = vertices[0].y;
var x2 = vertices[1].x;
var y2 = vertices[1].y;
var x3 = vertices[2].x;
var y3 = vertices[2].y;
var x4 = vertices[3].x;
var y4 = vertices[3].y;
if (x1 && y1 && x2 && y2 && x3 && y3 && x4 && y4) {
// 4点の位置情報が取れたら、各座標間に線を描画します。
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.moveTo(x2, y2);
ctx.lineTo(x3, y3);
ctx.moveTo(x3, y3);
ctx.lineTo(x4, y4);
ctx.moveTo(x4, y4);
ctx.lineTo(x1, y1);
ctx.lineWidth = 3;
ctx.strokeStyle = strokeStyle;
ctx.stroke();
return 0;
}
// 座標が取れなかったら、描画しません。
return 1;
}
まとめ
以上で誰でも作れるほぼリアルタイムで顔検知を行なうWebアプリの説明は終わりです。
Vision APIを使えば、簡単に作れますね。
Vision APIには顔検知機能以外にも、物体検知やOCRなどの機能もあるのでいろいろなアプリに利用できます。
例えば、物体検知をやりたい場合は、下記のように工程3の$scope.faceDetection関数で作成しているリクエストデータのrequests[0].featurest[0].typeの値に「LABEL_DETECTION」を指定して、顔検知で使用したURLと同じURLにリクエストを投げるだけです。
※返却値は、指定した機能によって異なりますので、その他OCR機能など詳細は公式ドキュメントを確認して下さい。
var data = {
"requests":[
{
"image":{
"content": base64FileData
},
"features":[
{
"type":"LABEL_DETECTION",
"maxResults":100
}
]
}
]
}
当社では当APIやGCPの各種機械学習サービスを利用したシステム開発を請け負っておりますので、「こんなこと出来る?」レベルでも構いませんので、是非お気軽にご相談ください。