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

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

前回は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(最新のソースコード)」「n4.4」「n5.0」が有りますが、安定していそうな「n4.4」にしてみました。

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

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

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

PATHへ追加

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

> ffmpeg -version
ffmpeg version n4.4.2-1-g8e98dfc57f-20220515 Copyright (c) 2000-2021 the FFmpeg developers
built with gcc 11.2.0 (crosstool-NG 1.24.0.533_681aaef)
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-libxml2 --enable-zlib --enable-libfreetype --enable-libfribidi --enable-gmp --enable-lzma --enable-fontconfig --enable-libvorbis --enable-opencl --disable-libpulse --enable-libvmaf --disable-libxcb --disable-xlib --enable-amf --enable-libaom --enable-libaribb24 --enable-avisynth --enable-libdav1d --enable-libdavs2 --disable-libfdk-aac --enable-ffnvcodec --enable-cuda-llvm --disable-frei0r --enable-libgme --enable-libass --enable-libbluray --enable-libmp3lame --enable-libopus --enable-librist --enable-libtheora --enable-libvpx --enable-libwebp --enable-lv2 --enable-libmfx --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopenh264 --enable-libopenjpeg --enable-libopenmpt --enable-librav1e --enable-librubberband --enable-schannel --enable-sdl2 --enable-libsoxr --enable-libsrt --enable-libsvtav1 --enable-libtwolame --enable-libuavs3d --disable-libdrm --disable-vaapi --enable-libvidstab --disable-vulkan --enable-libx264 --enable-libx265 --enable-libxavs2 --enable-libxvid --enable-libzimg --enable-libzvbi --extra-cflags=-DLIBTWOLAME_STATIC --extra-cxxflags= --extra-ldflags=-pthread --extra-ldexeflags= --extra-libs=-lgomp --extra-version=20220515
libavutil      56. 70.100 / 56. 70.100
libavcodec     58.134.100 / 58.134.100
libavformat    58. 76.100 / 58. 76.100
libavdevice    58. 13.100 / 58. 13.100
libavfilter     7.110.100 /  7.110.100
libswscale      5.  9.100 /  5.  9.100
libswresample   3.  9.100 /  3.  9.100
libpostproc    55.  9.100 / 55.  9.100

WSL2側の準備

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

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

$ sudo apt-get install libopencv-dev

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

$ sudo apt-get install ffmpeg

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

$ ip -4 addr show dev eth0
6: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    inet 172.29.147.89/20 brd 172.29.159.255 scope global eth0
        valid_lft forever preferred_lft forever

IPアドレスはinetに続く「172.29.147.89」の部分です。

このIPアドレスはのちのち使うのでメモしておきます。

WSL2ではWSL2を起動する度にIPアドレスが変化してしまいますので注意してください。

IPアドレスはストリーミングを実施する直前に確認する方が良いかもしれません。

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

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

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

USBカメラ名の確認

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

> ffmpeg -list_devices true -f dshow -i dummy

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

ffmpeg version n4.4.2-1-g8e98dfc57f-20220515 Copyright (c) 2000-2021 the FFmpeg developers
  built with gcc 11.2.0 (crosstool-NG 1.24.0.533_681aaef)
  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-libxml2 --enable-zlib --enable-libfreetype --enable-libfribidi --enable-gmp --enable-lzma --enable-fontconfig --enable-libvorbis --enable-opencl --disable-libpulse --enable-libvmaf --disable-libxcb --disable-xlib --enable-amf --enable-libaom --enable-libaribb24 --enable-avisynth --enable-libdav1d --enable-libdavs2 --disable-libfdk-aac --enable-ffnvcodec --enable-cuda-llvm --disable-frei0r --enable-libgme --enable-libass --enable-libbluray --enable-libmp3lame --enable-libopus --enable-librist --enable-libtheora --enable-libvpx --enable-libwebp --enable-lv2 --enable-libmfx --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopenh264 --enable-libopenjpeg --enable-libopenmpt --enable-librav1e --enable-librubberband --enable-schannel --enable-sdl2 --enable-libsoxr --enable-libsrt --enable-libsvtav1 --enable-libtwolame --enable-libuavs3d --disable-libdrm --disable-vaapi --enable-libvidstab --disable-vulkan --enable-libx264 --enable-libx265 --enable-libxavs2 --enable-libxvid --enable-libzimg --enable-libzvbi --extra-cflags=-DLIBTWOLAME_STATIC --extra-cxxflags= --extra-ldflags=-pthread --extra-ldexeflags= --extra-libs=-lgomp --extra-version=20220515
  libavutil      56. 70.100 / 56. 70.100
  libavcodec     58.134.100 / 58.134.100
  libavformat    58. 76.100 / 58. 76.100
  libavdevice    58. 13.100 / 58. 13.100
  libavfilter     7.110.100 /  7.110.100
  libswscale      5.  9.100 /  5.  9.100
  libswresample   3.  9.100 /  3.  9.100
  libpostproc    55.  9.100 / 55.  9.100
[dshow @ 000001f8f3d12b80] DirectShow video devices (some may be both video and audio devices)
[dshow @ 000001f8f3d12b80]  "Microsoft® LifeCam HD-3000"
[dshow @ 000001f8f3d12b80]     Alternative name "@device_pnp_\\?\usb#vid_045e&pid_0779&mi_00#8&15933275&0&0000#{65e8773d-8f56-11d0-a3b9-00a0c9223196}\global"
[dshow @ 000001f8f3d12b80] DirectShow audio devices
[dshow @ 000001f8f3d12b80] Could not enumerate audio only devices (or none found).
dummy: Immediate exit requested

重要なのは「DirectShow video devices」の次の行に表示されるUSBカメラの名前です。私の場合は「Microsoft® LifeCam HD-3000」です。

ストリーミングの開始

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

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

> ffmpeg -f dshow -i video="Microsoft® LifeCam HD-3000" -preset ultrafast -tune zerolatency -vcodec h264 -r 10 -b:v 4M -s 640x480 -ab 32k -ar 44100 -f mpegts -flush_packets 0 udp://172.29.147.89:5120?pkt_size=1316

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

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

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

ビットレートは4Mbpsを指定していますが、この解像度とフレームレートならもっと低くても良いかもしれません。

フレームレートをもっと上げたかったのですが、私のPCでは20fps程度が限界のようでした。

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

ストリーミングの確認

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

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

$ ffplay udp://127.0.0.1:5120

を実行してみましょう。

最初は

[h264 @ 0x7fd488026580] decode_slice_header error
[h264 @ 0x7fd488026580] non-existing PPS 0 referenced
[h264 @ 0x7fd488026580] decode_slice_header error
[h264 @ 0x7fd488026580] non-existing PPS 0 referenced
[h264 @ 0x7fd488026580] decode_slice_header error
[h264 @ 0x7fd488026580] non-existing PPS 0 referenced
[h264 @ 0x7fd488026580] decode_slice_header error
[h264 @ 0x7fd488026580] non-existing PPS 0 referenced
[h264 @ 0x7fd488026580] decode_slice_header error
[h264 @ 0x7fd488026580] non-existing PPS 0 referenced
[h264 @ 0x7fd488026580] decode_slice_header error
[h264 @ 0x7fd488026580] non-existing PPS 0 referenced
[h264 @ 0x7fd488026580] decode_slice_header error
[h264 @ 0x7fd488026580] no frame!

とエラーっぽいメッセージが表示されますが、5~10秒ほど待つとウィンドウが表示され、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://127.0.0.1:5120");

    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(100) >= 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

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

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

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

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

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

また、cv::VideoCaptureのインスタンスを生成する部分を

cv::VideoCapture cap("udp://127.0.0.1:5120" , cv::CAP_FFMPEG);

とするとうまくいくかもしれません。

まとめ

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

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

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

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

コメント

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