Unity で Tesseract-OCR を利用できるようにする

これは [KLab Advent Calendar 2016](http://qiita.com/advent-calendar/2016/klab) の12日目の記事です。

#はじめに
OCR とは、画像から文字を解析して読み取る装置やソフトウェアのことです。
最近では、家計簿アプリのレシートの読み取りなどに活用されています。

この機能を使って、ちょっとした Editor 拡張を作りたかったので、調べてみました。

ちなみに、自分の環境は macOS ですので、 Windows などの他の環境の方は適宜読みかえてください。

# ライブラリの選定

Unity で使えそうな OCR ライブラリを検索すると、

* [Vuforia](https://developer.vuforia.com/) 
* [Tesseract-OCR](https://github.com/tesseract-ocr/tesseract) 

の2つのライブラリが見つかります。

~~響け!~~ Vuforia は拡張現実を Unity で簡単に実装できるようになるライブラリで、その中の機能として文字認識があるようです。
Tesseract-OCR は GitHub で公開されている Apache ライセンス下の OCR ライブラリです。
この Tesseract-OCR は .Net Framework で扱えるようにラップしたライブラリ [.NET wrapper for tesseract-ocr](https://github.com/charlesw/tesseract) があり、 Unity でも使えるかもしれないということで Tesseract の方を試してみることにしました。

# .NET wrapper for tesseract-ocr を導入してみる
__結論から言えば、.NET wrapper for tesseract-ocr は Unity で利用することができませんでした。__

.NET wrapper for tesseract-ocr は、 `System.Drawing.dll` が必要になります。
`System.Drawing.dll` 自体は mono に入っているので、そこから Unity の Plugins フォルダなどに入れます。

しかし、[ライブラリ内部](https://github.com/charlesw/tesseract/blob/master/src/Tesseract/InteropDotNet/LibraryLoader.cs#L84)で使われている `System.AppDomain.CurrentDomain.BaseDirectory` が Unity では `null` を返すので動いてくれません。
また、これを解決しても`liblept172.so` と `libdl.so` が無いと言われます。
Unix で動くようになっていますが、 macOS では難しいようです。 

Unity で使う上で .NET で使えるようにラップされているのは魅力的ですが、一旦ここで別のアプローチを試みてみます。

# Tesseract-OCR を DllImport して使う
本家 Tesseract は macOS なら Homebrew でインストールできます。
別環境の方は、本家 [wiki](https://github.com/tesseract-ocr/tesseract/wiki#installation) をみてもらうと良いと思います。

```
$ brew install tesseract
```

インストールした tesseract を Unity スクリプト上で `DllImport` して使います。

tesseract の [API](https://github.com/tesseract-ocr/tesseract/blob/3.04/api/capi.cpp) をみながら Unity から渡せそうな引数の関数を探します。
下記の関数があれば、基本的なことはできると思います。

* `TessBaseAPICreate`
    * ハンドラを生成する
* `TessBaseAPIInit3`
    * ハンドラの初期化する
    * 教師データ、設定ファイルが入ったディレクトリや言語を指定する
* `TessBaseAPISetImage`
    * 画像を設定する
    * `bytes_per_pixel` は　24 bit 画像なら 3 にする
    * `bytes_per_line`　は　`bytes_per_pixel * width`
* `TessBaseAPIGetUTF8Text`
    * 画像から文字列を抽出する
    * UTF8 で取得できるの最高

テスト用に書いたスクリプトを記載しておきます。
Inspector 上でスクリプトの source に読み取りたい画像を表示した Image コンポーネントをアタッチして、実行するとコンソール上に読み込んだ文字列が出力されます。

```C#
using UnityEngine;
using UnityEngine.UI;
using System;
using System.Runtime.InteropServices;
using System.Linq;
using System.Collections.Generic;

public class OcrTest : MonoBehaviour
{
	[SerializeField]
	Image source;

	[DllImport ("libtesseract", EntryPoint="TessBaseAPICreate")]
	private static extern IntPtr Create();

	[DllImport ("libtesseract", EntryPoint="TessBaseAPIInit3")]
	private static extern int Init(IntPtr handle, string datapath, string language);

	[DllImport ("libtesseract", EntryPoint="TessBaseAPISetImage")]
	private static extern int SetImage(IntPtr handle, byte[] imagedata, int width, int height, int bytes_per_pixel, int bytes_per_line );

	[DllImport ("libtesseract", EntryPoint="TessBaseAPIGetUTF8Text")]
	private static extern string GetText(IntPtr handle);

	void Start()
	{
		var originalData = source.sprite.texture.GetRawTextureData().Reverse().ToArray();

		var tmp = new List<byte>();
		var destination = new List<byte>();
		for(var i = 0;i < originalData.Length;i += 3)
		{
			tmp.Add(originalData[i]);
			tmp.Add(originalData[i + 1]);
			tmp.Add(originalData[i + 2]);
			if (tmp.Count >= 3 * source.sprite.texture.width) 
			{
				tmp.Reverse();
				destination.AddRange(tmp);
				tmp.Clear();
			}
		}

		var handle = Create();
		Init(handle, null, "eng");

		SetImage(handle, destination.ToArray(),
			source.sprite.texture.width, 
			source.sprite.texture.height, 
			3, 
			3 * source.sprite.texture.width);
		
		Debug.Log(GetText(handle));
	}
}

```

気を付けなければならないのは、 `GetRawTextureData()` は画像の左下から始まる byte 配列を返すことです。
画像の左上からの始まる byte 列に変換してやる必要があります。
上のスクリプトでは、 `GetRawTextureData()` で得られた配列を反転して 3 byte ずつ読み取っていき、画像幅長まできたら、一列分をさらに反転するという方法でやってます。

# Unity Editor での実行結果

読み取り画像

<img width="467" alt="source.png" src="https://qiita-image-store.s3.amazonaws.com/0/146380/c2277933-4ba8-2219-762d-570d33c8a210.png">

Unity コンソールの出力

```
ABCD
Enjoy your life.
EFG
1984 0712
abc de f
Hello world!
Unity 5.4.3p1
```

このスクリプトを実行してみると、こんな感じに出力されます。
カメラで紙にかかれた文字を撮るよりは精度が良いように思います。
また、 tesseract は日本語も読み取ることができるので、試してみるのも面白いかもしれません。

# おわりに
やっと文字が読み取れるようになりました。
Unity で扱いやすいライブラリが当然あると思っていましたが、 OCR を使うだけでかなり迷走した感があります。
結局のところ DllImport って最高ですね。
Windows Phone 8.0 用に Sqlite3 をラップした経験がここで生かされたのでよかったです。

これで作りたい Editor 拡張への一歩がやっと踏み出せました。