技術は私たちの力。技術は私たちの楽しみ。 Creative Developer BLOG 技術部ブログ
Technology is our strength. Technology is what we enjoy.

ブラウザの進化について考える

2018-05-11 サーバ・AWS
「進化」をテーマにしたものづくりの勉強会、第3回です!
今回は、システム部の菊田が担当いたします。

どんな進化?

ちょうど先月、Mozillaが、「WebAssembly Studio」を公開したことや

iOS11.3でもPWAの一部に対応し、iPhoneでもWebアセンブリが利用可能になったこと

Microsoftでも.NETをブラウザで動くようにできる「Blazor」を登場させたり、

GoogleもGO言語をWebAssemblyに対応させたりと、

もうあちこちでこの「WebAssembly」に関して、活発にリリースが続いてます!

ここまで各社が足並み揃えてブラウザを進化させようと取り組んでる技術ですから、試さずにはいられません

WebAssemblyってどんな技術?

ブラウザから機械語(アセンブリ)を実行できるようにする技術です。

Emscriptenというコンパイラで、CやC++から吐き出されるLLVMビットコードを.wasmバイナリに変換し、javascriptでアクセスする、、、感じです。

javascriptが年々活躍の場を広げ、処理速度が求められるようになった結果、より高速に処理を行えるようにするために、あらかじめビルドしたバイナリを呼び出せるようにした実験的な試みです。


以前社内でService Workerのネタで勉強会をした時にも話をしましたが、
インターネットを利用する環境は言うまでもなく変化していて、通信の安定してるPCより、移動中に使われるスマートフォンからの利用のほうが多いわけですから、通信が不安定な中でも安定してネットを使えるようにする技術が急ピッチで進化しているわけです。

WebAssemblyで画像処理をやってみる

サーバー側でしか動かせないものを、ブラウザで動かせると聞いて、すぐに試してみたくなったのは、画像処理でした!

昨年から行っている共同研究でも、pythonからOpenCVという画像ライブラリを使って、画像処理をしていてます。

ただ、サーバー側の話しなんで、実用段階になれば、撮影した名刺画像を、どうにかしてサーバに送ってもらわないといけなくて、どうにかこのあたりの仕組みを、ユーザビリティを損なわない形で実現したいと思ってるんですよね。

もしもサーバに送らなくても、ブラウザ側だけで、画像処理をすることができるようになれれば、、、

なんともわくわくする話になります!


ということで、画像処理のライブラリでは有名なImageMagicとか、OpenCVを使えないか調べてみたら、なんとOpenCVは今、WebAssemblyでビルドできるようになってることがわかりました!(けっこう前から、、、笑)

ブラウザで動くOpenCVでできること

サーバー側で動くOpenCVに比べると、まだ機能的に制限されてるところが多そうでしたが、簡単な画像処理は問題なくできました。

チュートリアルを一つづつやってくとわかりやすいですが


例えばこんな感じで、名刺画像の中に、会社のロゴデザインがあるかどうかを検出することができたり

よくわからないラクガキの絵を使ってためしたサンプルだと、いまひとつ効果がわかりづらいですが、、、笑

こんな感じで、画像を、リサイズしたり、回転させたり、いろいろなことができます。

すでにお気づきと思いますが、この記事のメイン画像の通り、顔を認識して、そこを四角でくくることなんかもできるんです。


たまにこういうビジュアル的に楽しい技術は楽しいですよね笑

こんな感じで、ブラウザ上で、OpenCVを使うことで顔認識できることがわかったので、せっかくなので、顔を認識したなにかを作ってみようと思いました

顔認証でログインするものを考えてみる

、、、ということで、あれこれ思案した結果、顔パスでサスケにログインする機能でも作れそうだなと思い、これを作ってみることにしました。


最近、昔ながらのパスワード認証を疑問視する声も大きくなってますしね、新しいログイン方式を検討するのが、無駄にはならないはずです!

顔認証するのに必要なこと

そっくりさんがいた時に、勝手に入られてしまうことほどやばいことはないので、かなりセキュリティのことは意識しないといけないですが、ひとまず細かい話は置いておいて、どうやったらWebサイトで顔認証が実現できるかを、考えてみたいと思います。

まず最低限必要なことをあげてみると、、、

①顔をWebカメラで、ブラウザ上にリアルタイムに映す

②映った映像の中から、顔を切り出してサーバに送信する

③サーバでは、送られてきた画像を、あらかじめ用意してある写真と見比べて、同じ人かどうか判断する

今あげた3つを実現することができれば、何とかなりそうですね。

では順番に検証していきたいと思います

①顔をWebカメラで、ブラウザ上にリアルタイムに映す

html5ではWebAPIが充実しているので、カメラを制御するようなことも、とても簡単な一文で実現できるようになってます。

長らくWebではこういうカメラを制御することが難しいと言われていたのに、今はこんな簡単な一文で制御できるんですよね。
素晴らしい!
            navigator.mediaDevices.getUserMedia({ video: true, audio: false })
.then(function(stream) {
video.srcObject = stream;
video.play();
})
.catch(function(err) {
console.log("An error occured! " + err);
});
getUserMediaってAPIを叩いて、許可を得る感じです。
Webカメラがついてないデバイスならエラーになります

映った映像の中から、顔を切り出してサーバに送信する

これはさきほど紹介したOpenCVが得意とする領域ですね。

ただまだこのWebで使えるOpenCVは、配布されてるものじゃないので、自分で頑張ってビルドする必要があります。
ビルド方法は、こちらあたりがとても参考になりましたが、まぁ、実際誰か動かしている人のサイトみれば、そこでいただくことなんかもできるので、そういう入手方法もあります笑

ちなみにビルドするには、gcc使うわけじゃなくcmakeってのが必要なので、あらかじめこれインストールしておかないとダメでした。

ビルドすると、拡張子がwasmってファイルができるので、これを読み込むような書き方をしていくわけなんですけど、使い方とかはOpenCVのサンプルソースなどご参考ください

faceClassifier = new cv.CascadeClassifier();
faceClassifier.load('haarcascade_frontalface_default.xml');

ちゃんとロードできれば、こういうOpenCVで用意されてる物体検出機能を使って、顔の領域を特定していくことができます。

ちなみにここで読み込んでる、顔のパターンが定義されたxmlファイルのアニメ顔バージョンみたいなものもあるので、そういうの読み込んでくと、アニメキャラの顔認識とかもできるようになります。(ただやり方悪かったのか、このブラウザで動かすやつではビルドしなおさないとダメっぽくて、サクッとつかえませんでした)
で、次に、opencvで、特定した領域の座標情報を元に、画像を切り出して、この画像をバイナリに変換して、サーバにPOSTするようにしました。
 
let xRatio = videoWidth/size.width;
let yRatio = videoHeight/size.height;

for (let i = 0; i < faceVect.size(); i++) {
let face = faceVect.get(i);
faces.push(new cv.Rect(face.x, face.y, face.width, face.height));

if (send) {
let dst = new cv.Mat();
let rect = new cv.Rect(face.x*xRatio, face.y*yRatio, face.width*xRatio, face.height*yRatio);
dst = srcMat.roi(rect);

cv.imshow('canvasOutputSend', dst);
dst.delete();

var canvas = document.getElementById("canvasOutputSend");

var base64 = canvas.toDataURL('image/jpeg');
var barr, bin, idx, len;
bin = atob(base64.split('base64,')[1]);
len = bin.length;
barr = new Uint8Array(len);
idx = 0;
while (idx < len) {
barr[idx] = bin.charCodeAt(idx);
idx++;
}
blob = new Blob([barr], {type: 'image/jpeg'});

var fd = new FormData();
fd.append('file', blob);
fetch('https://localhost.saaske.com/home/face/rcv.php', {
mode: 'cors',
method: 'POST',
body: fd
})
.then(function(response){
return response.json();
}).then(function(json){
console.dir(json)
console.log(json.Similarity);
document.getElementById('info').innerHTML = 'Similarity '+json.Similarity+'%';
});

③送られてきた画像データを、あらかじめ用意してある写真とつけあわせして、似てるかどうか判断する

同じ人物かどうかを判断するのを、自力でやろうとするのはこの勉強会の準備期間では難しいので、ここは、世の中にある便利なサービスを使わせていただくことにしました。

IBMのワトソンとか、AWSのrekognitionとか、いろいろと選択肢はあるんですが、普段よく使うAWSでやることにしました。

AWSの「Rekognition」です。

Rekognitionのcompare facesのAPI使うと、二つの画像を比較して、特徴量を検出できます。

マネジメントコンソールだと、こちらです

一度試してもらうとわかりますが、かなりの精度です!
こちら、各言語ごとにSDKも出てるので、システム連携もしやすいです。


やり方はマネジメントコンソールもAPIも同じで、2つ方法があって、直接2つの画像をなげて評価するか、S3に保存した画像を指定して評価するかの二択でした。

今回は一度S3に保存するのが手間だったので、直で画像を送り付ける方式にしてます。

PHPだとこんな感じ

$rekognitionObject = new Aws\Rekognition\RekognitionClient([
'credentials' => array(
'key' => 'awsのアクセスキー',
'secret'=> 'awsのシークレットアクセスキー'
),
'region' => 'us-east-1',
'version' => '2016-06-27'
]);

$image_org = file_get_contents(dirname(__FILE__).'/tmp/upfile_org.jpeg');
$image = file_get_contents(dirname(__FILE__).'/tmp/upfile.jpeg');
$result = $rekognitionObject->compareFaces([
'SimilarityThreshold' => 80,
'SourceImage' => [
'Bytes' => $image_org
],
'TargetImage' => [
'Bytes' => $image
]
]);

あ、ハマったのは、画像の減色処理はしないほうがいいってこと。

色の情報なく、白黒同士の画像を渡したほうが認識率あがるんじゃないかと思って、色を抜いた画像を渡したら、画像として認識できないとエラーになってしまいました。
なので、渡す画像は、切り取ったそのままの画像でOKです。
perlだとこんな感じでいけました

my $similarity;
eval{
use Paws;
use Paws::Credential::Explicit;
use Paws::Rekognition;
use Paws::Rekognition::CompareFaces;
use Paws::Rekognition::Image;
my $rekognition =
Paws->service(
'Rekognition',
region => 'us-east-1',
credentials =>
Paws::Credential::Explicit->new(
access_key => 'awsのアクセスキー',
secret_key => 'awsのシークレットアクセスキー'
)
);
use MIME::Base64;
my $file_org="./tmp/upfile_org.jpeg";

my $file_org_data = &openfile($file_org);
$file_org_data = encode_base64(join("",@$file_org_data));
my $file = "./tmp/upfile.jpeg";
my $file_data = &openfile($file);
$file_data = encode_base64(join("",@$file_data));

my $res = $rekognition->CompareFaces(
SourceImage =>{Bytes=>$file_org_data},
TargetImage =>{Bytes=>$file_data},
SimilarityThreshold => 80
);

foreach my $face (@{ $res->FaceMatches }) {
$similarity = $face->{'Similarity'};
last;
}
};
Perlの場合は正式にSDKとして提供されてるものがないのですが、Pawsは様々なサービスがカバーされていえ、かつ、AWSのブログでも紹介されてたので、Perlで動いてるシステムからAWSへ連携したい時にはおススメです。


そしてこれらSDKの結果としては、シンプルに、顔が一致してるかどうかの判定結果のパラメータのみを返却するようにしました。

Hands-on

見た目ひどいですけど、、、笑
サスケに組み込むとどんな感じになるか、入れ込んでみました。

そっくりさんを担保できないんで、最低限、ログインIDだけは入れてもらってからの認証にしました。

このまま使おうとすると読み込みにすごい時間かかって厳しいですね。
実用化する時には、Service Workerなど使ってストレスレスに利用できるようにしていかないと厳しいかなって印象です。


あと、ちなみにこれ、ログインできてしまうと、何が起こったかわからないので、わかりやすいように、顔認証の結果だけを表示する形にだけして、今回は検証を終えました。

一部顔を隠したり、険しい顔をしてみたりしても、認識率にそう差は生まれませんでしたね。

なかなか良い結果だったと思います!

最後に

ずっと気になってた技術を、あれもこれも沢山触れてやっとスッキリしました!

見ての通り、wasmファイルが重たすぎるので、最初の読み込み時にだいぶ待たされる問題があるので、まだまだ実用化へは通り印象ですが、様々な技術者が関心を寄せるこの技術に、今後も注目していきたいと思っております。



ご静聴ありがとうございました!
記事一覧へ