face.comのdetect APIを使ったら目からビームが捗った話

目からビームな画像を生成するWebサービスを作った - すぎゃーんメモの続き。


6月27日発売、ももいろクローバーZの「Z女戦争」。ついにMVも公開されました。


自分の中では最高にツボっていて、どストライクです。これはヤバい。大好きです。
発売まであと2週間、超楽しみですね。

Z女戦争

Z女戦争


で、先日「目からビーム画像ジェネレーター」を作ってみたのだけど、公開したところ最初に使っていた「顔ラボ 顔検出WebAPI」があっという間に利用可能回数制限に引っかかってしまい、使えなくなってしまった。
何か他に使えるものあれば入れ替えよう、と探していたところface.comAPIを教えていただいた。

http://developers.face.com/

ここでdeveloper登録してAPIを利用することで、同じように画像から顔検出ができるようだ。その他にも学習させたり顔を識別させたりも出来るのかな?かなり高機能な模様。API利用回数制限も、1時間あたり5000回となっていて、簡単な個人サービスレベルなら十分な回数。
face.com APIを利用するためのRubyライブラリもちゃんとGemsにある。
face | RubyGems.org | your community gem host
早速faces.detect APIを使ってみたところ、非常に様々なパラメータが取れて、とても良い。
http://developers.face.com/docs/api/return-values/
顔の位置、幅、高さはもちろん、各部位(両目、両耳、鼻、口など)の位置、さらには性別判定、眼鏡をかけているか、笑っているか否かといった表情についてまで推定した結果を返してくれる。
そして目からビームにおいて非常に重要なパラメータとして顔の向きの角度を表すものが3つ含まれているのがとても有り難かった。

yaw	float	yaw (facing sideways) angle value as -90 to 90
pitch	float	pitch (up or down) angle value as -90 to 90
roll	float	roll (face rotation) angle value as -90 to 90

どうやら、yawが左右の向き、pitchが上下の向き、rollが顔自体の傾きを表しているらしい。図にすると以下のようなかんじ。


ということで、yawpitchから「右上/左上/左下/右下 どっちの方向にビームを出すべきか」はだいたい決定できる。rollで傾けた角度を基準に、yawが大きければ横方向寄りに、小さければ縦方向寄りに。
せっかくなので両目並行に放つのではなく少し拡散させよう、ということも考え。上記で決めた角度を中心に、最大40°ずつ開くように調整した。yawpitchも小さくて正面向きに近い場合は大きく開き、逆にそれが大きいとき、すなわち上下向きや左右向きに偏っている場合は開きを少なく。

現状での描画部分のコードは以下のようなかんじ。

def draw_beam (img, tags)
  tags.reverse.each do |tag|
    return unless tag['eye_left'] && tag['eye_right']
    logger.info tag
    d = Magick::Draw.new
    reye = [tag['eye_right']['x'] * img.columns / 100.0, tag['eye_right']['y'] * img.rows / 100.0]
    leye = [tag['eye_left']['x']  * img.columns / 100.0, tag['eye_left']['y']  * img.rows / 100.0]
    line = Proc.new{ |eye, angle, pitch|
      edge = Proc.new{ |s|
        if s >= 0
          [0, eye[1] - (pitch >= 0 ? 1 : -1) * s * eye[0]]
        else
          [img.columns, eye[1] + (pitch >= 0 ? 1 : -1) * s * (img.columns - eye[0])]
        end
      }
      slope0 = Math::tan((90 - angle + 2.5) / 180 * Math::PI)
      slope1 = Math::tan((90 - angle - 2.5) / 180 * Math::PI)
      edge0 = edge.call(slope0)
      edge1 = edge.call(slope1)
      d.polygon(eye[0], eye[1], edge0[0], edge0[1], edge1[0], edge1[1])
    }
    cangle = tag['roll'] + (tag['pitch'] > 0 ? 1 : -1) * tag['yaw']
    langle = cangle + 30 * (1 - tag['pitch'].abs / 90.0) * (1 - tag['yaw'].abs / 90.0)
    rangle = cangle - 30 * (1 - tag['pitch'].abs / 90.0) * (1 - tag['yaw'].abs / 90.0)
    width = 1 + (tag['width'] + tag['height']) / 20.0
    # color
    d.stroke(['#FF0000', '#00FF00', '#FFFF00', '#FF00FF', '#800080'][rand 5])
    # light source
    d.stroke_width(width - 1)
    d.stroke_opacity(0.1)
    d.fill('white')
    d.fill_opacity(0.2)
    d.ellipse(leye[0], leye[1], width, width, 0, 360)
    d.ellipse(reye[0], reye[1], width, width, 0, 360)
    # beam
    d.stroke_width(width + 2)
    d.stroke_opacity(0.4)
    d.fill_opacity(0.6)
    line.call(leye, langle, tag['pitch'])
    line.call(reye, rangle, tag['pitch'])
    d.draw(img)
  end
end

https://github.com/sugyan/eyebeam-herokuapp/blob/357a6ac58d4857755c4f264571d33ebb87e7a236/app.rb#L140

以前は目から直線を引いていただけだったけど、今度は少し広がるように5°ほどの角度をもつpolygonを描画するように変更した。角度さえ決めてしまえばその傾きから切片を求めてどの座標に結べば良いか計算できる。あとは適度に透明度を設定して。
結果、こんなかんじに仕上がった。

複数の顔にも対応して複数本のビームも出すようにしている。顔の向きに応じてそれぞれ角度が調整されているのを確認できるはず。

まぁ精度はそこまで高くなくてたまに変な方向に出ちゃったりはするけど、「そこそこ自然に見えるレベル」にはなっているのではないかと。


細かいバグなどまだ残っているのでこれから修正する予定。
あとやっぱりmemcacheだけで作ってて時間経つと消えてしまう仕様だと勿体無い気がしてきたので、永続化する仕組みも入れようかと検討中…