ブラウザだけで顔画像認識と合成(ドラゲナイ風)してみた。

概要
---
ブラウザだけで顔画像認識と画像合成を行うことを題材に勉強してみました。
結果をサンプルアプリとしてまとめました。

ドラゲナイザー(顔画像を認識して、"ドラゲナイ"風画像にします)という名前にしました。
https://hrkt.github.io/dragenizer/
![dragenai.png](https://qiita-image-store.s3.amazonaws.com/0/65002/ad9dae56-56a5-4bd1-3dd8-69dc9dbc5080.png)

背景
---
HTML5でcanvasやローカルファイル入出力が利用可能になったこと、JavaScriptのライブラリが増えたことにより、ブラウザ単体でもいろいろなことができるようになりました。
2012年頃から、顔画像認識ネタがネット上に見られるようになっている中、2015年1月くらいに、セカイノオワリの"ドラゴンナイト"風にするための画像があるのをみて、勉強がてら書いてみたものです。

ポイント
-------
こういったアプリを作るときの技術要素は、以下です。
* 画像認識。
* ローカルファイルからの画像入力
* 画像ファイル保存

技術要素がある上で、今回のチューニングポイントは以下です。
* 顔画像と合成用画像のマッチング
* クロスドメイン対応

では、やってみた上でのポイントを順に書きます。

画像認識
-------
画像認識といえば、フリーで最も使いやすいライブラリとしてOpenCVが挙げられます。
今回は、ブラウザだけで完結したいので、JavaScriptで動作可能なものを探していたところ、次の記事がありました。
http://tokkono.cute.coocan.jp/blog/slow/index.php/web-technology/face-detection-using-ccvjs/

参照されていたライブラリは下記のサイトにあります。
https://github.com/liuliu/ccv

このライブラリは、画像を入力として、画像中で顔に見えそうな領域を検出し、結果を複数個返してくれるものです。この過程をJavaScriptで実装可能です。
使ってみたところ、横幅1000px程度の画像において、MacBookPro(Core i7@2.0GHz)でもiPad(Retina mini)でも、高々2-3秒のオーダーで検出過程が終わったので、速度の面では、十分使えるものだと考えました。

ローカルファイルからの画像入力
-------------------------
HTML5では、File APIがあるため、これを使います。

```
<input type="file">
```

要素を使うと、MacやPCに加え、iOSやAndroidでも動作します。
(OSx 10.10上のSafariとChromeとFireFox, iOS7のiPad、LolliPopのNexus7(2012)で確認)。

さらに、MacやPCを使っている場合にはファインダーやエクスプローラからDrag & DropできるAPIもあるので、これも適用します。

今回のソースでは、次のようにしてjQueryでバインドしています。

```
    //バインド
    $('#d-and-d').on('drop', dropped);

function dropped(evt)
{
    evt.preventDefault();

    var file = evt.originalEvent.dataTransfer.files[0];
    
    // ファイル内容の読み取り
    var reader = new FileReader();
    reader.onloadend = function(){

```

画像ファイル保存
-------------
画像処理をするにはHTML5のcanvasを使いますが、こちらを保存するには、2015年2月時点では複数ブラウザにまたがって同じ方法で処理するのは難しそうでした。
そこで、ライブラリを探してみたところ、今回の目的にぴったりだったものがあったので、これらを使いました。

https://github.com/eligrey/canvas-toBlob.js/
このライブラリは、canvasからHTML5のblobデータに変換するためのライブラリとして使用します。2015年2月時点でFirefoxが持っているメソッドをFirefox以外にpolifillするために使いました。

https://github.com/eligrey/FileSaver.js
このライブラリは、blobをローカルに保存するために使用します。複数ブラウザ対応で、ローカルに保存するためのメソッドを提供してくれます。
今回のアプリでは、下記のコードで保存しています。

```
    //ダウンロードボタンが押されたときの処理
    $('#downloadButton').on('click', function() {
	canvas.toBlob(function(blob) {
	    saveAs(blob, "dragonized.png");
	});
    });
```

顔画像と合成用画像のマッチング
-------------------------
技術要素が揃ったところで、あとは調整です。
今回のアプリでは、ポイントが2つあります。

### ポイント1
顔画像検出のライブラリから複数要素が出てきた場合の対応については、できれば1つだけに絞って対応したいものです。
このため、今回は"画像の中心に一番近いものを採用する"ことにしています。
下記のようなメソッドにまとめてみましたが、TODOにも書いた通り、アルゴリズムとして「画像の中で最も大きい領域として検出されたもの」にしてもよいかもしれません。

```
/**
 * 顔領域として複数候補がある場合、中心に近そうなものを探す。
 * @param {Object} img 画像
 * @param {number[]} comp 顔領域候補の配列
 * @param {Object} ctx canvascontext。デバッグ用に線を引くのに使う。
*/
function selectTarget(img, comp, ctx) {
    //TODO:大きさが大きなものを選んでもいいかもしれない。

 ```

### ポイント2
今回のアプリでは、人物の顔として検出された部分と、重ね合わせ用の画像（ドラゲナイの無線機、および手）の位置をなるべく合わせたいものです。
このために、以下のステップを踏んでいます。
- 重ね合わせ用画像中の手の位置を決めておく
- 顔画像のサイズを元に、重ね合わせ用画像中の手と、顔画像とをだいたい同じサイズになるように画像をスケールさせる
- 必要なだけ画像をずらし、重ね合わせ表示する。

クロスドメイン対応
---------------
ブラウザは、基本的に、特定のドメインのサイトでは、特定のドメインのサイト内のリソースのみアクセスできるようにし、安全性を高める、という原則で作られています。
このため、今回のアプリでは、他のドメインにある画像リソースをブラウザ単体で使用することには制限がありました。
このため、以下の対応をしています。
1.ユーザが使用する画像は、ローカルのファイルに限定する。
2.重ね合わせ用の画像は、アプリが最初からデータとして保持することにする。

上記2.については、Imageのsrcを、base64されたデータとして持つことにより対応しています。

まとめ
-----
ブラウザ単体で画像認識を行うための方法について勉強し、結果をアプリとして作りました。
顔の認識率はあまり高くありませんが、ぜひ遊んでみてください。