前回はWSL2上のOpenCVでUSBカメラを利用する方法を紹介しました。この記事ではWSL2でUSBカメラを使うためにUSBIPD-WINというツールを使いましたが、いろいろ課題がありました。
そこで今回はこの課題の解消を試みたいとお思います。
USBIPD-WIN利用時の課題
WSL2は仮想環境のようなものでUSBデバイスを直接利用することができません。
そこでUSBIPD-WINというツールを使って、USBデータをIPに変換したうえでWSL2に転送し、WSL2側でIPからUSBデータを復元することでUSBデバイスを利用できるようにすることができます。
これでWSL2でUSBカメラを利用できるようになるのですが、試したところ次のような課題があると感じました。
- 環境構築が面倒
USBIPD-WINの導入と、Linux kernelのコンパイルが必要 - 事前設定が面倒
Windowsの管理者権限でUSBカメラをアタッチする必要がある - USBのスループットが低い
解像度やフレームレートを下げないとUSBカメラの映像をWSL2で取得できない
全部を改善するのは無理かもしれませんが、なんとか改善する方法を考えたいと思います。
ストリーミングの利用
そこで試してみたのはUSBカメラの映像をWindowsからWSL2へストリーミングする方法です。
参考にしたのはこちらのサイトです。
この元ネタのサイトを参考にしつつ、次のようなシステムを構築してみます。
- USBカメラはWindows PCに接続する
- Windows PCからWSL2のLinuxへUSBカメラの映像をストリーミングする
- WSL2のLinuxで動作するOpenCVではストリーミングされた映像を受信する
つまり、USBカメラから映像をキャプチャするのはWindows側で、そのキャプチャした映像をWSL2側にストリーミングすることで、WSL2のOpenCVでUSBカメラの映像(正確にはストリーミングされたUSBカメラの映像)を取得することを目指します。
Windows側の準備
Windowsにはストリーミングを行うソフトウェアとしてFFMPEGをインストールします。
FFMEPGは下記のサイトからダウンロードできます。
「Get packages & executable files」でWindowsロゴを選び、表示された「Windows EXE Files」のリストからダウンロード先を選びます。
ダウンロード先は2つありますが、私が見た感じ「Windows builds by BtbN」の方がわかりやすい気がしました。
「Windows builds by BtbN」には多数のファイルがあります。
今回はWindowsで利用するので「win64」のものをダウンロードします。
バージョンは「master(最新のソースコード)」「n4.4」「n5.0」が有りますが、安定していそうな「n4.4」にしてみました。
さらにライセンスとして「gpl」「lgpl」、リンク形式として「shared」「非shared(static)」が有ります。今回はライセンスはGPL問題ないので「gpl」の「非sahred」をダウンロードします。
ダウンロードしたzipファイルは展開して、「ffmpeg」というフォルダ名に変えてCドライブ直下に移動しておきます。
さらに、環境変数pathにffmpegフォルダ内のbinフォルダを追加します。
これで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でストリーミングができることが確認できました。
WSL2のターミナルでffplayコマンドを実行する際には、WSL2でGUIが利用できる状態で行ってください。
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がストリーミングしている映像)が表示されます。
もし「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を使ってみます。
コメント