WSL2上のOpenCVでUSBカメラを利用してみる(C++)

WSL2上のOpenCVでUSBカメラを利用してみる(C++) WSL
スポンサーリンク

前回はXiaomiのモニターライトのレビューでしたが、再びWSL2+USBカメラネタです。

今回はWSL2上のOpenCVを使ってUSBカメラから映像をキャプチャしてみたいと思います。

なお、WSL2とVS CodeでC++を利用する方法については下記で紹介しています。

WSL2とVS Codeの環境でOpenCVを利用してみる(C++)
今回はVisual Studio Code (VS Code)とWSL2を連携させた状態で、OpenCVを利用する簡単なプログラムをC++で作成してみます。ちゃんと設定すると、コーディング時はIntelliSenseによるアシストを受けることができます。また、WSL2がWSLgに対応していれば、そのまま実行・デバッグを行うことができます。WSL2を活用することで、Linux PCを用意できない人もLinux向けOpenCVの開発をできそうです。
スポンサーリンク

WSL2とUSBカメラ

WSL2 (Windows Subsystem for Linux 2)はいわばWindows上の仮想PCです。そのため、本来はWindows PCに接続されたUSBデバイスを利用することはできません。

しかし、この制約はUSBIPD-WINを導入することによって回避することができます。

WSL2でUSBカメラを使う - 前編
今回はWSL2のUbuntuでUSBデバイスを認識できるようにしてみます。WSL2は仮想マシンのようなものなので物理的なUSBポートは有りませんが、USBIPD-WINというツールを使うことでWindowsに接続されたUSBデバイスを認識することができます。ただWSL2のLinux kernelにはUSBカメラのドライバが含まれていないため、USBカメラを使うにはもう一工夫必要です。
WSL2でUSBカメラを使う - 後編
今回はWSL2でUSBカメラを使えるようにしてみます。WSL2のLinux KernelはV4L2やUVCが有効になっていないため、USBカメラを利用するにはLinux Kernelを自分でコンパイルして差し替える必要があります。Linux Kernelを変更すればUSBIPD-WINと組み合わせることによってWSL2でUSBカメラを利用することができます。ただ転送速度に難があるようなのが残念です。

USBIPD-WINを使ってWSL2からUSBカメラを利用できるようにしても、ルート権限を持ったユーザのみしか使えないという問題もありますが、これはudevを使うことによりなんとかなります。

WSL2でのUSBカメラのパーミッションをなんとかする
今回はWSL2のLinuxでudevを有効にすることにより、USBカメラを利用する際のパーミッション問題を解決してみます。最近のWSL2では起動時に実行するコマンドを簡単に設定できるので、起動時にudevを有効にすることも簡単です。WSL2でUSBデバイスを使う予定がある方は設定しておくことをオススメします。

今回の記事ではこれらの対応をしている前提で行います。

スポンサーリンク

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カメラを使う - 後編
今回はWSL2でUSBカメラを使えるようにしてみます。WSL2のLinux KernelはV4L2やUVCが有効になっていないため、USBカメラを利用するにはLinux Kernelを自分でコンパイルして差し替える必要があります。Linux Kernelを変更すればUSBIPD-WINと組み合わせることによって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」にしたときでした。

guvcviewの設定画面

ということで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, …)の行です。

このソースコードをコンパイルして実行してみたところ・・・・

OpenCVでのキャプチャ

USBカメラの映像を表示できました!

ちなみに、USBカメラの映像が表示されているウィンドウで何らかのキーを押せばこのプログラムは終了します。

フォーマットをMotion JPEGにしておくと、フレームレートは30fpsにしても大丈夫そうでしたが、解像度を640×480に上げるとまたUSBカメラの映像を取得できなくなってしまいました。

私の環境ではWSL2上で安定してキャプチャできるのは320×240程度のようです。

まとめ

今回はWSL2上のOpenCVでUSBカメラから映像をキャプチャしてみました。

WSL2でUSBカメラを利用するためにはUSBIPD-WINという仕組みを使う必要があり、このせいかUSBの通信速度がかなり遅くなっています。

このためUSBカメラから映像を取得する際には解像度を下げ、フォーマットをMotionJPEGとする必要がありました。

次回はWSL2でのUSBカメラ映像のキャプチャを改善するためにストリーミングを試してみます。

コメント

タイトルとURLをコピーしました