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へストリーミングする方法です。

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

Opencv Web camera and Video streams in Windows subsystem for Linux WSL, by FFmpeg and GStreamer
Opencv Web camera and Video streams in Windows subsystem for Linux WSL, by FFmpeg and GStreamer stream result to the web player. Simple step ty step.

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

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