[Rust/WinRT] 画像文字認識 (OCR)

昨日「[Windows Native C++でWinRTのOCR APIを使う方法](https://qiita.com/Yukio-Ichikawa/items/f8d3111a60a337adfd48)」という記事を拝見して Win10 が画像文字認識 (OCR) を提供していることを知ったので, Rust/WinRT で試してみました. 驚くほど簡単に PNG 画像を OCR するコンソールアプリが作成できて感動的です.

コード全体はこちら: https://github.com/osanshouo/ocr-by-rust-winrt

# 準備

## 環境

本記事のコードは以下の環境で検証しました.

- Windows 10 v1909 build 18363.900
- Rust 1.44.0 stable-x86_64-pc-windows-msvc
- winrt 0.7.0

## WinRT のインポート

例によって Rust/WinRT では次の読み込みマクロがコード冒頭に必要です.

```rust
winrt::import!(
    dependencies
        os
    types
        windows::storage::*
        windows::graphics::imaging::*
        windows::media::ocr::*
);
```

# 使用可能な言語の確認

「設定」→「時刻と言語」→「言語」から確認できるインストール済みの言語による OCR が利用可能です.

```rust
use windows::media::ocr::OcrEngine;

fn main() -> winrt::Result<()> {
    for lang in OcrEngine::available_recognizer_languages()? {
        println!("{}: {}", lang.language_tag()?, lang.display_name()?);
    }

    Ok(())
}
```

実行結果の例です. ここで表示される [BCP47 言語タグ](https://tools.ietf.org/html/bcp47) (`de-DE` や `ja` など) は後で使用します.

```shell-session
$ cargo run
de-DE: ドイツ語 (ドイツ)
ja: 日本語
```

参考文献: [OcrEngine.AvailableRecognizerLanguages Property](https://docs.microsoft.com/en-us/uwp/api/windows.media.ocr.ocrengine.availablerecognizerlanguages?view=winrt-19041#Windows_Media_Ocr_OcrEngine_AvailableRecognizerLanguages)

# 画像文字認識

コマンドライン引数として画像ファイルのパスを与えると OCR を実行し, その結果を出力するというプログラムを作成してみます. ここでは簡単のため入力は PNG に限ることにします. 

先に `main` 関数を示しておきます. 見ての通り `open_image` と `ocr` というふたつの関数を定義するのが目標です.

```rust
fn main() -> winrt::Result<()> {
    // 画像ファイルを開きビットマップに変換
    let bitmap = open_image()?;

    // OCR を実行
    ocr(bitmap)
}
```

## 画像の読み込み

Win10 の OCR エンジンはビットマップ画像しか認識できないため, まず PNG ファイルをビットマップに変換する必要があります. そこでコマンドライン引数としてファイルパスを取得し, そのパスのファイルを開いて `BitmapDecoder` に渡します.

```rust
use std::env;
use windows::storage::{StorageFile, FileAccessMode};
use windows::graphics::imaging::{BitmapDecoder, SoftwareBitmap};

fn open_image() -> winrt::Result<SoftwareBitmap> {
    // コマンドライン引数の取得
    let path = env::args().nth(1).expect("ファイルパスを引数に指定してください.");

    // ファイルパスから `StorageFile` オブジェクトを取得
    let file = StorageFile::get_file_from_path_async(path)?.get()?;

    // ファイルを読み込みビットマップに変換
    let bitmap = BitmapDecoder::create_with_id_async(
        BitmapDecoder::png_decoder_id()?, // ファイル形式は PNG とする
        file.open_async(FileAccessMode::Read)?.get()?
    )?.get()?;

    // `SoftwareBitmap` 型へ変換
    bitmap.get_software_bitmap_async()?.get()
}
```

参考文献: [SoftwareBitmap Class](https://docs.microsoft.com/en-us/uwp/api/windows.graphics.imaging.softwarebitmap?view=winrt-19041), [BitmapDecoder Class](https://docs.microsoft.com/en-us/uwp/api/windows.graphics.imaging.bitmapdecoder?view=winrt-19041), [StorageFile Class](https://docs.microsoft.com/ja-jp/uwp/api/Windows.Storage.StorageFile?view=winrt-19041), [ファイルの作成、書き込み、および読み取り](https://docs.microsoft.com/ja-jp/windows/uwp/files/quickstart-reading-and-writing-files)

## OCR の実行

得られたビットマップ画像に対して OCR を実行します. なお OCR 言語はユーザープロファイルから設定することもできますが, ここでは言語タグを用いて手動設定するようにしました.

```rust
use windows::globalization::Language;
use windows::media::ocr::OcrEngine;

// OCR で使用する言語の指定
const LANG: &'static str = "ja";

fn ocr(bitmap: SoftwareBitmap) -> winrt::Result<()> {
    // 言語タグをもとに OCR エンジンを生成
    let lang = Language::create_language(LANG)?;
    let engine = OcrEngine::try_create_from_language(lang)?;

    // OCR の実行
    let result = engine.recognize_async(bitmap)?.get()?;

    // 結果を表示
    println!("{}", result.text()?);

    Ok(())
}
```

参考文献: [OcrEngine Class](https://docs.microsoft.com/en-us/uwp/api/windows.media.ocr.ocrengine?view=winrt-19041), [OcrResult Class](https://docs.microsoft.com/en-us/uwp/api/windows.media.ocr.ocrresult?view=winrt-19041)

# 実行結果

## 日本国憲法前文

次の PNG ファイルに対してこのプログラムを実行してみます.

![Constitution_of_Japan.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/466807/933a366b-aec7-3e8f-1a85-5d0f2c380c3f.png)

結果はこちら. (見にくいので引用形式にしました)

> 日 本 国 憲 法 日 本 国 民 は 、 正 当 に 選 挙 さ れ た 国 会 に お け る 代 表 者 を 通 じ て 行 動 し 、 わ れ ら と わ れ ら の 子 孫 の た め に 、 諸 国 民 と の 協 和 に よ る 成 果 と 、 わ が 国 全 土 に わ た っ て 自 由 の も
た ら す 恵 沢 を 確 保 し 、 政 府 の 行 為 に よ っ て 再 び 戦 争 の 参 禍 が 起 る こ と の な い や う に す る こ と を 決 意 し 、 こ こ に 主 権 が 国 民 に 存 す る こ と を 宣 言 し 、 こ の 法 を 確 定 す る 。 そ も そ も 国
政 は 、 国 民 の 厳 粛 な 信 託 に よ る も の て あ っ て 、 そ の 権 威 は 国 民 に 由 来 し 、 そ の 権 力 は 国 民 の 代 表 者 が こ れ を 行 使 し 、 そ の 福 利 は 国 民 が こ れ を 享 受 す る 。 こ れ は 人 類 普 遍 の 原 理 て
あ り 、 こ の 法 は 、 か か る 原 理 に 基 く も の で あ る 。 わ れ ら は 、 こ れ に 反 す る - 切 の 法 、 法 令 及 び 詔 勅 を 排 除 す る 。 日 本 国 民 は 、 恒 久 の 平 和 を 念 願 し 、 人 間 相 互 の 関 係 を 支 配 す る 崇 高 な 理 想 を 深 く 自 覚 す る の で あ っ て 、 平 和 を 愛 す る 諸 国 民 の 公 正 と 信 義 に 信 頼 し て 、 わ れ ら の 安 全 と 生 存 を 保 持 し よ う と 決 意 し た 。 わ れ ら は 、 平 和 を 維 持 し 、 専 制 と 隷 従 、 圧 迫 と 偏 狭 を 地 上 か ら 永 遠 に 除 去 し よ う と 努 め て ゐ る 国 際 社 会 に お い て 、 名 誉 あ る 地 位 を 占 め た い と 思 ふ 。 わ れ ら は 、 全 世 界 の 国 民 が 、 ひ と し く 恐 怖 と 欠 乏 か ら 免 か れ 、 平 和 の う ち に
生 存 す る 権 利 を 有 す る こ と を 確 認 す る 。 わ れ ら は 、 い づ れ の 国 家 も 、 自 国 の こ と の み に 専 念 し て 他 国 を 無 視 し て は な ら な い の で あ っ て 、 政 治 道 徳 の 法 則 は 、 普 遍 的 な も の で あ り
、 こ の 法 則 に 従 ふ こ と は 、 自 国 の 主 権 を 維 持 し 、 他 国 と 対 等 関 係 に 立 た う と す る 各 国 の 責 務 て あ る と 信 す る 。 日 本 国 民 は 、 国 家 の 名 誉 に か け 、 全 力 を あ げ て こ の 崇 高 な 理 想 と 目
的 を 達 成 す る こ と を 誓 ふ 。


なんだか半角スペースが邪魔ですが, それを除けばかなり良いです. 間違いは「参 禍」, 「- 切 の」の二か所でしょうか.

## 地下鉄路線図

より OCR が難しそうな地下鉄路線図も試してみます. 題材はこちらの京都市営地下鉄です.

https://commons.wikimedia.org/wiki/File:Kyoto_Subway_Map_jp.png

結果はこちら.

> KO 1 ■ 第 会 館 T15 T14 K02 〇 鳩 1 1 東 都 〇 “ K08 / T13 烏 丸 御 池 T12 〇 町 〇 今 出 掛 〇 第 ロ K04 北 大 第 K03 T11 T10 山 T09 上 ・ 東 西 線 T08 To Line T07 山 料 T03 ■ ・ 〇 ・ 鳩 T06 東 野 烏 丸 線 Karasuma. L1neo 十 条 〇 く い な 橋 〇 竹 出 T02 石 田 T01 六 第 を 京 都 市 営 地 下 鉄 路 線 ■ KYOtO Subway MaD

フォントのせいか, それとも題材のせいか, 先ほどよりは成績が悪いです. 「山 料」は惜しい… でもそれなりに読めていますし, とても簡単に利用できることを考えるとこんなものでしょうか.

# 参考文献

- [Microsoft OCR をデスクトップのWFPアプリで動かす方法](https://www.moonmile.net/blog/archives/8584)
- [Windows Native C++でWinRTのOCR APIを使う方法](https://qiita.com/Yukio-Ichikawa/items/f8d3111a60a337adfd48)