WSL2上のOpenCVでUSBカメラつかう (FFMPEGによるストリーミング)

WSL2上のOpenCVでUSBカメラつかう (FFMPEGによるストリーミング) WSL
スポンサーリンク

2025/03/14更新

2022年5月に作成した記事を、Windows 11 + Ubuntu 24.04 LTSの環境を念頭に更新しました。

前回はWSL2上のOpenCVでUSBカメラを利用する方法を紹介しました。この記事ではWSL2でUSBカメラを使うためにUSBIPD-WINというツールを使いましたが、いろいろ課題がありました。

そこで今回はこの課題の解消を試みたいと思います。

スポンサーリンク

USBIPD-WIN利用時の課題

WSL2は仮想環境のようなものでUSBデバイスを直接利用することができません。

そこでUSBIPD-WINというツールを使って、USBデータをIPに変換したうえでWSL2に転送し、WSL2側でIPからUSBデータを復元することでUSBデバイスを利用できるようになります。

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

これでWSL2でUSBカメラを利用できるようになるのですが、試したところ次のような課題があると感じました。

  • 環境構築が面倒
    USBIPD-WINの導入と、Linux Kernelのコンパイルが必要
  • 事前設定が面倒
    Windowsの管理者権限でUSBカメラを共有する必要がある
  • USBのスループットが低い
    解像度やフレームレートを下げないとUSBカメラの映像をWSL2で取得できない

全部を改善するのは無理かもしれませんが、なんとか改善する方法を考えたいと思います。

スポンサーリンク

ストリーミングの利用

そこで試してみたのはUSBカメラの映像をWindowsからWSL2へストリーミングする方法です。

参考にしたのはこちらのサイトです。

Use Webcams in WSL with OpenCV & FFmpeg [Fix 2025]
Use Webcams in WSL with OpenCV & FFmpeg - Funvision opencv C++ tutorials

この元ネタのサイトを参考にしつつ、次のようなシステムを構築してみます。

USBカメラはWindows PCに接続する

Windows PCからWSL2のLinuxへUSBカメラの映像をストリーミングする

WSL2のLinuxで動作するOpenCVではストリーミングされた映像を受信する

つまり、USBカメラから映像をキャプチャするのはWindows側で、そのキャプチャした映像をWSL2側にストリーミングすることで、WSL2のOpenCVでUSBカメラの映像(正確にはストリーミングされたUSBカメラの映像)を取得することを目指します。

Windows側の準備

Windowsにはストリーミングを行うソフトウェアとしてFFMPEGをインストールします。

FFMEPGは下記のサイトからダウンロードできます。

Download FFmpeg

「Get packages & executable files」でWindowsロゴを選び、表示された「Windows EXE Files」のリストからダウンロード先を選びます。

FFMPEGのダウンロード

ダウンロード先は2つありますが、私が見た感じ「Windows builds by BtbN」の方がわかりやすい気がしました。

「Windows builds by BtbN」には多数のファイルがあります。

FFMPEGのダウンロード

今回はWindowsで利用するので「win64」のものをダウンロードします。

バージョンは「master(最新のソースコード)」「n6.1」「n7.1」がありますが、最新のリリース版である「n7.1」にしてみました。

さらにライセンスとして「gpl」「lgpl」、リンク形式として「shared」「非shared(static)」が有ります。今回はライセンスはGPLで問題ないので「gpl」の「非shared」をダウンロードします。

ダウンロードしたzipファイルは展開して、「ffmpeg」というフォルダーに変えてCドライブ直下に移動しておきます。

さらに、Windowsの環境変数Pathにffmpegフォルダー内のbinフォルダーを追加します。

Pathへ追加

これでFFMPEGのインストールは完了です。PowerShell (Windows Terminal)を起動して「ffmpeg –version」で情報が表示されればOKです。

> ffmpeg -version
ffmpeg version n7.1-214-g71889a8437-20250228 Copyright (c) 2000-2024 the FFmpeg developers
built with gcc 14.2.0 (crosstool-NG 1.26.0.120_4d36f27)
configuration: --prefix=/ffbuild/prefix --pkg-config-flags=--static --pkg-config=pkg-config --cross-prefix=x86_64-w64-mingw32- --arch=x86_64 --target-os=mingw32 --enable-gpl --enable-version3 --disable-debug --disable-w32threads --enable-pthreads --enable-iconv --enable-zlib --enable-libfreetype --enable-libfribidi --enable-gmp --enable-libxml2 --enable-lzma --enable-fontconfig --enable-libharfbuzz --enable-libvorbis --enable-opencl --disable-libpulse --enable-libvmaf --disable-libxcb --disable-xlib --enable-amf --enable-libaom --enable-libaribb24 --enable-avisynth --enable-chromaprint --enable-libdav1d --enable-libdavs2 --enable-libdvdread --enable-libdvdnav --disable-libfdk-aac --enable-ffnvcodec --enable-cuda-llvm --enable-frei0r --enable-libgme --enable-libkvazaar --enable-libaribcaption --enable-libass --enable-libbluray --enable-libjxl --enable-libmp3lame --enable-libopus --enable-librist --enable-libssh --enable-libtheora --enable-libvpx --enable-libwebp --enable-libzmq --enable-lv2 --enable-libvpl --enable-openal --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopenh264 --enable-libopenjpeg --enable-libopenmpt --enable-librav1e --enable-librubberband --enable-schannel --enable-sdl2 --enable-libsnappy --enable-libsoxr --enable-libsrt --enable-libsvtav1 --enable-libtwolame --enable-libuavs3d --disable-libdrm --enable-vaapi --enable-libvidstab --enable-vulkan --enable-libshaderc --enable-libplacebo --disable-libvvenc --enable-libx264 --enable-libx265 --enable-libxavs2 --enable-libxvid --enable-libzimg --enable-libzvbi --extra-cflags=-DLIBTWOLAME_STATIC --extra-cxxflags= --extra-libs=-lgomp --extra-ldflags=-pthread --extra-ldexeflags= --cc=x86_64-w64-mingw32-gcc --cxx=x86_64-w64-mingw32-g++ --ar=x86_64-w64-mingw32-gcc-ar --ranlib=x86_64-w64-mingw32-gcc-ranlib --nm=x86_64-w64-mingw32-gcc-nm --extra-version=20250228
libavutil      59. 39.100 / 59. 39.100
libavcodec     61. 19.101 / 61. 19.101
libavformat    61.  7.100 / 61.  7.100
libavdevice    61.  3.100 / 61.  3.100
libavfilter    10.  4.100 / 10.  4.100
libswscale      8.  3.100 /  8.  3.100
libswresample   5.  3.100 /  5.  3.100
libpostproc    58.  3.100 / 58.  3.100

WSL2側の準備

WSL2のLinuxで導入する必要があるのはOpenCVとFFMPEGです。FFMPEGは事前の動作確認時に利用します。

OpenCVについては次のコマンドで導入できます。

sudo apt-get install libopencv-dev

FFMPEGのインストールは次のコマンドです。

sudo apt-get install ffmpeg

またストリーミングに必要な情報としてWSL2側のIPアドレスが必要です。WSL2のIPアドレスは次のコマンドで表示されます。

$ hostname -I
172.21.92.66

つまりWSL2側のIPアドレスは「172.21.92.66」となります。このIPアドレスはのちのち使うのでメモしておきます。

FFMPEGによるUSBカメラのストリーミング

それではWindowsにインストールしたFFMPEGを使ってUSBカメラの映像をストリーミングしてみましょう。

操作はWindowsのコマンドライン(PowerShell・Windows Terminal・コマンドプロンプト)で行います。

USBカメラ名の確認

まずUSBカメラの名前を次のコマンドで確認しましょう。

cmd /c "chcp 65001 & ffmpeg -sources dshow"

本来はffmpeg -sources dshowでよいのですが、USBカメラの名称が日本語の場合に文字化けしてしまいます。

文字化けを回避するために、cmd.exeを介してffmpegを実行しています。

私の場合は次のような結果となりました。

> cmd /c "chcp 65001 & ffmpeg -sources dshow"
Active code page: 65001
ffmpeg version n7.1-214-g71889a8437-20250228 Copyright (c) 2000-2024 the FFmpeg developers
  built with gcc 14.2.0 (crosstool-NG 1.26.0.120_4d36f27)
  configuration: --prefix=/ffbuild/prefix --pkg-config-flags=--static --pkg-config=pkg-config --cross-prefix=x86_64-w64-mingw32- --arch=x86_64 --target-os=mingw32 --enable-gpl --enable-version3 --disable-debug --disable-w32threads --enable-pthreads --enable-iconv --enable-zlib --enable-libfreetype --enable-libfribidi --enable-gmp --enable-libxml2 --enable-lzma --enable-fontconfig --enable-libharfbuzz --enable-libvorbis --enable-opencl --disable-libpulse --enable-libvmaf --disable-libxcb --disable-xlib --enable-amf --enable-libaom --enable-libaribb24 --enable-avisynth --enable-chromaprint --enable-libdav1d --enable-libdavs2 --enable-libdvdread --enable-libdvdnav --disable-libfdk-aac --enable-ffnvcodec --enable-cuda-llvm --enable-frei0r --enable-libgme --enable-libkvazaar --enable-libaribcaption --enable-libass --enable-libbluray --enable-libjxl --enable-libmp3lame --enable-libopus --enable-librist --enable-libssh --enable-libtheora --enable-libvpx --enable-libwebp --enable-libzmq --enable-lv2 --enable-libvpl --enable-openal --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopenh264 --enable-libopenjpeg --enable-libopenmpt --enable-librav1e --enable-librubberband --enable-schannel --enable-sdl2 --enable-libsnappy --enable-libsoxr --enable-libsrt --enable-libsvtav1 --enable-libtwolame --enable-libuavs3d --disable-libdrm --enable-vaapi --enable-libvidstab --enable-vulkan --enable-libshaderc --enable-libplacebo --disable-libvvenc --enable-libx264 --enable-libx265 --enable-libxavs2 --enable-libxvid --enable-libzimg --enable-libzvbi --extra-cflags=-DLIBTWOLAME_STATIC --extra-cxxflags= --extra-libs=-lgomp --extra-ldflags=-pthread --extra-ldexeflags= --cc=x86_64-w64-mingw32-gcc --cxx=x86_64-w64-mingw32-g++ --ar=x86_64-w64-mingw32-gcc-ar --ranlib=x86_64-w64-mingw32-gcc-ranlib --nm=x86_64-w64-mingw32-gcc-nm --extra-version=20250228
  libavutil      59. 39.100 / 59. 39.100
  libavcodec     61. 19.101 / 61. 19.101
  libavformat    61.  7.100 / 61.  7.100
  libavdevice    61.  3.100 / 61.  3.100
  libavfilter    10.  4.100 / 10.  4.100
  libswscale      8.  3.100 /  8.  3.100
  libswresample   5.  3.100 /  5.  3.100
  libpostproc    58.  3.100 / 58.  3.100
Auto-detected sources for dshow:
  @device_pnp_\\?\usb#vid_045e&pid_0779&mi_00#7&12243f&0&0000#{65e8773d-8f56-11d0-a3b9-00a0c9223196}\global [Microsoft® LifeCam HD-3000] (video)
  @device_cm_{33D9A762-90C8-11D0-BD43-00A0C911CE86}\wave_{BD418239-DA33-4ECF-BFF6-7D18DBE1C438} [デスクトップ マイク (2- Microsoft® LifeCam HD-3000)] (audio)
  @device_cm_{33D9A762-90C8-11D0-BD43-00A0C911CE86}\wave_{7C7417DC-DB94-446F-ACFD-5FEBB13F7D62} [ヘッドセット (2- OpenRun Pro by Shokz)] (audio)

重要なのは「Auto-detected sources for dshow」以降に表示されるUSBカメラの名前です。私の場合は「Microsoft® LifeCam HD-3000」です。

ストリーミングの開始

名前がわかったらFFMPEGによるUSBカメラのストリーミングを開始します。

FFMPEGによるストリーミングは次のコマンドで行います。

ffmpeg -f dshow -i video="Microsoft® LifeCam HD-3000" -vcodec mjpeg -r 15 -s 640x480 -b:v 10M -f mjpeg udp://172.21.92.66:1234

ここではストリーミングのフォーマットしてMotionJPEGを利用しています。そのため

以前は次のようにコーデックをH.264にして、MPEG-TSでストリーミングしていたのですが、OpenCVでのキャプチャに失敗することが多いようです。

ffmpeg -f dshow -i video="Microsoft® LifeCam HD-3000" -preset ultrafast -tune zerolatency -vcodec libx264 -r 15 -s 640x480 -b:v 10M -f mpegts -flush_packets 0 udp://172.21.92.66:1234

そのため安定して動作するMotionJPEGを利用するようにしました。

「video」で指定するカメラの名前は先ほど調べたものを利用します。

また「udp://172.21.92.66」の部分には、事前に調査したWSL2側のIPアドレスを指定します。

このコマンドでは、videoで指定したUSBカメラの映像を解像度640×480、フレームレート15fpsでストリーミングしています。フォーマットはMotionJPEG、そしてストリーミングはUDPを利用しています。

ビットレートは10Mbpsを指定していますが、この解像度とフレームレートなら実際にはもっと低くなるようです。

ビットレートを指定しないと、かなり画質が悪くなるので、ビットレートを指定するのがオススメです。

また、私のPCではフレームレートをこれ以上高くすると、遅延が大きくなってしまうようでした。

このコマンドを実行すると、FFMPEGは指定されたIPアドレスとポートに対して、UDPでストリーミングデータを送信を開始します。

ストリーミングの確認

FFMPEGによるストリーミングを開始したら、このストリーミングデータがWSL2側に届いているかどうかを確認してみましょう。

この確認はFFMPEGに付属する「ffplay」というコマンドを利用します。WSL2側のターミナルで

ffplay udp://@:1234

を実行してみましょう。

数秒待つとウィンドウが表示され、USBカメラの映像(=WindowsのFFMPEGでストリーミングされている映像)が表示されるはずです。

FFMPEGのストリーミングの確認

これでFFMPEGでストリーミングができることが確認できました。

WSL2上のOpenCVによるキャプチャ

ようやく本題です。

WSL上のOpenCVで上記のFFMPEGによるストリーミングをキャプチャするために、次のようなソースコードを作成します。

#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("udp://@:1234", cv::CAP_FFMPEG);

    if ( !cap.isOpened() ) {
        std::cerr << "Can't open capture device" << std::endl;
        return -1;
    }

    cv::namedWindow(window_name, cv::WINDOW_GUI_NORMAL | cv::WINDOW_AUTOSIZE);

    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(1) >= 0 ) {
            break;
        }
    }

    return 0;
}

このファイルをcapture-streaming.cppとして保存して、次のようにコンパイルします。

/usr/bin/g++ -I/usr/include/opencv4 -fdiagnostics-color=always capture-streaming.cpp -o capture-streaming -lopencv_core -lopencv_imgcodecs -lopencv_highgui -lopencv_videoio

ビルドに成功すると「capture-streaming」というファイル名の実行ファイルが生成されます。

そしてその実行ファイルを実行してみます。

./capture-streaming

2,3秒ほどすると「OpenCV Sample」というタイトルのウィンドウが表示され、そのウィンドウでUSBカメラの映像(=Windows上のFFMPEGがストリーミングしている映像)が表示されます。

OpenCVによるストリーミングのキャプチャ

もし「Can’t open capture device」というエラーになってしまう場合は

  • Windows上のFFMPEGでストリーミングを開始しているか?
  • WSL2上でストリーミングを受信しているプログラムはないか?(たとえば、テストに利用したffplay)

を確認してみてください。

まとめ

今回はWSL2上のOpenCVでUSBカメラの映像を取得するために、Windows上のFFMPEGを使ってストリーミングしてみました。

事前にストリーミングを開始する手間が必要ですが、USBIPD-WINを使ってWSL2にUSBデバイスを見せるよりも、高解像度の映像を取得できるのがメリットです。

ただし、映像の遅延はストリーミングの方が大きくなるので、使い分けると良さそうです。

次回はストリーミングにFFMPEGではなくVLC media playerを使ってみます。

コメント

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