JAWS-UG名古屋での発表用に、顔認識をするようなアプリをLambdaで作りました。
内容としては、アップロードした画像を顔認識して、両目の座標を取得したのち目線を入れる、というものになります。
仕組み
この処理のポイントとしては、クライアントサイドでGUIDを生成して、そのGUID名を使ってデータをやり取りするところになります。クライアントはGUIDを生成して、AWS SDK for Javascriptを使ってデータをアップロードします。その後、クライアントサイドはGUID名/result.jsonをポーリングします。
通知を受けたLambdaはサーバサイドで顔認識を行い、結果をGUID/result.jsonの形で吐き出します。
クライアントサイドで作ったIDで、Lambdaと個別やりとりをする形になり、こうすることでEC2を使わずに、Lambdaと1 on 1で対話出来るようにしています。
サーバサイド処理
実のところ、本当はサーバサイドで画像加工したかったのですが、phantomjsやopencvの設定が今一つうまく行かず、LT発表の時間が迫っていたため、今回は外部サービス(SkyBiometry)に頼ってしまいました(このサービスは、以前Ice Bucket Challengeのときに id:tottokug さんから教えてもらいました!)
ですのでサーバサイドは実に簡潔です。
var aws = require('aws-sdk'); var http = require('http'); exports.aws = aws; exports.handler = function(event, context) { var bucket = event.Records[0].s3.bucket.name; var key = event.Records[0].s3.object.key; var region = event.Records[0].awsRegion; var op = event.Records[0].eventName; if(op !=="ObjectCreated:Put"){ context.done(null, 'op'); return; } console.log("key="+key); var skybio = "http://api.skybiometry.com/fc/faces/detect.json?api_key=XXXXXXX&api_secret=XXXXXXX&urls="; skybio += "https://S3バケット/" + key; console.log(skybio); http.get(skybio, function(res) { var body = ""; res.on("data", function(chunk) { body += chunk; }); res.on('end', function() { var uuid = key.split("/")[0]; console.log("uuid=" + uuid); var resultKey = uuid + "/result.json"; var s3 = new aws.S3({ params : { Bucket : bucket, Key : resultKey } }); s3.putObject({ Body : body }, function(err) { if (err) { context.done("ERROR", err); } else { context.done(null, 'put result'); } }); }); }).on('error', function(e) { console.log("Got error: " + e.message); }); };
クライアント処理
<!DOCTYPE html> <html> <head> <title>S3 uploader</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <script src="https://sdk.amazonaws.com/js/aws-sdk-2.0.29.min.js"></script> <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script> <script> var s3BucketName = "バケット名"; var s3RegionName = "us-west-2"; var guid; var fileName; var file; function uploadFile() { disableUploadButton(); var params = { AccountId: "AWSアカウントID", RoleArn: "IAMロール", IdentityPoolId: "CogniteのIdentityPoolId" }; AWS.config.region = 'us-east-1'; AWS.config.credentials = new AWS.CognitoIdentityCredentials(params); AWS.config.credentials.get(function(err) { if (!err) { beginUpload(); }else{ alert(err); } }); } function beginUpload(){ $('#putButton').text('アップロード中....'); guid = generateGUID(); file = document.getElementById('fileToUpload').files[0]; if (file) { fileName = file.name; var key = guid+"/"+fileName; var s3 = new AWS.S3({region: s3RegionName, maxRetries: 100}); s3.putObject({Bucket: s3BucketName,Key: key, ContentType: file.type, Body: file}, function(err, data) { if (data !== null) { waitForResult(); } else { alert("エラーが発生しました。 "+err); enableUploaButton(); } }); } } function waitForResult(){ $('#putButton').text('認識実行中....'); var key = guid+"/result.json"; var s3 = new AWS.S3({region: s3RegionName, maxRetries: 1}); s3.getObject({Bucket: s3BucketName,Key: key}, function(err, data) { if (data !== null) { showImage(data); } else { setTimeout(waitForResult,1000); } }); } function showImage(data){ var result = JSON.parse(data.Body); drawBlindfold(result); } function drawBlindfold(result){ var canvas = document.getElementById('myCanvas'); if ( ! canvas || ! canvas.getContext ) { alert("canvas is not supported"); return false; } var photo = result.photos[0]; canvas.width = photo.width; canvas.height = photo.height; var ctx = canvas.getContext('2d'); var img = new Image(); img.src = result.photos[0].url+"?" + new Date().getTime(); img.onload = function() { ctx.drawImage(img, 0, 0); ctx.strokeStyle = 'rgb(0,0,0)'; ctx.fillStyle = 'rgb(0,0,0)'; //目線入れる var tags = photo.tags; for(var i = 0;i < tags.length;i++){ var tag = tags[i]; var eye_left = tag.eye_left; var eye_right = tag.eye_right; if(eye_left!=undefined && eye_right!=undefined){ var w = photo.width; var h = photo.height; var rightX = w * eye_right.x / 100; var rightY = h * eye_right.y / 100; var leftX = w * eye_left.x / 100; var leftY = h * eye_left.y / 100; ctx.beginPath(); var marginX = 30;var marginY=10; ctx.moveTo(rightX-marginX,rightY-marginY); ctx.lineTo(leftX+marginX,leftY-marginY); ctx.lineTo(leftX+marginX,leftY+marginY); ctx.lineTo(rightX-marginX,rightY+marginY); ctx.lineTo(rightX-marginX,rightY-marginY); ctx.closePath(); ctx.stroke(); ctx.fill(); } } enableUploaButton(); } } function disableUploadButton(){ $("#putButton").attr('disabled', true); $('#putButton').text('認証情報取得中....'); } function enableUploaButton(){ $('#putButton').attr('disabled', false); $('#putButton').removeAttr('disabled'); $('#putButton').text('認識完了'); } function generateGUID() { var s4 = function() { return Math.floor((1 + Math.random()) * 0x10000) .toString(16) .substring(1); } return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4(); } </script> </head> <body> <center> <h1>AWS Lambdaで顔認識</h1> <h2>画像ファイルを選択して、認識実行を押してください。</h2> <input type="file" id="fileToUpload" /> <button id="putButton" onclick="uploadFile()">認識実行</button> <br> <br> <canvas id="myCanvas"></canvas> </body> </center> </html>
いずれにせよ、工夫次第でS3+Lambdaだけで動的(っぽい)サイトが作れそうですね。