動機
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
SoftwareBitmap
のLockBuffer
関数を呼んで、BitmapBuffer
を取得します。BitmapBuffer
で直接ビットマップデータにアクセスできます。BitmapBuffer
へのデータの書き込みが終わったらスコープを抜けるか、明示的にdrop
を呼ぶなりしてロックを解除する必要があります。
{
let bmp_buf = bmp.LockBuffer(BitmapBufferAccessMode::Write)?;
// ここでビットマップデータの書き込み処理
}
// スコープを抜けてロック解除
IMemoryBufferByteAccess
BitmapBuffer
への参照を作って、その参照をIMemoryBufferByteAccess
にキャストします。IMemoryBufferByteAccess
のGetBuffer
関数で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
を返します。IAsyncOperation
はFuture
を実装しているので、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(())
}
コード
コピペ用サンプルコード
[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"
]
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年前に投稿していました。自分も「いいね」しておったのに、まったく覚えてないとは…