2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Rust で Windows の OCR を使ってみる

Last updated at Posted at 2022-10-20

動機

Windows 10 以降で OCR できるということを知ったので Rust で OcrEngine を使ってみました。

環境

Windows 10 21H1
rustc 1.64.0 (a55dd71d5 2022-09-19)
windows crate 0.42.0

実装

OcrEngine

ドキュメントに書いてある通り、OCR するにはOcrEngineクラスのRecognizeAsync関数を呼び出すだけです。簡単!

let engine = OcrEngine::TryCreateFromUserProfileLanguages()?;
engine.RecognizeAsync(&bmp)?;

SoftwareBitmap

RecognizeAsync関数にはSoftwareBitmapを渡します。SoftwareBitmapは圧縮されていないビットマップを表すということなので、どうにかしてビットマップデータを書き込んでやればよいようです。

let bmp = SoftwareBitmap::Create(BitmapPixelFormat::Rgba8, width, height)?;
// この後、bmp にデータを書き込む処理

image crate

いろいろな画像形式に対応したいので image crate を使います。

[dependencies.image]
version = "0.24"
default-features = false
features = [
    "jpeg",
    "png",
    "bmp",
    "tiff"
]
let img = image::open("path to image file")?;
let width = img.width() as i32;
let height = img.height() as i32;
let rgb = img.to_rgb8().to_vec(); // RGB 形式のビットマップ

BitmapBuffer

SoftwareBitmapLockBuffer関数を呼んで、BitmapBufferを取得します。BitmapBufferで直接ビットマップデータにアクセスできます。BitmapBufferへのデータの書き込みが終わったらスコープを抜けるか、明示的にdropを呼ぶなりしてロックを解除する必要があります。

{
    let bmp_buf = bmp.LockBuffer(BitmapBufferAccessMode::Write)?;
    // ここでビットマップデータの書き込み処理
}
// スコープを抜けてロック解除

IMemoryBufferByteAccess

BitmapBufferへの参照を作って、その参照をIMemoryBufferByteAccessにキャストします。IMemoryBufferByteAccessGetBuffer関数でBitmapBufferへの生ポインタを取得できます。

let array: IMemoryBufferByteAccess = bmp_buf.CreateReference()?.cast()?;

let mut data = ptr::null_mut();
let mut capacity = 0;
unsafe {
    array.GetBuffer(&mut data, &mut capacity)?;
}
// SoftwareBitmap は RGBA 型式なので 幅 × 高さ × 4bytes。
assert_eq!((width * height * 4).abs(), capacity as i32);

書き込み

バッファへの生ポインタが取得できたので、あとはそこにデータを書き込むだけです。生ポインタをそのまま使うのは厄介なので Rust の世界のスライスに変換して書き込みます。

let slice = unsafe { slice::from_raw_parts_mut(data, capacity as usize) };
slice.chunks_mut(4).enumerate().for_each(|(i, c)| {
    c[0] = rgb[3 * i];
    c[1] = rgb[3 * i + 1];
    c[2] = rgb[3 * i + 2];
    c[3] = 255;
});

出力

SoftwareBitmapが準備できたのでRecognizeAsync関数に渡します。RecognizeAsync関数はIAsyncOperationを返します。IAsyncOperationFutureを実装しているので、awaitで待ちましょう。そして結果を一行ずつ表示します。

#[tokio::main]
async fn main() -> Result<()> {
    // omitted
    engine
        .RecognizeAsync(&bmp)?
        .await?
        .Lines()?
        .First()?
        .filter_map(|line| line.Text().ok())
        .for_each(|text| println!("{text}"));
    Ok(())
}

コード

コピペ用サンプルコード
Cargo.toml
[package]
name = "ocr"
version = "0.1.0"
edition = "2021"

[dependencies]
anyhow = "1.0"
tokio = { version = "1.21", features = ["rt-multi-thread", "macros"] }

[dependencies.image]
version = "0.24"
default-features = false
features = [
    "jpeg",
    "png",
    "bmp",
    "tiff"
]

[dependencies.windows]
version = "0.42"
features = [
    "Graphics_Imaging",
    "Foundation",
    "Foundation_Collections",
    "Media_Ocr",
    "Win32_System_WinRT"
]
main.rs
use anyhow::Result;
use std::{ptr, slice};
use windows::{
    core::*,
    Graphics::Imaging::{BitmapBufferAccessMode, BitmapPixelFormat, SoftwareBitmap},
    Media::Ocr::*,
    Win32::System::WinRT::*,
};

#[tokio::main]
async fn main() -> Result<()> {
    let img = image::open("path to image file")?;
    let width = img.width() as i32;
    let height = img.height() as i32;
    let rgb = img.to_rgb8().to_vec();

    let bmp = SoftwareBitmap::Create(BitmapPixelFormat::Rgba8, width, height)?;
    {
        let bmp_buf = bmp.LockBuffer(BitmapBufferAccessMode::Write)?;
        let array: IMemoryBufferByteAccess = bmp_buf.CreateReference()?.cast()?;

        let mut data = ptr::null_mut();
        let mut capacity = 0;
        unsafe {
            array.GetBuffer(&mut data, &mut capacity)?;
        }
        assert_eq!((width * height * 4).abs(), capacity as i32);

        let slice = unsafe { slice::from_raw_parts_mut(data, capacity as usize) };
        slice.chunks_mut(4).enumerate().for_each(|(i, c)| {
            c[0] = rgb[3 * i];
            c[1] = rgb[3 * i + 1];
            c[2] = rgb[3 * i + 2];
            c[3] = 255;
        });
    }

    let engine = OcrEngine::TryCreateFromUserProfileLanguages()?;
    engine
        .RecognizeAsync(&bmp)?
        .await?
        .Lines()?
        .First()?
        .filter_map(|line| line.Text().ok())
        .for_each(|text| println!("{text}"));
    Ok(())
}

結果

試しに Rust のトップページをキャプチャして OCR にかけてみました。

英文

lnstall
第 st
Learn
Playground
Tools
Governance
Community
Blog
English (en-US)
A language empoweringeveryone
to build reliable and efficientsoftware.
Why Rust?
performance
Rustis blazinglyfastand memory-
efficient:with no runtime orgarbage
collector, itcan power performance-
criticalservices, run on embedded
devices, and easily i ntegrate with other
languages ・
Reliability
Rust's rich type system and ownership
modelguarantee memory-safetyand
thread-safety—enablingyou tO eliminate
manyclassesofbugsatcompile-time.
GET STARTED
Version 1.64.0
productivity
Rusthasgreatdocumentation,afriendly
com piler with useful erro r messages, a nd
top-notch tooling—an integrated
package managerand build t00 し smart
multi-editorsupportwith auto-
completion and type inspections, an
auto-formatter, and more.

和文

イ ン ス - ト = ル
第 st
学 ぶ
Playground
ッ = ル
カ ( 丿 こ ) ィ . ス
コ
ー ユ
一 丁 ィ
プ ロ グ
日 本 語 (ja)
効 率 的 で 信 頼 で き る ソ フ ト ウ ェ ア を
誰 も が つ く れ る 言 語
な ぜ Rust か ?
パ フ ォ - マ ン ス
Rust は 非 常 に 高 速 で メ モ リ 効 率 が 高 く ラ ン
タ イ ム や カ べ - ジ コ レ ク タ が な い た め 、 バ
フ ォ - マ ン ス 重 視 の サ - ビ ス を 実 装 で き ま
す し 、 組 込 み 機 器 上 で 実 行 し た り 他 の 言 語
と の 調 和 も 簡 単 に で き ま す 。
信 頼 性
バ イ ル 時 に 排 除 す る こ と が 可 能 で す 。
さ れ ま す 。 さ ら に 様 々 な 種 類 の バ グ を コ ン
よ り メ モ リ 安 全 羅 と ス レ ッ ド 安 全 羅 が 保 証
Rust の 豊 か な 型 ラ ス テ ム と 所 有 権 モ デ 丿 レ に
は じ め る
ノ ( - ジ ョ ン 1.64.0
が 数 多 く 揃 っ て い ま す 。
自 動 フ ォ - マ ッ タ と い っ た 一 流 の ツ - ル 群
応 す る ス マ - ト な 自 動 補 完 と 型 検 機 能 、
ジ ャ と ビ ル ド ッ - ル 、 多 数 の エ デ イ タ に 対
イ ラ 、 お よ び 統 合 さ れ た バ ッ ケ - ジ マ ネ -
ラ - メ ッ セ - ジ を 備 え た 使 い や す い コ ン バ
Rust に は 優 れ た ド キ ュ メ ン ト 、 有 用 な 工
生 産 性

まとめ

Windows の OcrEngine を Rust から使ってみました。精度はあまりよくないように思います。

おまけ

PDF を OCR したいのなら Ghostscript を使って PDF をビットマップ画像に変換すればよいです。下記のコードで動くと思います。OCR したいページ番号は-sPageList=のオプションで指定してください。

let output = Command::new("./gswin64c.exe")
    .arg("-dSAFER")
    .arg("-dBATCH")
    .arg("-dNOPAUSE")
    .arg("-dNOPAGEPROMPT")
    .arg("-dNOPROMPT")
    .arg("-dQUIET")
    .arg("-r300")
    .arg("-sPageList=1")
    .arg("-sDEVICE=bmp16m")
    .arg("-dGraphicsAlphaBits=4")
    .arg("-dTextAlphaBits=4") 
    .arg("-sstdout=%stderr")
    .arg("-sOutputFile=-")
    .arg("path to pdf file")
    .output()?;
let img = image::load_from_memory_with_format(&output.stdout, ImageFormat::Bmp)?;

記事を書いてから気づきましたが、@osanshouo さんが同じような記事を2年前に投稿していました。自分も「いいね」しておったのに、まったく覚えてないとは…

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?