オンライン手書き文字認識やってみた

# やりたいこと

**HTMLのCanvasに文字を書いて、一画ごとに通信走らせてその近似文字を選択させたい！！**
ってことでオンライン文字認識について調べて実装したので、その辺書いていきます。


# オンライン手書き文字認識とは？

IMEパッドしかり、NintendoDSの脳トレしかり、手で書いた文字がどの文字コードになるのか、っていうアレです。
今回は、Tomoeっていうオープンソースの手書き文字認識エンジンを使いました。
http://tomoe.osdn.jp/cgi-bin/ja/blog/index.rb

っていうか日本語で認識させたい場合は、Tomoeかその亜種しかないと思います。
ただ、TomoeはLinuxのIME的な位置づけ？なのかなという感じでそのままAjaxで使うのは難儀っぽいです。


# どうやってやるの？ - 救世主Zinnia -

そのへんに転がってる石みたいなPHPerの僕は、Tomoeを１から入れてどうこうが難儀でした。
そこで調べを進めるとTomoeの認識モデルを元につかえるZinniaというライブラリにたどりつきました。
http://taku910.github.io/zinnia/index-ja.html

こちらは、サンプルコードがCとC++で載っているので、なんとかなるレベルが若干高いように思えたので採用しました。
CでもC++でもコンパイルして叩けばいけるやろっていう算段での実装です。


# はよコードみせろや！

## HTML

以下を参考にAngularでつくりました。（諸事情でソース公開できません＞＜）
http://chasen.org/~taku/software/ajax/hwr/

## PHP

```php
<?php

$fp = fopen('php://input', 'r');
$arg = '';

if ($fp) {
    while (!feof($fp)) {
        $s = fread($fp, 65536);
        $request = json_decode($s);
        foreach ($request as $stroke => $points) {
            foreach ($points as $point) {
                $arg .= ' ' . $stroke . ' ' . $point->x . ' ' . $point->y;
            }
        }
    }
    fclose($fp);
}

exec('./zinnia_api' . $arg, $output, $ret);

if ($ret == 0) {
    foreach($output as $key => $str) {
        $converted = iconv('UTF-8', 'UTF-8//IGNORE', $str);
        if ($converted === false) {
            unset($output[$key]);
        }
    }
    echo json_encode($output);
} else {
    echo 'ERROR[' . $ret . ']';
}
```

## C

```c
#include <stdio.h>
#include <stdlib.h>
#include "zinnia.h"

int main(int argc, char *argv[])
{
  int count;
  char *arg1;
  char *arg2;
  char *arg3;

  size_t i;
  zinnia_recognizer_t *recognizer;
  zinnia_character_t *character;
  zinnia_result_t *result;

  recognizer = zinnia_recognizer_new();

  if (!zinnia_recognizer_open(recognizer, "/usr/local/lib/zinnia/model/tomoe/handwriting-ja.model")) {
    fprintf(stderr, "ERROR: %s\n", zinnia_recognizer_strerror(recognizer));
    return -1;
  }

  character = zinnia_character_new();
  zinnia_character_clear(character);

  // キャンバスのサイズ
  zinnia_character_set_width(character, 420);
  zinnia_character_set_height(character, 420);


  for (count = 1; count <= ((argc - 1) / 3); count++) {
    zinnia_character_add(
      character,
      atoi(argv[(count * 3) - 2]),
      atoi(argv[(count * 3) - 1]),
      atoi(argv[(count * 3) - 0])
    );
  }


  // レスポンスの数
  result = zinnia_recognizer_classify(recognizer, character, 10);
  if (result == NULL) {
    fprintf(stderr, "ERROR: %s\n", zinnia_recognizer_strerror(recognizer));
    return -1;
  }

  for (i = 0; i < zinnia_result_size(result); ++i) {
    fprintf(stdout, "%s\n", zinnia_result_value(result, i));
  }

  zinnia_result_destroy(result);
  zinnia_character_destroy(character);
  zinnia_recognizer_destroy(recognizer);

  return 0;
}
```


# なにかいてるかわからんわ！

えーと、諦め半分ではないんですが、C言語でいい感じに処理させたかったんですけど、かなーり動的な感じでもにょりたかったのでファイルキャッシュつくってもげもげみたいなことはせず、phpからc側に**全部引数でなげちゃいました**。
n+0番目の引数に 画数
n+1番目の引数に x軸のプロット座標
n+2番目の引数に y軸のプロット座標
が入るようになってます。

ほかはエラー処理だったり、イニシャライズだったりでお作法にしたがってくださいね、ってやつなので公式のドキュメント読んでください。

これ、実際ほとんどソースサンプルがなくて、そんなに需要ないニッチな感じなの！？ってちょっと震えました。
誰かの何かのお役にたてば幸いです。

僕の知ってる限りの知識はお伝えしますので、質問等はお気軽にどうぞ(・ω・)