あいさつ

こんにちは.最近の新月期の土日はなかなかぱっとしない天気ですね.こんな時は遠征記でも書きたいのですが,タイミング的にまだ違うと思ったので,代わりの記事になります.今流行り(?)のRustを使った小ネタです.

Rustとは

Rustについて言及するのはこのブログでは初めてですが,Rustとは,FireFox等を開発するMozillaによる比較的新しいプログラミング言語です.メモリ安全性,C,C++等の言語が吐くバイナリに匹敵する速度,安全性によってもたらされる並行性などに焦点をあてながら開発されており,現在人気の高いプログラミング言語の一つになっています.実は私の研究で開発に携わっているフレームワークはこの言語で開発されています.

rawloader Crateとは

Crateというのは他の言語でいうところのライブラリのようなものです.crates.ioというページで公開されていて,このCrateがあることで,プログラムの依存ライブラリの管理が容易になっています.その中で,rawloader crateはその名の通り,様々なカメラ製品から出力されるRaw画像や,付随するメタデータを読み込むためのライブラリです.著作権はpedrocr氏にあります.Python等でも同様のライブラリはあるようですが,Rustではこれくらいしかなさそうなので,実際に読み込んで遊んでみたいと思います.

実際にRawを読んでみる

githubにあるサンプルプログラム通りに書いてみますが,自分の場合Canon EOS 6Dから出力されるRaw画像を扱うので,ほんのちょっと工夫が必要です.
以下がプログラムです.

// main.rs
// c.f. https://github.com/pedrocr/rawloader
use std::env;
use std::fs::File;
use std::io::prelude::*;
use std::io::BufWriter;

extern crate rawloader;

fn main() {
    let args: Vec<_> = env::args().collect();
    if args.len() != 2 {
        println!("Usage: {} [file]", args[0]);
        std::process::exit(2);
    }
    let file = &args[1];
    let image = rawloader::decode_file(file).unwrap();
    println!("{}", image.make);
    println!("{}", image.model);
    println!("{}, {}", image.width, image.height);
    println!("{}", image.cpp);

    let mut f = BufWriter::new(File::create(format!("{}.ppm", file)).unwrap());
    let preamble = format!("P3\n{} {}\n{}\n", image.width, image.height, 16383).into_bytes(); //.CR2 -> 14bit
    f.write_all(&preamble).unwrap();
    let mut counter = 0;
    if let rawloader::RawImageData::Integer(data) = image.data {
        for pix in data {
            // interpret data as RG/GB Bayer Array
            if (counter / image.width) % 2 == 0 {
                if (counter - (counter / image.width) * image.width) % 2 == 0 {
                    let d = format!("{} 0 0\n", pix).into_bytes();
                    f.write_all(&d).unwrap();
                } else {
                    let d = format!("0 {} 0\n", pix).into_bytes();
                    f.write_all(&d).unwrap();
                }
            } else {
                if (counter - (counter / image.width) * image.width) % 2 == 0 {
                    let d = format!("0 {} 0\n", pix).into_bytes();
                    f.write_all(&d).unwrap();
                } else {
                    let d = format!("0 0 {}\n", pix).into_bytes();
                    f.write_all(&d).unwrap();
                }
            }
            counter += 1;
        }
    } else {
        eprintln!("Don't know how to process non-integer raw files");
    }
}

if let rawloader::RawImageData::Integer(data) = image.data
で画像データ(image.data)を新しいデータ(data)に束縛しています.image.dataの型はrawloader::RawImageDataで,これをInteger(Vec<u16>)と解釈します.なのでInteger(data)のdataの型はVec<u16>ですね.他にも,ドキュメンテーションを見るとRawImageDataはFloat(Vec<f32>)もサポートしているっぽいです.
6Dのデータは14bitなので,1画素あたり0~16383までの値が16bitのメモリ領域に収まっています.あとはこれをベイヤー配列の色(RG/GB)に変換して,書き出しています.もしこれを単に値として書き出して,プリアンブル部をP3からP2(グレースケール)とすれば単に輝度のモノクロベイヤー配列が得られるでしょう.出力形式は今のところ理解しやすいようにテキスト(PNMフォーマット)なので容量はめっちゃ増えます(20MB→100MBくらい).

使い方

ビルドして,

$ [binary file] [raw file]

しばらく待つと[raw file].ppmができます.

結果

テキトーなRawを突っ込んでGIMPで開いてみました.いい感じですね.

GIMP(上記プログラムから吐かれた.ppmファイル)
gimp
一つわからないのが,画像左と上に謎の暗部ができてしまっていることです.どうやら上記プログラムでサイズ(image.widthとimage.height)が正しく読めていないっぽいのですが,理由がよくわかりません.

SI8(元の.CR2ファイル)
si8

ベイヤー(GIMPで開いた画像を拡大)
bayer

ちゃんとベイヤー配列も得られていることがわかりますね.

TODO! デモザイクもしてみる

ベイヤーデータが得られたのでデモザイキングをすればカラー画像に変換できそうです.今後やってみたいと思います.