概要

  • 最近勉強がてらRustを触り初めて、Rust * 画像処理はまだまだ日本語資料が少ないので防備録がてら残しておく
    • pngとかの画像から読み込んで画像処理しているのは結構ヒットするんだけど、カメラ入力のものがあんまりヒットしないので
  • やったことはopencvのhello world程度なので悪しからず
    • カメラの入力画像を取ってきて
    • Image crateにconvertして
    • 各pixel操作の画像処理としてgray scale化
    • 画像全体への操作として二値化
  • https://github.com/scepter914/camera-image-processing-template 実装とかはここに置いた
    • 得られた知見としては、画像サイズが小さくなるとimage::RgbImage::from_vec よりもimage::RgbImage::from_rawを使った方が高速

実装

使ったlibraryについて

  • image, imageproc
  • rscam
    • https://github.com/loyd/rscam
    • カメラ画像を取り込むインターフェイスとして選択
    • 他にもlibv4l-rs, nokhwa, uvc, camera_capture とかが見つかって一通り眺めてみたけど、rscamが一番シンプルに触れそうと思ったので採用

カメラ入力

    let device = "/dev/video0";
    let mut camera = rscam::new(device).unwrap();
    let width = 1920;
    let height = 1080;
    let fps = 50;
    // let width = 640;
    // let height = 360;
    // let fps = 330;
    camera
        .start(&rscam::Config {
            interval: (1, fps),
            resolution: (width, height),
            format: b"RGB3",
            ..Default::default()
        })
        .unwrap();

画像処理部分

  • お馴染みのloopで回すところ
    • captureでカメラのframeを取ってきて
    • image::RgbImageに突っ込んで
    • 画像処理する
    • っていうだけ
    loop {
        let frame = camera.capture().unwrap();
        let rgb_image = image::RgbImage::from_vec(width, height, (&frame[..]).to_vec()).unwrap();
        let gray_image = rgb_to_gray(&rgb_image);

        let otsu_level = imageproc::contrast::otsu_level(&gray_image);
        let binarized_image = imageproc::contrast::threshold(&gray_image, otsu_level);
    }

画像をとってくる

  • パッとdocumentを眺めた感じ image::RgbImage::from_vec と image::RgbImage::from_rawが使えそう
    let rgb_image_raw = image::RgbImage::from_raw(width, height, (&frame[..]).to_vec()).unwrap();
    let rgb_image_vec = image::RgbImage::from_vec(width, height, (&frame[..]).to_vec()).unwrap();
  • 気になったのでベンチマークを取ってみた(結果はまとめて下に)
    println!("from_raw");
    let bench = Benchmark::set_start_time();
    let rgb_image_raw = image::RgbImage::from_raw(width, height, (&frame[..]).to_vec()).unwrap();
    bench.print_bench_time();
    rgb_image_raw.save("data/from_raw.png").unwrap();

    println!("from_vec");
    let bench = Benchmark::set_start_time();
    let rgb_image_vec = image::RgbImage::from_vec(width, height, (&frame[..]).to_vec()).unwrap();
    bench.print_bench_time();
    rgb_image_vec.save("data/from_vec.png").unwrap();

グレースケール化

fn rgb_to_gray(rgb_image: &image::RgbImage) -> image::GrayImage {
    let width = rgb_image.width();
    let height = rgb_image.height();
    let mut gray_image = image::GrayImage::new(width, height);
    // gray scale
    for i in 0..width {
        for j in 0..height {
            let pixel = rgb_image.get_pixel(i, j);
            let gray_pixel = [((pixel[0] as f32 * 0.2126) as u32
                + (pixel[1] as f32 * 0.7152) as u32
                + (pixel[2] as f32 * 0.0722) as u32) as u8; 1];
            gray_image.put_pixel(i, j, image::Luma(gray_pixel));
        }
    }
    return gray_image;
}

二値化

  • 大津の二値化を使っているだけ
        let otsu_level = imageproc::contrast::otsu_level(&gray_image);
        let binarized_image = imageproc::contrast::threshold(&gray_image, otsu_level);

結果

  • 入力画像
    • 因みにこういった簡単な試験の時に、100均とかで買える単色シールは色チェックに使えて便利です
  • グレースケール
  • 二値化画像
  • ベンチマークの結果
    • 画像サイズが小さくなるとimage::RgbImage::from_vec よりもimage::RgbImage::from_rawを使った方が高速なことが分かる
Camera /dev/video0: 1920 * 1080, 50 FPS
capture
Process 5.379 msec
from_raw
Process 0.591 msec
from_vec
Process 1.562 msec
rgb to gray
Process 12.432 msec
otsu binarize
Process 4.265 msec
Camera /dev/video0: 640 * 360, 330 FPS
capture
Process 1.002 msec
from_raw
Process 0.130 msec
from_vec
Process 0.058 msec
rgb to gray
Process 0.775 msec
otsu binarize
Process 0.194 msec

感想

  • rust初心者なので間違っているところ等あったらgithub, twitter等で教えていただければ幸いです
  • 結構ひいこら言いながらrustを実装してましたが、割と楽しいので今後も少しずつやっていこうと思います(続くといいなぁ)