2025/03/01更新
2022年5月に作成した記事を、Windows 11 + Ubuntu 24.04 LTSの環境で再検証して更新しました。
前回はXiaomiのモニターライトのレビューでしたが、再びWSL2+USBカメラネタです。
今回はWSL2上のOpenCVを使ってUSBカメラから映像をキャプチャしてみたいと思います。
なお、WSL2とVS CodeでOpenCVを利用する方法については下記で紹介しています。

WSL2とUSBカメラ
WSL2 (Windows Subsystem for Linux 2)はいわばWindows上の仮想PCです。そのため、本来はWindows PCに接続されたUSBデバイスを利用することはできません。
しかし、この制約はUSBIPD-WINを導入することによって回避できます。


今回の記事ではこの対応をしている前提で行います。
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カメラを使うために、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」にしたときでした。
ということで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, …)」の行です。
このソースコードをコンパイルして実行してみたところ・・・・
USBカメラの映像を表示できました!
ちなみに、USBカメラの映像が表示されているウィンドウで何らかのキーを押せばこのプログラムは終了します。
フォーマットをMotion JPEGにしておくと、フレームレートは30fpsにしても大丈夫そうでしたが、解像度を640×480に上げるとまたUSBカメラの映像を取得できなくなってしまいました。
私の環境ではWSL2上で安定してキャプチャできるのは320×240程度のようです。
まとめ
今回はWSL2上のOpenCVでUSBカメラから映像をキャプチャしてみました。
WSL2でUSBカメラを利用するためにはUSBIPD-WINという仕組みを使う必要があり、このせいかUSBの通信速度がかなり遅くなっています。
このためUSBカメラから映像を取得する際には解像度を下げ、フォーマットをMotionJPEGとする必要がありました。
次回はWSL2でのUSBカメラ映像のキャプチャを改善するためにストリーミングを試してみます。
コメント