OpenCVでのオーバーヘッドの少ない静止画像撮影方法
はじめに
Amazon Rekognition を使った顔認識システムを検討しています。
- Raspberry Pi 3 とカメラモジュールと OpenCV を使う
- USBで接続したキーボードのEnterキーを押したら撮影
- ASWへのリクエストで1秒程度必要になるので、撮影に要する時間はなるべく短く
シャッターボタン(Enterキー)を押してから画像データを取得するまでの遅延をなるべく小さくするためには、OpenCVでの処理にある程度のコツが必要なことが分かったのでこの記事にまとめてみました。
Raspberry Pi の準備
- Raspbeian Buster with desktop をインストール
- raspi-config を起動して Interfacing Options → Camera で カメラインタフェースを有効化
- apt-get install python-opencv
ダメダメな例
最初にうっかり書いてしまったバージョンです。
raw_input で入力待ちをして、入力があれば VedeoCapture オブジェクトを生成して read で画像を1枚読み込む、という処理を無限ループします。 一応動きますが、毎回 VideoCapture を作っているので遅い(キー入力から画像取得まで1秒弱)です。
さらに、後述の通り2回目以降正しく動作しません。
ダメな例
ループの外で VideoCapture を生成して使い回すようにしました。VideoCapture生成処理の分(0.4秒程度)早くなりました。
ただし、上のダメダメな例でもそうでしたが、2回目以降のシャッターは正しく動作しません。
- 顔をカメラに向けた状態で起動
- キー入力する
- 顔が写った画像が記録される
- 顔をフレームから外す
- キー入力する
- 顔が写った画像が記録される
といった具合です。2回目以降はキー入力された時点の映像がキャプチャされません。
正しいキャプチャの方法
VideoCapture を使うサンプルコードは世に溢れてますが、そのほとんどは動画のすべてのフレームを漏れなく処理するには、のようなパターンで、処理をブロックしておいて何らかのトリガで1枚だけキャプチャをするという今回のような使い方はあまりしないようです。
改めて VideoCapture の仕様を調べてみると、フレームレートに応じた頻度で read を呼び出して VideoCapture 内部のフレームを進めないと、過去のフレームを読み込んでしまうことになるようです。要するに、ダメな例のコードでは read の回数が少なすぎた訳です。
したがって、以下のような動作が必要になります。
- 適切なウェイト(50msec程度)を入れつつreadを繰り返し実行
- キー入力が発生したら最後に読み込んだ画像をメインの処理に渡す
こういう場合の定石は cv2.imshow して cv2.waitKey でタイムアウト有りのキー入力待ちをすることです。タイムアウトを50msecに設定すれば、キー入力が無ければ50msec毎に cv2.read を実行できます。
もっと動作を軽く
上の定番のやり方の場合、imshowするのでそれなりにCPU負荷が掛かります。カメラに映っている映像を表示しなくても良い用途のためにもっと軽い方法も欲しくなります。
select を使ってタイムアウト付きキー入力関数を自前で用意しました。
おわりに
imshowとwaitKeyを使う版だとほとんど何もしていないときでもCPU使用率が20%強になりますが、動画表示しない版だと3%程度まで抑えることができました。また、プログラムの構成上キー入力の前に画像読み込みが終わっているので、処理の遅延をなるべく小さくという意味でも非常に良いものになっています。