Rustで画像入出力

みなさん,Rust使ってますか?
新元号がRustに決まったことは記憶に新しいですね.
まだ使っていないよ!という方もこれを機に始めてみてはいかがでしょうか.

さて今回はRustで画像処理を行うために画像入出力用のcrateであるimageを使ってみたいと思います.
https://docs.rs/image/0.20.1/image/

GolangではC++実装のOpenCVを直接呼出しました( GolangでもOpenCVしたい!!! ).
RustでもOpenCVのラッパであるcv-rsを使ってもよいのですが,どのみち処理はすべて自分で書くので入出力だけできればよく,今回はimageを選びました.(imshowが出来ないのはちょっと不便かも).

今回はサンプルとしてグレースケール化を実装します.
もちろん画像読み込み時に直接グレースケールとして解釈すればよいのですが,一連の処理フローを確認するという意味でいくつかの方法でべた書きで実装します.

コードは以下.

extern crate image;
use std::env;
fn main() {
let mut args = env::args();
args.next(); // skip command name
let img_path = match args.next() {
None => {
eprintln!("Error: Input file path is not specified. ");
eprintln!("Usage: cargo run /path/to/input/image");
return;
},
Some(s) => s,
};
// Load Image
let img = image::open(&img_path).unwrap();
let img = img.to_rgb(); // derive RGB Image
println!("Name: {}", img_path.rsplit("/").next().unwrap());
println!("Size: {}x{}", img.width(), img.height());
// 1. Use Iterator
let res_img = //
image::GrayImage::from_vec(img.width(), img.height(),
img.pixels()
.map(|&p| ((p.data[0] as u32 + p.data[1] as u32 + p.data[2] as u32) / 3) as u8)
.collect::<_>()).unwrap();
res_img.save("./result_iter.png").unwrap();
// 2. Interpret function as Image
let grayscale_filter = |u, v| {
let mut val = [0; 1];
if u < img.width() && v < img.height() {
let pix = img.get_pixel(u, v); // panics if out of the bounds
val[0] = ((pix.data[0] as u32 + pix.data[1] as u32 + pix.data[2] as u32) / 3) as u8;
}
return image::Luma(val);
};
let res_img = image::GrayImage::from_fn(img.width(), img.height(), grayscale_filter);
res_img.save("./result_fn.png").unwrap();
// 3. As usual
let mut res_img = image::GrayImage::new(img.width(), img.height());
for v in 0..img.height() {
for u in 0..img.width() {
let pix = img.get_pixel(u, v);
let val = [((pix.data[0] as u32 + pix.data[1] as u32 + pix.data[2] as u32) / 3) as u8; 1];
let gray = image::Luma(val);
res_img.put_pixel(u, v, gray);
}
}
res_img.save("./result_usual.png").unwrap();
// 4. Simply interpret as GrayImage
let img = image::open(&img_path).unwrap();
let res_img = img.to_luma();
res_img.save("./result_grayimage.png").unwrap();
}
view raw main.rs hosted with ❤ by GitHub

使い勝手はそれなりにいいですが,いくつかうーんと思う仕様もあります.
たとえばget_pixelは範囲外にアクセスした際にpanicしてしまいますが,RustなのだからOptionで返すなどしてほしいです.
またget_pixel時のオプションとして範囲外アクセス時の挙動(clamp, wrapなど,CUDAでいうところのアドレッシングモード)の指定ができるとよいですね.
他にもimage::Rgbで各要素に.r()/.g()/.b()でアクセスできるようにしたり,まだまだ開発途上感はありますが,PNGやJPEGを自前でエンコード/デコードしなくてよいのはそれだけで非常にありがたい事です.

FnMutを画像として読み込むことが出来るのも極めて興味深い点です.
テスト用の合成画像を作りたい場合や簡単なフィルタを適用したいときに便利かと思います.

最後に,image crateに限らず,もしライブラリのビルドに失敗する場合はrustup updateを適用してみてください.rustcのバージョン違いでビルドできていない可能性があります(一敗).

それではみなさん,Rustで楽しい画像処理ライフを.

Posted on: 2018年12月3日, by :