Raspberry Pi+OpenCVでのUSBカメラフレーム取得速度を向上したい

OpenCV,Programing,Python,Raspberry Pi,Tips

Raspberry PiOpenCVを導入し、USBカメラを動かしたいわけですが
この辺の手法は、他でも散々紹介されたおしてるのでこの記事では書きません。
OpenCVの導入もスルーします。

すでにOpenCVも入ってるし、USBカメラから画像の取り込みくらいはできたよ!
っていう前提で話を進めます(

処理時間の問題

さて、Raspberry PiでUSBカメラのフレームを1枚1枚取得していると皆さん思うところがあると思います。

「遅くね?」

OpenCVから確認、設定できるFourCC(画像の圧縮方法などを指定できる)を見ると
大体のカメラはYUYV、MJPGは使えるかと。
YUYVに関しては論外なくらい遅く、MJPGだと解像度やFPSの設定次第ではギリ使えるかなくらい。
H264などが使えればかなり早くなると思います。まぁ圧縮されてるので画質はそれなりに落ちますが。
Raspberry Pi(私の環境は3 Model B+)ですが、これのUSBがボトルネックになっているっていう話もちらほら出てます。
これに関してはもうソフト的にはどうしようもないのですが
read()メソッドで読みだしたフレームを画像として保存するときに使用するcv2.imwrite()がまた遅いと。
あと、私の場合読み込んだフレームを一定長のリングバッファで管理する必要があったのでそのバッファアクセスもまた時間がかかると。

ということで、これらの処理を高速化します。

方法としては

  1. threading.Threadを使う
  2. multiprocessing.Processを使う

のどちらかかなと。

threading.Threadを使っての高速化

これは意外とすんなり書けます。threadingで色々書いたことある人なら多分困らない。
ただ問題は、pythonのthreadは"並列処理"ではなく"並行処理"である点。
言ってしまえばpythonのthreadに処理時間を早くする効果はないってことです。*1

なので、別に動作するスレッドが増えれば増えるほど別のスレッドの動作時間に影響されフレームレートがどんどん落ちるということに。

multiprocessing.Processを使っての高速化

というわけでthreadingをやめてmultiprocessingで書こうとすると
スレッドのときとは違いうまく動いてくれませんでした。

私が最初に書いたソースだと
メインプロセスでサブプロセスの生成とOpenCVでのカメラ設定を行い、
サブプロセスでカメラのフレームを読み込むという動作だったのですが
どうやらプロセスが変わると読み込みがうまく動かないようです。
つまり、サブプロセス側でカメラの設定やらをする必要があります。

なので、途中でカメラの設定変更などをしたい場合もサブプロセス側ですることになります。メイン側から参照しようとするとAtribute Errorになります。

つまり、Pipeなどを使ってメインプロセスからサブプロセスに対して、設定変更の内容などを送り、サブプロセスでそれを受け取ったら設定の変更。
ということをしないといけません。

実際に書いたソースはあまりにも長いので割愛します()

ちなみにフレームレートの方は、カメラ設定を同一条件にした場合でも倍近く向上しています。
元が5~10くらいしか出てなかったのがしょぼい
解像度を下げてやれば30fpsまでなんとか出るようになりました。

*1:並列処理と並行処理の違いなどはこちらのサイトが詳しく書いてくれています。
Pythonの並列処理・並行処理をしっかり調べてみた – Qiita