前回はXiaomiのモニターライトのレビューでしたが、再びWSL2+USBカメラネタです。
今回はWSL2上のOpenCVを使ってUSBカメラから映像をキャプチャしてみたいと思います。
なお、WSL2とVS CodeでC++を利用する方法については下記で紹介しています。
WSL2とUSBカメラ
WSL2 (Windows Subsystem for Linux 2)はいわばWindows上の仮想PCです。そのため、本来はWindows PCに接続されたUSBデバイスを利用することはできません。
しかし、この制約はUSBIPD-WINを導入することによって回避することができます。
USBIPD-WINを使ってWSL2からUSBカメラを利用できるようにしても、ルート権限を持ったユーザのみしか使えないという問題もありますが、これはudevを使うことによりなんとかなります。
今回の記事ではこれらの対応をしている前提で行います。
OpenCVとUSBカメラ
OpenCVではビデオカメラに対してVideo for Linux 2(V4L2)というフレームワークを通してアクセスすることができます。
これはcv::VideoCaptureのインスタンスを、APIファンレンスcv::CAP_V4L2を指定してオープンすることによって行えます。
USBカメラから映像を取得して表示するOpenCV(C++)のサンプルコードは次のようになります。
#include <string> #include <iostream> #include <opencv2/imgcodecs.hpp> #include <opencv2/highgui.hpp> int main(int argc, char* argv[]) { const std::string window_name("OpenCV Sample"); cv::VideoCapture cap(0, cv::CAP_V4L2); if ( !cap.isOpened() ) { std::cerr << "Can't open capture device" << std::endl; return -1; } cv::Mat frame; while (1) { cap >> frame; if ( frame.empty() ) { std::cerr << "Fail to capture video" << std::endl; break; } cv::imshow(window_name, frame); if ( cv::waitKey(33) >= 0 ) { break; } } return 0; }
cv::VideoCaptureのインスタンスcapを作成する際に、cv::CAP_V4L2を指定していることがわかります。
capを作成する際の第一引数はカメラのインデックスです。接続されているUSBカメラが1個であれば、指定するインデックスは「0」になります。
V4L2とUVCが有効なLinuxカーネルを利用していれば、これでcapがUSBカメラと結びつけることができます。
あとはこのように作成したcapにたいして「>>演算子」を適用するとUSBカメラから映像をキャプチャすることができます。
サンプルコードの実行
OpenCVでのUSBカメラの使い方がわかったところで、このサンプルコードをWSL2で試してみましょう。
もちろん事前準備として、Windows PCに接続したUSBカメラをUSBIPD-WINにアタッチしておきます。
サンプルのソースコードをビルドして実行してみると・・・エラーになりました。
[ WARN:0] global ../modules/videoio/src/cap_v4l.cpp (998) tryIoctl VIDEOIO(V4L2:/dev/video0): select() timeout. Fail to capture video
エラーを起こしているのは
cap >> frame;
の部分です。
OpenCV的にはUSBカメラをオープンできたのですが、そこからデータを取り込むことができないという状況のようです。
コードの修正
残念ながらシンプルなサンプルコードでは、WSL2上のOpenCVではUSBカメラの映像を取得することができませんでした。
サンプルコードを修正してなんとかUSBカメラの映像を取得するようにしてみたいと思います。
解像度とフレームレートを下げる
以前の記事でguvcviewというツールでWSL2上でUSBカメラの映像を表示させたところ、解像度やフレームレートを下げないと映像が表示されないという現象がありました。
この現象は、WSL2でUSBカメラを使うために、USBをIPに変換しているのが原因と思います。
このことから考えると、WSL2上のOpenCVでUSBカメラの映像を取得する際にも、解像度を下げたり、フレームレートを下げたりする必要がありそうです。
そこで解像度を320×240に、フレームレートを15fpsにしてみます。ソースコードは次のようになります。
#include <string> #include <iostream> #include <opencv2/imgcodecs.hpp> #include <opencv2/highgui.hpp> int main(int argc, char* argv[]) { const std::string window_name("OpenCV Sample"); cv::VideoCapture cap(0, cv::CAP_V4L2); if ( !cap.isOpened() ) { std::cerr << "Can't open capture device" << std::endl; return -1; } cap.set(cv::CAP_PROP_FRAME_WIDTH, 320); cap.set(cv::CAP_PROP_FRAME_HEIGHT, 240); cap.set(cv::CAP_PROP_FPS, 15); cv::Mat frame; while (1) { cap >> frame; if ( frame.empty() ) { std::cerr << "Fail to capture video" << std::endl; break; } cv::imshow(window_name, frame); if ( cv::waitKey(66) >= 0 ) { break; } } return 0; }
解像度とフレームレートを変更するために追加したのは、中盤あたりの3つのcap.set()です。また、15fpsに合わせてcv::waitKey()の待ち時間を33msから66msに変更しています。
このコードをコンパイルして実行してみたところ・・・結果は変らず
[ WARN:0] global ../modules/videoio/src/cap_v4l.cpp (998) tryIoctl VIDEOIO(V4L2:/dev/video0): select() timeout. Fail to capture video
となってしまいました。
解像度とフレームレートを変更しただけではダメなようです。
フォーマットをMotion JPEGに変更する
「guvcview」でUSBカメラの映像を表示したときを思い出すと、うまくいったときはカメラの出力を「MJPG – Motion-JPEG」にしたときでした。
ということでOpenCVでUSBカメラの映像を取得する際にもMotion JPEGを指定してみます。ソースコードは次のようになります。
#include <string> #include <iostream> #include <opencv2/imgcodecs.hpp> #include <opencv2/highgui.hpp> int main(int argc, char* argv[]) { const std::string window_name("OpenCV Sample"); cv::VideoCapture cap(0, cv::CAP_V4L2); if ( !cap.isOpened() ) { std::cerr << "Can't open capture device" << std::endl; return -1; } cap.set(cv::CAP_PROP_FRAME_WIDTH, 320); cap.set(cv::CAP_PROP_FRAME_HEIGHT, 240); cap.set(cv::CAP_PROP_FPS, 15); cap.set(cv::CAP_PROP_FOURCC, cv::VideoWriter::fourcc('M', 'J', 'P', 'G')); cv::Mat frame; while (1) { cap >> frame; if ( frame.empty() ) { std::cerr << "Fail to capture video" << std::endl; break; } cv::imshow(window_name, frame); if ( cv::waitKey(66) >= 0 ) { break; } } return 0; }
追加したのはcap.set(CV::CAP_PROP_FOURCC, …)の行です。
このソースコードをコンパイルして実行してみたところ・・・・
USBカメラの映像を表示できました!
ちなみに、USBカメラの映像が表示されているウィンドウで何らかのキーを押せばこのプログラムは終了します。
フォーマットをMotion JPEGにしておくと、フレームレートは30fpsにしても大丈夫そうでしたが、解像度を640×480に上げるとまたUSBカメラの映像を取得できなくなってしまいました。
私の環境ではWSL2上で安定してキャプチャできるのは320×240程度のようです。
まとめ
今回はWSL2上のOpenCVでUSBカメラから映像をキャプチャしてみました。
WSL2でUSBカメラを利用するためにはUSBIPD-WINという仕組みを使う必要があり、このせいかUSBの通信速度がかなり遅くなっています。
このためUSBカメラから映像を取得する際には解像度を下げ、フォーマットをMotionJPEGとする必要がありました。
次回はWSL2でのUSBカメラ映像のキャプチャを改善するためにストリーミングを試してみます。
コメント