全自動水玉コラ生成マシーン Web版

全自動水玉コラ生成マシーン - onk.ninja が面白いなーと思って、

ていう流れになって。
ちょうどつい先日 OpenCVを使ったWebアプリについての知見を得ていたところだったので、やってみようと。

微妙に違うところもあるけど だいたいそのまま移植してみたつもり。

しかし実際試してみるとなかなか上手くいく例はない… もうちょっと色んな改良が必要そうです。Webでやるには処理速度的に今くらいのが限界っぽい感じもするけど。。

El Capitanにしたらzsh上でのPATHが上書きされた

先日ようやく El Capitanに上げたのだけど、そうしたらtmux上でzshログインした際にPATHがおかしくなる、という問題が起きて。
どうやらEl Capitanでは/etc/zprofileというのが作られていて、こいつが

# system-wide environment settings for zsh(1)
if [ -x /usr/libexec/path_helper ]; then
        eval `/usr/libexec/path_helper -s`
fi

となっていて、PATHを書き換えてしまうようで。
tmuxで新しくwindowを開いたりする際にこいつが呼ばれてしまうのが原因だったらしい。
最初はそいつをrenameして対応したけど、

ということだったのでsetopt no_global_rcsで解決しました。ありがとうございます!

画像の検出まわりの資料など

メモ。

Heroku + OpenCVで簡易顔検出API

Docker Image of Python with OpenCV 3.0 for Heroku - すぎゃーんメモ の続き的なかんじで。

OpenCVでよく使われるObject Detection機能で、画像から顔を検出するAPIを作ってみた。


Heroku app
https://face-detector.herokuapp.com/
Github repository
https://github.com/sugyan/face-detector

顔検出 基礎

一番簡単なオブジェクト検出の手法が、Haar-like特徴に基づくカスケード型分類器(Haar Feature-based Cascade Classifiers)というのを用いるやつ。

OpenCVには顔や目などに関して学習済みのデータが同梱されているので、これを使うことで簡単に画像から顔を検出できる。
ここではhaarcascade_frontalface_alt2.xmlというのを使う。他との違いはあんまりよく分かってない。

import cv2
from os import path

# cascadeファイルをロードする
cascades_dir = path.normpath(path.join(cv2.__file__, '..', '..', '..', '..', 'share', 'OpenCV', 'haarcascades'))
cascade_f = cv2.CascadeClassifier(path.join(cascades_dir, 'haarcascade_frontalface_alt2.xml'))

# 画像を読み込む
img = cv2.imread('sugyan.jpg')
# グレイスケールに変換
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 検出して四角で囲む
faces = cascade_f.detectMultiScale(gray)
for (x, y, w, h) in faces:
        cv2.rectangle(gray, (x, y), (x + w, y + h), (0, 0, 0), 2)

こんな感じで、簡単に顔の領域を検出してマーキングすることができる。

before:

after:

傾き(回転)に対応する

ところで最近は色んなアイドルさんが自撮り画像を上げてくれてたりする。
最近とても気になっているのが 虹のコンキスタドール の、「ののた」こと奥村野乃花ちゃん。

ののた可愛い。


それはともかく、、こういった自撮り画像、結構な角度で傾いているものだったりする。
OpenCVでのHaar Feature-based Cascade Classifiersによる顔検出はちょっとでも傾きがあると精度が一気に変わってしまうようで、先述の例のような真正面の顔が期待できない場合はそのままではほぼ使えない。ののたの自撮りは傾いているものが多くて厳しい。

ということで 以下の記事を参考に、元画像を徐々に回転したものを生成して、それぞれを対象に繰り返し検出を試みる。

斜辺サイズの枠を用意して、中央に元画像を配置してそれを中心に回転行列をかけて画像を変換。

import math
import numpy

...

rows, cols, colors = img.shape
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# 元画像の斜辺サイズの枠を作る(0で初期化)
hypot = int(math.hypot(rows, cols))
frame = numpy.zeros((hypot, hypot), numpy.uint8)
frame[(hypot - rows) * 0.5:(hypot + rows) * 0.5, (hypot - cols) * 0.5:(hypot + cols) * 0.5] = gray
# 各loopで違う角度の回転行列をかけた結果のものに対して検出を試みる
for deg in range(-50, 51, 5):
    M = cv2.getRotationMatrix2D((hypot * 0.5, hypot * 0.5), -deg, 1.0)
    rotated = cv2.warpAffine(frame, M, (hypot, hypot))
    faces = cascade_f.detectMultiScale(rotated)
    for (x, y, w, h) in faces:
        cv2.rectangle(rotated, (x, y), (x + w, y + h), (0, 0, 0), 2)

とりあえず5°ずつのstepでやってみた結果が以下。

40°まで回転させたところでようやく顔を検出している。35°のところでは全然関係ない箇所が誤検出されている。
ちなみにこのへんの精度はある程度detectMultiScaleの引数を変更することで調整することもできる。

例えば第2引数のscaleFactorをデフォルトの1.1から1.05にすると

のようになり、35°〜50°でも顔が検出できるようになるのだけど、10°, 15°, 25°など誤検出が増える。
そこで第3引数のminNeighborsをデフォルトの3から4に増やすと

となり、誤検出が消える。

ただこのへんはケースにもよるので「どの値が一番良い」みたいなものは一概には言えなそう。あと処理速度にも影響があってscaleFactorを小さくすると処理量が増大する。


顔が検出できたら さらにその検出された領域で両目を検出してみる。これはhaarcascade_eye.xmlという別のファイルを使うだけで、同じ要領でできる。デフォルトの引数のままで35°付近の結果を試してみると

cascade_e = cv2.CascadeClassifier(path.join(cascades_dir, 'haarcascade_eye.xml'))

...

for deg in range(30, 50):
    M = cv2.getRotationMatrix2D((hypot * 0.5, hypot * 0.5), -deg, 1.0)
    rotated = cv2.warpAffine(frame, M, (hypot, hypot))
    faces = cascade_f.detectMultiScale(rotated)
    if len(faces) > 0:
        (x, y, w, h) = faces[0]
        # 検出された顔の領域だけの画像を取得し、目の検出を試みる
        roi = rotated[y:y + h, x:x + w]
        eyes = cascade_e.detectMultiScale(roi)
        for (ex, ey, ew, eh) in eyes:
            cv2.rectangle(roi, (ex, ey), (ex + ew, ey + eh), (0, 0, 0), 2)


のようになる。33°, 35°はそもそも顔領域が変な位置で検出されている場合で、当然ながら目は見つからない。その他のものでは 正しく目の部分を検出できている場合もあれば全然ちがう部分を検出しまくってグロいことになっている場合もある。ののたゴメン…。

1°の変化もだいぶ結果が違う。。とは言え多くの場合は目を検出できているので、例えば顔の下半分領域で検出されたものは目なはずないので無視する、とかフィルタリングはできると思う。


とりあえず、顔領域から目を2つ検出した場合のみを「正しく顔を検出できた」と見なして、そのときの顔の中心、目の中心の座標を取り 回転行列の逆変換をかけてやれば、元画像における顔や目の中心座標が取得できる。複数の回転角度で取得されて重複している場合は 検出した両目がより水平に近い(つまりatan2(y, x)が最も0に近い)ものを選択する、などするとより良い結果を得られるかな、ということでそうしてみた。

のが今のコード。
https://github.com/sugyan/face-detector/blob/cc22fd576416f79f291674e756cc00e4841289f9/lib/detector.py

数十行くらいでこれくらいのロジックが書けてしまうしpython + cv2便利〜。

JSON APIでHeroku deploy

で、これはただcv2を使っただけのpythonのコードなので、 OpenCV用のdocker image などがあればHerokuに上げてJSON APIとして使ったりすることができる。

https://face-detector.herokuapp.com/ ではそのAPIを使って、入力されたURLの画像における顔の座標を各軸の%座標で取得し、canvasでその結果を描画しているだけ。

精度と速度のトレードオフ

まぁ動かしてみると分かるけどレスポンスはかなり遅い。普通に十数秒かかったりする。
やっぱりfor loopで各角度に回転した画像を生成してそこから顔を検出して…というのを複数回繰り返すのはかなりの負荷で、±50°くらいの範囲で5°ずつで20回くらいやるだけで相当つらい。
かといってstepを10°ずつにすると2倍ちかく高速になるものの35°くらいの傾きの顔をピンポイントで見逃したりもする。試してみたかんじでは6〜8°ずつくらいならそれほど精度落ちないかなぁ、と。
あと元画像が大きいときは処理が重くなりすぎないように最大数百ピクセル以内になるようリサイズしているけど、それによる精度の影響とかも起こるようで、これも より大きい画像を許容するようにすれば精度上がるかもしれないけどそのぶん処理が重くなる。
あとは前述のscaleFactor, minNeighborsをどうするか、とか 顔の検出時と目の検出時で別の値にすべきか、とか 色々な調整箇所はあると思う。

結局どういった画像を対象にどれくらいの精度で出したいかはケースによると思うので正解は無いと思うけど 汎用的に使えるように、っていうのは難しいですね。


商用の顔検出サービスとかってどういう技術を使っているんだろう… このへんもDeep Learningで劇的に改善できるものなんだろうか…?

結論

ののた可愛い。

TensorFlowでのMNIST学習結果を、誰でも実際に手書きして試せるようになった

先日、
TensorFlowでのMNIST学習結果を、実際に手書きして試す - すぎゃーんメモ
という記事を書いて、「Herokuで動かそうとしたが上手くいかず〜〜」ということだったのだけど、その後も幾つか変更があって そのおかげなのか何なのか分からないけどもう一度Heroku環境用のpipパッケージをビルドしてみたら上手く出来て、それを同梱することで無事にHeroku環境でも動くようになった。

これでブラウザさえあれば誰でも学習結果を試すことができますね。色んな数字を書いて試して遊んでみてください。

誰かスマホ対応してくれると嬉しいなー。。

Docker Image of Python with OpenCV 3.0 for Heroku

を、作った。

Docker Hub URL
https://hub.docker.com/r/sugyan/heroku-python-opencv/
Dockerfile repository
https://github.com/sugyan/docker-heroku-python-opencv
Example app
https://docker-python-opencv-example.herokuapp.com/

これを使って https://devcenter.heroku.com/articles/docker のようにheroku-docker pluginを使ってdeployすれば、cv2でOpenCVの機能を使うPython appが動くと思います。

例: https://docker-python-opencv-example.herokuapp.com/

余談

Deep Learningでアイドルの顔認識を試してみようかなーと思って、でもまずは顔の画像をたくさん集める必要があるなー 収集workerをどっかのサーバで動かせると良いのかなー、とかで ローカル環境以外のところでもOpenCVで画像から顔検出してくれるAPIみたいなのがあると便利そう…というところから

という流れになって。Dockerってまだ殆ど使ったことなかったしこの機会にちょっと勉強しておこうかな、というのもあり。
HerokuからCedar Stack用のheroku/cederイメージ、それにPython実行環境を追加したheroku/pythonというイメージが既に公開されているので、それを使ってさらに追加でOpenCVをインストールする、というDockerfileを書いた次第。

OpenCVのインストールは各Linuxディストリビューションスクリプト一発で最新のものが入るようにしてくれているのがあったので、それを拝借した。

普通の環境ならこれで簡単にインストールできて動かせるのだと思うけど、heroku/pythonでインストールされているpythonのパスを指定したり インストール先のパスを変えたり(/usr/localとかに入れてもreleaseするときにslugに含まれないらしい)、あと実際のheroku環境には入っていないライブラリなどもありOFFにしないとダメなようだったり、で 幾つかcmakeのビルドオプションを弄った。

-cmake -D CMAKE_BUILD_TYPE=RELEASE -D CMAKE_INSTALL_PREFIX=/usr/local -D WITH_TBB=ON -D BUILD_NEW_PYTHON_SUPPORT=ON -D WITH_V4L=ON -D INSTALL_C_EXAMPLES=ON -D INSTALL_PYTHON_EXAMPLES=ON -D BUILD_EXAMPLES=ON -D WITH_QT=ON -D WITH_OPENGL=ON ..
+cmake -D CMAKE_BUILD_TYPE=RELEASE -D CMAKE_INSTALL_PREFIX=/app/.heroku/opencv -D BUILD_opencv_python2=ON -D WITH_V4L=ON -D INSTALL_C_EXAMPLES=ON -D INSTALL_PYTHON_EXAMPLES=ON -D BUILD_EXAMPLES=ON -D WITH_OPENGL=ON -D PYTHON_INCLUDE_DIR=/app/.heroku/python/include/python2.7 -D WITH_1394=OFF -D WITH_GSTREAMER=OFF -D WITH_FFMPEG=OFF ..

など。


Docker Hub の Automated Builds はGithubリポジトリにDockerfileをpushするだけで勝手にimageを作ってくれるのでとってもラクチンでありがたい。

https://docs.docker.com/docker-hub/builds/

し、コードとして記述する感じでイメージが作られるので成果物がどういうものか分かりやすいし 変更の履歴も追えるし良いですね。


今回のをちょっと変更すれば他の言語用のbindingsを動かす環境も作れる、はず。ちょっと調べた感じでは

とか。でもまぁLLで使うならPythonが一番無難そうかな、と…

TensorFlowでのMNIST学習結果を、実際に手書きして試す

Deep Learningという言葉を色んなところで聞くようになり、Googleからも TensorFlow というものが出たし、そろそろちょっと勉強してみるか〜 ということで初心者が機械学習に手を出してみた。

TensorFlowのtutorialを見てみると、まず最初に「MNIST」という手書き文字の識別問題が出てくる。その問題に対して、こういうモデルを作ってこうやって学習させていくと91.2%くらいの識別率になります、さらに飛躍させてこういうモデルでこうやって学習させると99.2%くらいまで識別率が上がります、とか書いてあって、確かになるほどーと数字で納得もできるのだけど、せっかくなら実際にその学習結果を使って自分の書いた数字を識別してもらいたいじゃないか、ということで そういうのを作ってみた。

canvasにマウスで自由に線を書くとそれを入力として(使用しているモデルに合わせて28x28にリサイズして)サーバに送り、MNIST用に学習したモデルたちがそれぞれどういう判定を返したか、を返して結果を表示する。

使用しているのはtutorialで出てくる、単純なロジスティック回帰を用いた単層のもの(識別率約91.2%)と、多層畳み込みニューラルネットワークのもの(識別率約99.2%)。

これをそれぞれMNISTのデータからある程度まで学習させて、それぞれの変数をdumpしておき、Webアプリではそれを読み込んで使うだけ。
実際に数字を書いてみると多層畳み込みのものの方がどれだけ精度が高いかを実感できる。Deep Learningすげー。

余談

せっかくWebアプリにしているんだからHerokuにでも上げて誰でも試せるようにしたい、と思ったのだけど、現時点ではTensorFlowがHerokuのPython buildpack上では動かないらしい。

python - undefined symbol: PyUnicodeUCS4_FromStringAndSize with tensorflow on heroku - Stack Overflow

これによると既に最新のソースコードでは解決しているそうなので、自前でpipパッケージをビルドすれば出来るのでは、とのことだったけど dockerでheroku/pythonの環境を作ってbazelやらswigやらinstallしてビルドしてみようとしたものの結局うまくビルドできず、断念した… tensorflowの次のバージョンがreleaseされるのを待っておこうと思います…

余談2

なんとなく今まで使ったことないものを使ってみよう、と思ってフロント側のJSをES2015で書いてGulpでwatchしつつ変更のたびにbabelで変換して適当にcompressして、みたいにしてみた。
まだ全然慣れないけど 最近のフロントエンド開発ってこんな感じなのかなー、と。

Deep Learningについて

ググると色んな解説記事などがあってありがたい。

とか特に丁寧に解説があって深層学習ってのがどういうことなのか、とかそれなりに理解できたような気がします。
まだまだ分かっていないことも多いけど 次は顔識別とか試してみたいなーと思っています。