手書き文字認識エンジン「Zinnia」をDockerコンテナ上で動かす

# 手書き文字認識エンジン「Zinnia」とは？
Zinniaは[Taku Kudo](https://github.com/taku910)さんが作成された手書き文字認識エンジンです。  
[説明](http://taku910.github.io/zinnia/index-ja.html)には"機械学習アルゴリズム SVM を用いたポータブルで汎用的な オンライン手書き文字認識エンジン"と書かれています。　　　

オンライン認識とオフライン認識の違いについては[こちら](http://web.tuat.ac.jp/~nakagawa/pub/2012/pdf/Zhubilan_IEICE2012.pdf)をご覧ください。

# Zinniaを動かしたい理由
- 最近スマホ・タブレットでよく使われている手書き文字認識を使って遊びたい
- けどAPIは有料ばっかりで使いたくない...
- あ、Zinniaはタダで使える、これにしょう!!!

# Zinniaについて参考にしたサイトなど
- [kaz_29@はてな](http://d.hatena.ne.jp/kaz_29/20111214/1323841470)さんのページで色々紹介されています。
- [shinichi-takahashi](http://qiita.com/shinichi-takahashi/items/73683381996934fd3cf8)さんがQiitaで紹介されています。

# Docker上で動かす理由
- よくわからないエラーが起きたらいやだなぁ

# Dockerのイメージを作る
今回はubuntu 14.04のイメージをもとに環境構築します。

```
# Dockerfile
FROM ubuntu:14.04

# コンパイルに必要なもの(build-essential)とwgetのインストール
RUN apt-get update && apt-get install -y build-essential wget

# homeディレクトリで作業します
WORKDIR /home

# zinnia本体と認識モデルの取得
RUN wget 'http://jaist.dl.sourceforge.net/project/zinnia/zinnia/0.06/zinnia-0.06.tar.gz'
RUN wget 'http://jaist.dl.sourceforge.net/project/zinnia/zinnia-tomoe/0.6.0-20080911/zinnia-tomoe-0.6.0-20080911.tar.bz2'

# 解凍
RUN tar xf zinnia-0.06.tar.gz
RUN tar xf zinnia-tomoe-0.6.0-20080911.tar.bz2

# /usr/local/libにパスを通す
ENV LD_LIBRARY_PATH=/lib:/usr/lib:/usr/local/lib

# インストール
RUN cd zinnia-0.06 && ./configure && make && make install
RUN cd zinnia-tomoe-0.6.0-20080911 && ./configure && make && make install

# コンテナが停止しない呪文
CMD ["/bin/bash"]
```

Dockerfileというファイル名で以上をコピペしたら保存して、  
ファイルがあるディレクトリでイメージを作成。

```
$ docker build -t zinnia-tomoe .
```

# Dockerコンテナの起動
とりあえず先ほど作ったイメージで、コンテナを起動します。

```
$ docker run -ti -p 5000:5000 --name zinnia zinnia-tomoe
```
オプションは、読み書きできる形で、コンテナを中(port: 5000)と外(port: 5000)でつないで、  
コンテナの名前をzinniaにして、先ほど作ったイメージ(zinnia-tomoe)で起動するというものです。

# Bottleを使ってWebサービスとして使えるようにしよう!! (準備編)
BottleはPythonの軽量Webフレームワークです。  
これを使って最終的には、[Ajaxを使った手書き文字認識](http://chasen.org/~taku/software/ajax/hwr/)みたいなのを作ります。  
なので、ZinniaのPython APIを今後は使っていきます。  
それでは、コンテナ内で以下のコマンドを打って、環境構築をしましょう。

```
# Python環境の構築(たぶん使うの一部だけど...)
apt-get install -y python-dev python-setuptools python-numpy python-scipy

# pipをインストール
$ easy_install pip

# Flaskをインストール
$ pip install bottle
```

次に、ZinniaのPython APIをインストールします。

```
$ cd /home/zinnia-0.06/python/
$ python setup.py install
```

# Bottleを使ってWebサービスとして使えるようにしよう!! (サーバー側の色々編)
homeディレクトリにBottleAppという名前でディレクトリを作って、そこで作業します。

```
$ mkdir /home/BottleApp
$ cd /home/BottleApp
```
では、/home/BottleApp/BottleApp.pyにプログラムを書いていきます。

```
#!/usr/bin/python
# -*- coding:utf-8 -*-

# Bottle, zinnia, jsonのライブラリをインポート 
from bottle import route, run, post, request, hook, response
import zinnia
import json
from pprint import pprint

# 
# レスポンスヘッダの付与
@hook('after_request')
def enable_cors():
    response.headers['Access-Control-Allow-Origin'] = '*'

# 
# POSTでリクエストを受ける
@route('/zin', method='POST')  # or @post('/post')
def zinpost2():
    canvasWidth = request.forms.get("canvasWidth")
    canvasHeight = request.forms.get("canvasHeight")

    print canvasWidth
    print canvasHeight

    try:
        # 認識したい文字の情報を入れるオブジェクト
        s = zinnia.Character()
        # 文字認識のための分類器のオブジェクトと認識モデルの読み込み
        r = zinnia.Recognizer()
        r.open("/usr/local/lib/zinnia/model/tomoe/handwriting-ja.model")
        # 初期化と描画サイズの設定
        s.clear()
        s.set_width(int(canvasWidth))
        s.set_height(int(canvasHeight))
        for stroke in json.loads(request.forms.get("history")):
            print stroke["numofStroke"]
            print stroke["selectedcolor"]
            print stroke["_lineWidth"]
            for point in stroke["points"]:
                s.add(int(stroke["numofStroke"]),int(point[0]),int(point[1]))
                print point[0]," : ",point[1]
        
        # 上位10件の認識結果を取得する
        result = r.classify(s, 10)
        size = result.size()
        resultValue = []
        resultScore = []

        # 認識した文字はvalueに、確率はscoreに入っている
        for i in range(0, (size - 1)):
            print "%s\t%f" % (result.value(i), result.score(i))
            resultValue.append(result.value(i))
            resultScore.append(result.score(i))

        json_data = {
            "resultValue": resultValue,
            "resultScore": resultScore
        }

        print resultValue
        print resultScore

        return json_data
    
    except RuntimeError, e:
        print "RuntimeError: ", e,
        return "RuntimeError"

run(host='0.0.0.0', port=5000, debug=True)
```

あとは、

```
python BottleApp.py
```

で実行すれば、サーバーが5000番ポートで起動します。   
dockerコンテナの起動時に"-p 5000:5000"をしていたので,   
[ホストマシンの5000番ポート](http://localhost:5000/zin)にリクエストを投げると、認識結果が帰ってきます。

# html canvasを使って文字を手書きし、リクエストを投げる
準備中...