顔認識技術を用いたAV女優検索

どうもゴンゾーです。

今月は何かと忙しくてあまりAV女優.comの開発に時間を割けなかったのですが、やっと本日新機能「 AV女優.com フェイスマッチ 」をリリースしました!

今回はその新機能を作るに当たって用いた技術などを紹介します。

新機能概要

何ができるかというと 顔認識技術を用いて芸能人や自分に一番似ているAV女優を探せます。 (モザイク部分が気になるあなたは AV女優.com フェイスマッチ からチェック!)

/img/posts/022/img_screen_shot.png

flickrやPicasaから芸能人の画像を検索し、その人に一番似ているAV女優を探すといった感じです。

twitterのプロフィール画像を用いたり、画像URLを直接指定して用いたり、PCから画像をアップロードして用いたりすることもできます。

自分に一番似ているAV女優とか検索してみたらおもしろいかもですw

利用した顔認識API

顔認識の部分は face.com のAPIを利用しました。

他にも

などの導入も検討しました。

しかし今回の機能では、顔が真正面を向いたときの各特徴点(目・鼻・口など)の位置を把握するのが必須だったので、何度左右を向いてるか、何度上下を向いてるか、何度顔が回転しているか、を取得できるface.comを利用しました。

detectFace();は50個もの特徴点を扱っていますし、OpenCVも色々できるようなので、用途によってはこれらのほうがいい場合もあるかと思います。
顔ラボは鼻や口すらも扱っていないので、あまり使えない感じです。

face.comで取得できるパラメータ のうち今回は「width、height、(tag)width、center、eye_left、eye_right、mouth_left、mouth_center、mouth_right、nose、yaw、pitch、roll」を使いました。

「width、height、(tag) width」は顔の大きさを補正するのに、「yaw、pitch、roll」はそれぞれ顔の左右、上下、回転、の角度を補正して正面を向かせるのに使いました(顔を正面向かせるにはデータで足りないのがあるのでちょっと仮定している部分もあります)。

顔の大きさも向きも補正できて、これからどういうアルゴリズムで顔の類似性を求めるか色々検討したのですが、最終的にはシンプルに各特徴点間の距離(目の離れ具合、鼻下の伸び具合、など)を比較して求めることにしました。それらの差が小さいほど類似しているといった感じに。

AV女優.com フェイスマッチ を見てもらえばわかるのですが、類似度何パーセントとかも表示しています。ここは距離の差の逆数とる形で計算しています。

ちなみにface.comで取得できるパラメータには「ear_left、ear_right、chin」などはないのですが、face.comのレスポンスフィールドには「ear_left、ear_right、chin」というのが返ってきます(全てnullですが)。これは今後追加予定(?)とかそういうことかもしれませんね。

最後にどうでもいいことですが、私ゴンゾーに一番似ているAV女優は「愛川香織」って人らしいですw

/img/posts/023/img_similar_gonzoo.jpg

外部API連携

どうも、すのまるです。

ここからは私が、 AV女優.com フェイスマッチ で使った 外部API を解説します。
画像系のサービスを作る際に、参考にしてください。

AV女優.com フェイスマッチ を作るために、次のAPIを使っています。

それぞれのAPIの使い方を説明します。

ウェブ上の画像をかっさらう

まずは、顔検索するために、ウェブ上の画像をかっさらいます。
簡単に検索でき、画像量が多いのはやはり、この 2サービス です。

flickr
Yahoo!が運営する写真サービスです。最近、リアルタイムの画像共有 (http://www.lifehacker.jp/2011/09/110929flickrandroid.html) に対応しましたね。
APIを使って、自分のアカウントに対し、色々と操作できます。
Picasa
Googleが運営する写真サービスです。
Google+との連携が強化され、使いやすいサービスになっていきそうです。

この2サービスはAPIが公開されており、様々な操作を行えます。
基本的な用途は、自分の画像をアップロードしたり、タグ付したりするものです。
しかし、ウェブに公開された画像も串刺しで検索できます。
つまり、これらのAPIを使えば、 Google画像検索のようなサービス が簡単に作れます。

この他に、自分の画像を認識するために、次のAPIとライブラリを使っています。

これらのAPIとライブラリの使い方を紹介していきます。

flickrのAPIを使う

flickrで画像を検索する方法です。
flickr APIでは受け取るデータ形式を以下から選べます。

  • XML
  • XML-RPC
  • SOAP
  • JSON
  • PHP

検索に使うのは flickr.photos.search です。
このメソッドを使うには、以下のURLにアクセスします。

http://api.flickr.com/services/rest/?api_key=xxxxx&method=flickr.photos.search&text=aoisora&per_page=50&format=json

レスポンス:

{
    "photos": {
        "page":1,
        "pages":5,
        "perpage":50,
        "total":"242",
        "photo":[
            {id: "4126335664", owner: "81217355@N00", secret: "78d286653a",
             server: "2536", farm: 3, title: "Sora Aoi"},
            {id: "4919839202", owner: "49590184@N06", secret: "203d917508",
             server: "4140", farm: 5, title: "SoraAoi 600x600"},
            // ...
        ]
    },
    "stat":"ok"
}

これはJSONの例です。
クライアントサイドでAPIを呼び出すなら、JSONが扱いやすいでしょう。

flickrはURLを自前で組み立てる必要があります。

http://farm[farm].static.flickr.com/[server]/[id]_[secret].jpg

これらを踏まえ、jQueryで書くとこうなります:

var flickrKey = 'xxxxx';
function requestFlickr(q) {
    var flickrQuery = {
            method: 'flickr.photos.search',
            api_key: flickrKey,
            text: q,
            per_page: 50,
            format: 'json',
        },
        url = 'http://api.flickr.com/services/rest/?jsoncallback=?',
        $container = $('#result');
    $.getJSON(url, flickrQuery, function(response) {
        if (response.stat !== 'ok') {
            return;
        }
        var photos = response.photos.photo;
        for (var i = 0, len = photos.length; i < len; i ++) {
            var url = 'http://farm' + photos[i].farm + '.static.flickr.com/' +
                      photos[i].server + '/' +
                      photos[i].id + '_' + photos[i].secret + '.jpg',
                $img = $('<img />').attr('src', url);
            $container.append($img);
        }
    });
}

簡単ですね。
ちょっと特殊なのは、flickrで jsoncallback が指定でき、それを取得するのに $.getJSON() を使うことぐらいでしょうか。

他の機能に関しては、以下のドキュメントを参考にしてください。

flickr API documentation
flickr APIドキュメントです。
メソッド名や使い方がわかりやすいので、英語が苦手な方も簡単にやりたいことを探せるはずです。

PicasaのAPIを使う

PicasaのAPIはflickrよりも素直です。
キーによる認証も必要ありません。

http://picasaweb.google.com/data/feed/api/all?q=aoisora&max-results=50

レスポンス:

<feed xmlns='http://www.w3.org/2005/Atom'
      xmlns:exif='http://schemas.google.com/photos/exif/2007'
      xmlns:gphoto='http://schemas.google.com/photos/2007'
      xmlns:media='http://search.yahoo.com/mrss/'
      xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/'
      xmlns:gml='http://www.opengis.net/gml'
      xmlns:georss='http://www.georss.org/georss'>
    <id>http://picasaweb.google.com/data/feed/api/all</id>
    <updated>2011-09-21T07:05:28.435Z</updated>
    <title type='text'>Search Results</title>
    <!-- ... -->
    <generator version='1.00' uri='http://picasaweb.google.com/'>
        Picasaweb
    </generator>
    <openSearch:totalResults>701</openSearch:totalResults>
    <openSearch:startIndex>1</openSearch:startIndex>
    <openSearch:itemsPerPage>50</openSearch:itemsPerPage>
    <entry>
        <id>http://picasaweb.google.com/data/entry/api/user/
            andyhouse1992/albumid/5487626511829759425/photoid/5487627435299441426
        </id>
        <published>2010-06-28T01:17:07.000Z</published>
        <updated>2011-09-19T06:59:40.742Z</updated>
        <category scheme='http://schemas.google.com/g/2005#kind'
                  term='http://schemas.google.com/photos/2007#photo'/>
        <title type='text'>Aoi Sora sexy 51.jpg</title>
        <summary type='text'>hot av idol Aoi Sora</summary>
        <content type='image/jpeg'
                 src='http://lh5.ggpht.com/-1wcMm8yVzdI/TCf4E-S2CxI/AAAAAAAAA3E/
                      8tl_KFhQMk0/Aoi%252520Sora%252520sexy%25252051.jpg'/>
        <!-- ... -->
    </entry>
</feed>

しかし、レスポンスが Atom しかないため、クライアントサイドで扱うのが若干、面倒です。
そのため、 AV女優.com フェイスマッチ ではクライアントサイドではなく、 サーバサイド で処理しています。
その用途も、画像をキャッシュする場合に限定しています。
flickrだけでも、それなりに画像が出てきますからね。

この他のメソッドに関しては、Picasaのドキュメントを参考にしてください。

Picasa Web Albums Data API Developers Guide
PicasaのAPIドキュメントです。
いつも通りの Google流ドキュメント で、Google系列のAPIを使い慣れた人にはわかりやすいはずです。

Tiwtterのプロフィール画像APIを使う

これは地味に便利なAPIです:

<img src="http://api.twitter.com/1/users/profile_image/sunomaru?size=bigger" />

レスポンス:

http://api.twitter.com/1/users/profile_image/sunomaru?size=bigger

これまで、私はTiwtterのプロフィール画像は Auth認証後 に取得していました。
しかし、このAPIでは、screen_nameだけわかっていれば、プロフィール画像を表示できます。

AV女優.com フェイスマッチ では、このAPIでTwitterのプロフィール画像検索を実装しています。

GET users/profile_image/:screen_name
先ほどの例ではsizeをbiggerとしていますが、サイズは3種類から選べます。
そのパラメータとAPIの挙動の説明がここにあります。

Ajax Uploadを使う

Ajax Upload
このプラグインを使うと、Google+の画像アップロードのように、 ドラッグアンドドロップファイラーによる画像アップロード を実現出来ます。
他のライブラリの依存もなく、扱いやすいです。

Ajax Uploadは、次の4つのクラスから成り立っています。

qqFileUploader
上記サイトのデモを簡単に実現できるクラスです。
お手軽な分、アップロード後にボックスが隠れてしまったり、ファイル一覧が強制的に出たりと、若干使いにくいクラスです。
qqFileUploaderBasic
上記クラスの親クラスで、これを使うとアップロードの挙動を調整できます。
qqUploadButton
ファイラーによるアップロードボタンを生成するクラスです。
qqFileUploadZone
ドラッグアンドドロップによるアップロードエリアを生成するクラスです。

AV女優.com フェイスマッチ では、qqFileUploadBasicを直接指定し、機能を実現しています。

詳しく説明したいのですが、長くなってしまうのでここでは省略します。
要望があれば、別にブログ記事を書きます。

本来、こちらのドキュメントを...と書きたいところなのですが、あまり充実していません。
使う場合はソースコードを追うのが一番ですね。

face.comを活用する

face.com API のAPIを使うと、以下のことができます。

顔の検出 (faces.detect)
顔を構成する要素の座標を検出します。目や口、鼻の座標を返してくれます。
その他に、メガネの有無や表情まで検出してくれます。
顔の学習 (tags.save, faces.train)
検出した画像をface.comに学習させます。
自分で名前空間を作り、そこに学習結果を記録していきます。
顔の識別 (faces.recognize)
学習した顔から、人物を特定します。

AV女優.com フェイスマッチ で使用しているのは、face.comの制限により faces.detect のみです。
その制限も含め、face.comの機能をここで紹介します [1]

検出から識別までの流れ

顔を検出、学習、識別の流れは次のようになります。

/img/posts/023/face-com-process.png

このように、face.comでは学習させる名前空間が非常に重要です。

faces.detect

detectの叩き方は簡単です。

http://api.face.com/faces/detect.json?api_key=xxxxx&api_secret=xxxxx&urls=http://farm3.static.flickr.com/2566/3896283279_0209be7a67.jpg

レスポンス:

{
    photos: [
        {
            url: "http://farm3.static.flickr.com/2566/3896283279_0209be7a67.jpg",
            pid: "F@53f98f0f3ffeef58413190e657e297ff_4b4b4c6d54c37",
            width: 333,
            height: 500,
            tags: [
                {
                    tid: "TEMP_F@53f98f0f3ffeef58413190e657e297ff_4b4b4c6d54c37_51.05_60.40_0",
                    threshold: null,
                    uids: [ ],
                    label: "",
                    confirmed: false,
                    manual: false,
                    width: 11.71,
                    height: 7.8,
                    center: {
                        x: 51.05,
                        y: 60.4
                    },
                    eye_left: {
                        x: 49.18,
                        y: 59.7
                    },
                    eye_right: {
                        x: 52.5,
                        y: 59.46
                    },
                    mouth_left: {
                        x: 50.16,
                        y: 62.03
                    },
                    mouth_center: {
                        x: 51.61,
                        y: 61.86
                    },
                    mouth_right: {
                        x: 52.65,
                        y: 61.86
                    },
                    nose: {
                        x: 51.55,
                        y: 60.53
                    },
                    yaw: 26.44,
                    roll: -6.19,
                    pitch: 19.47,
                    attributes: {
                        gender: {
                            value: "male",
                            confidence: 95
                        },
                        glasses: {
                            value: "true",
                            confidence: 95
                        },
                        smiling: {
                            value: "false",
                            confidence: 95
                        }
                    }
                },
                {
                    tid: "TEMP_F@53f98f0f3ffeef58413190e657e297ff_4b4b4c6d54c37_54.35_34.40_0",
                    threshold: null,
                    uids: [ ],
                    label: "",
                    confirmed: false,
                    manual: false,
                    width: 37.24,
                    height: 24.8,
                    center: {
                        x: 54.35,
                        y: 34.4
                    },
                    eye_left: {
                        x: 48.18,
                        y: 32.16
                    },
                    eye_right: {
                        x: 58.85,
                        y: 31.65
                    },
                    mouth_left: {
                        x: 51.46,
                        y: 40.07
                    },
                    mouth_center: {
                        x: 55.55,
                        y: 39.56
                    },
                    mouth_right: {
                        x: 58.65,
                        y: 39.64
                    },
                    nose: {
                        x: 55.92,
                        y: 34.96
                    },
                    yaw: 30.97,
                    roll: -4.1,
                    pitch: 21.04,
                    attributes: {
                        gender: {
                            value: "male",
                            confidence: 95
                        },
                        glasses: {
                            value: "true",
                            confidence: 95
                        },
                        smiling: {
                            value: "false",
                            confidence: 95
                        }
                    }
                }
            ]
        }
    ],
    status: "success",
    usage: {
        used: 1,
        remaining: 199,
        limit: 200,
        reset_time_text: "Wed, 03 Mar 2010 13:46:40 +0000",
        reset_time: "1267624000"
    }
}

これだけで簡単に情報を取得できます。

tags.saveとfaces.train

タグの関連付けにはfaces.detectで取得した タグID を利用します。
先ほど検出した顔にはタグID TEMP_F@53f98f0f3ffeef58413190e657e297ff_4b4b4c6d54c37_51.05_60.40_0 が付いています。
この顔がユーザ sunomaru であることを、face.com上で関連付けるには次のURLを叩きます。

http://api.face.com/tags/save.json?api_key=xxxxx&api_secret=xxxxx&uid=sunomaru@kobo-chan.av-jyo.com&tids=TEMP_F@53f98f0f3ffeef58413190e657e297ff_4b4b4c6d54c37_51.05_60.40_0

レスポンス:

{
    saved_tags: [
            {
                detected_tid: "TEMP_F@53f98f0f3ffeef58413190e657e297ff_4b4b4c6d54c37_51.05_60.40_0"
                tid: "297ff_4b4b4c6d54c37"
            }
    ]
    message: "Tag saved with uid: sunomaru@kobo-chan.av-jyo.com, label: "
    status: "success"
}

さらに、face.comが自動で顔を識別できるように学習させます。

http://api.face.com/faces/train.json?api_key=xxxxx&api_secret=xxxxx&uids=sunomaru@kobo-chan.av-jyo.com&callback_url=http://av-jyo.com/callback-url

レスポンス:

{
    no_training_set: [
        {
            uid: "sunomaru@kobo-chan.av-jyo.com",
            training_set_size: 0,
            last_trained: 0,
            training_in_progress: false
        },
    ],
    unchanged: [
        {
            uid: "konzoo2424@kobo-chan.av-jyo.com",
            training_set_size: 18,
            last_trained: 1267098123,
            training_in_progress: false
        },
    ],
    updated: [],
    status: "success",
    usage: {
        remaining: 200,
        limit: 200,
        reset_time_text: "Thu, 01 Jan 1970 00:00:00 +0000",
        reset_time: null
    }
}

これを繰り返し、 名前空間kobo-chan.av-jyo.com にユーザ sunomaru を学習させていきます。

faces.recognize

学習が終わったら、やっと顔を識別できます。
顔を識別するには、先ほど指定していた名前空間が必要です。
画像 http://a2.twimg.com/profile_images/1379998546/110603_sunomaru_icon_bigger.png を名前空間 kobo-chan.av-jyo.com から、検出します。

http://api.face.com/faces/recognize.json?api_key=xxxxx&api_secret=xxxxx&urls=http://a2.twimg.com/profile_images/1379998546/110603_sunomaru_icon_bigger.png&uids=sunomaru@kobo-chan.av-jyo.com

レスポンス:

{
    no_training_set: []
    photos: [
        {
            url: "http://a2.twimg.com/profile_images/1379998546/110603_sunomaru_icon_bigger.png",
            pid: "F@2f9d1c8f44d03e82367d7d8737556342_4b4b4c6d54c37",
            width: 73,
            height: 73,
            tags: [
                {
                    tid: "TEMP_F@2f9d1c8f44d03e82367d7d8737556342_4b4b4c6d54c37_44.53_42.06_2",
                    threshold: 60,
                    uids: [
                        {
                            uid: "sunomaru@kobo-chan.av-jyo.com",
                            confidence: 97
                        },
                        {
                            uid: "konzoo2424@kobo-chan.av-jyo.com",
                            confidence: 23
                        },
                        // ...
                    ],
                    label: "",
                    confirmed: false,
                    manual: false,
                    width: 6.84,
                    height: 10.29,
                    center: {
                        x: 44.53,
                        y: 42.06
                    },
                    eye_left: {
                        x: 43.38,
                        y: 40.84
                    },
                    eye_right: {
                        x: 45.77,
                        y: 40.97
                    },
                    mouth_left: {
                        x: 43.42,
                        y: 44.74
                    },
                    mouth_center: {
                        x: 44.74,
                        y: 45.23
                    },
                    mouth_right: {
                        x: 45.78,
                        y: 44.7
                    },
                    nose: {
                        x: 45.15,
                        y: 43.42
                    },
                    yaw: 42.48,
                    roll: 2.07,
                    pitch: -3.71,
                    attributes: {
                        gender: {
                            value: "male",
                            confidence: 34
                        },
                        glasses: {
                            value: "true",
                            confidence: 95
                        },
                        smiling: {
                            value: "false",
                            confidence: 61
                        }
                    }
                }
            ]
        }
    ],
    status: "success",
    usage: {
        used: 1,
        remaining: 199,
        limit: 200,
        reset_time_text: "Wed, 03 Mar 2010 13:46:40 +0000",
        reset_time: "1267624000"
    }
}

これで画像 http://a2.twimg.com/profile_images/1379998546/110603_sunomaru_icon_bigger.png がsunomaruであることがわかります。

face.comの技術はFacebookでも利用されているため、しっかり学習させれば検出率はかなり高いです。
AV女優.com は画像がAV女優に関連付けられており、学習用画像には事欠きません。
あとは face.comの制限 さえなければ、AV女優顔データベースをface.com上に展開できるのですが。

[1] ちなみに、ここでは出して良い画像がなかったため、私の顔アイコンを例にサンプルを書きました。見ての通り、私はアニメ顔なので実際には顔を検出出来ません...。

face.comの制限

上記で紹介した通り、 名前空間 に対して顔を学習させます。
顔を識別するサービスを作る場合、この名前空間が非常に重要です。
例えば、 ユーザ登録して、そのユーザを不特定多数の写真から見つけるサービス など。

しかし、この名前空間に登録できるユーザ数に制限があります。

Terms :: Rate Limiting
face.comの制限に関するドキュメントです。
名前空間に登録できるユーザ数は1,000人まで、顔の検出は1時間に5,000画像まで、とここにあります。

顔の検出制限は、1秒毎に1回APIを叩いても制限に達しないので、さほど問題ないのです。
問題はユーザ数ですね。
小さいサービスでも1,000ユーザはすぐに到達してしまいます。

AV女優.com フェイスマッチ でも、この faces.recognize を利用しようと思ったのですが、このユーザ数制限に引っかかりました。
現在、 AV女優.com に登録されている女優は6,000を超えます。

ただし、初期のTwitter APIのように ホワイトリスト制度 があるようです。
ウチ以外のまともなサービス ならば、ホワイトリストを検討するのが良いと思います。

なんだか、 MA7 に出すのか、ってぐらいマッシュアップしてしまいました。
外部APIを使うと、色々と大変なのですが、それはまた今度まとめます。

AV女優.com フェイスマッチをよろしくお願いします!

ちょっと一言

gonzoo48

@gonzoo48

次は新たにアダルトサイトを作る予定です。そちらも完成したら当ブログでご報告しますのでお楽しみに!