Rustでスクリーンショットを撮影して そのままOpenCVで扱う
はじめに
自分の中で、Rust と OpenCV(というか画像処理) が いつかやってみたいものリスト にあったので、
なんとなく同時に入門してみたんですが、何気にここでハマったのでひとつのTipsとして共有します。
主な登場クレート
↑スクリーンショットを撮れるライブラリを探していたら、割とこれくらいしか使えそうなのが見つからなかったのでこれを使った。
↑僕は画像処理の知見を何も持っていないので、できるだけ情報がインターネットに溢れているものを使いたく、OpenCVの(特にC++の)情報をかなり近い形で流用できるこれを使った。
やりたかったこと
- スクリーンショットを撮影する
- 撮影したスクリーンショットを OpenCV の Mat として扱う
- あとはよしなに画像を処理する
ただし、諸事情によりなるだけ早くこのループを回したかったので、可能な限り余計なI/Oは発生させたくない
どうやったらできたか(結論)
スクリーンショットを撮る
// 接続されているディスプレイをすべて取得する
let screens = Screen::all();
// プライマリなディスプレイを取得する
let main_screen = screens.unwrap()[0];
// キャプチャを取得する
let capture: Image = main_screen.capture().unwrap();
// ベクタとして画像を取得する
let buffer: &Vec<u8> = capture.buffer();
Mat に変換する
let img: Mat = imdecode(&VectorOfu8::from_iter(buffer), 1).unwrap();
注: 第ニ引数の flags
についてはここでは意識していない
何が厄介だったか
screenshots における Image 型は、データが内部で png にエンコードされているのが罠でした。
(以下ファイルの先頭を見ればわかりますが、ここでいう Encoder
は png::Encoder
です)
取得したスクリーンショットのデータが入っているはずのベクタが妙に短くて、(少なくとも1920x1080x3とかになると予想してたのに)
実行ごとに(画面の状況ごとに) len
も capacity
も変わるのでさてはエンコードされてるだろ!と思って気づきました。
しかも、エンコードしていない状態で取り出す口がどうやらなさそうなんですよね。
単純に BGR とかが入っている配列なりベクタなりがあれば、それを Mat.data
にねじこむ方法に絞って探せば良いかなと思ったんですが、生データじゃないなら作戦を変える必要がありました。
で、それがわかった上でどこにハマったか
実を言うと、 imread
こそが OpenCV で png を扱う方法である という思い込みがありました。
そうすると、
(screenshotsの仕事)スクリーンショットを撮影する→ファイルに画像を書き出す
(OpenCVの仕事)ファイルから画像を読み出す→Matに格納する
になってしまうので「嫌だなぁ…」と思ったところ、 imdecode
なるものを知りました。
しかし第一引数の要求が buf: &dyn core::ToInputArray
だったもので、
Rust 初学者の自分には &Vec<u8>
からどう変換するかもわからず、ハマってしまいました。
同時に複数の難しいものに入門するべきではないですね。
余談
先述
ただし、諸事情によりなるだけ早くこのループを回したかったので、可能な限り余計なI/Oは発生させたくない
したように、
Rust と OpenCV に同時に入門した上で僕がやってみたいことというのは、結構な実時間性が求められます。(僕が求めています)
ただ、試しに今できているところまでで1ループを回してみたら スクリーンショット→画像処理 のループが 5秒くらいかかりました。
「Rust は早いと聞いていたけど、そんなもんかな」と思ったんですが
全く同じことを言っている人がいました。リリースモードなるものがあるんですね。
cargo run --release
覚えました。
0.2秒以内に処理が完了しました。
「pngにエンコード→デコード」を挟んだことを加味しても、これなら許容範囲です。
Discussion