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

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

2025/03/01更新

2022年5月に作成した記事を、Windows 11 + Ubuntu 24.04 LTSの環境で再検証して更新しました。

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

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

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

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カメラを利用することができます。ただ転送速度に難があるようなのが残念です。

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

スポンサーリンク

OpenCVとUSBカメラ

OpenCVではビデオカメラに対してVideo for Linux 2(V4L2)というフレームワークを通してアクセスできます。

これはcv::VideoCaptureのインスタンスを、APIプリファレンスとしてcv::CAP_V4L2を指定してオープンすることによって行えます。

USBカメラから映像を取得して表示するOpenCV(C++)のサンプルコードは次のようになります。

#include <string>
#include <iostream>
#include <opencv2/videoio.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::namedWindow(window_name, cv::WINDOW_GUI_NORMAL);
   
    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を使ってWSL2に接続しておきます。

サンプルのソースコードをビルドして実行してみると・・・エラーになりました。

[ WARN:0@10.236] global ./modules/videoio/src/cap_v4l.cpp (1013) 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/videoio.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::namedWindow(window_name, cv::WINDOW_GUI_NORMAL);

    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@10.253] global ./modules/videoio/src/cap_v4l.cpp (1013) 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/videoio.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::namedWindow(window_name, cv::WINDOW_GUI_NORMAL);

    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をコピーしました