OpenCVSharp x ffmpegでOBS Virtual Cameraをキャプチャする

Page content

OpenCV(Sharp)のVideoCaptureはピクセルフォーマットがNV12の映像のキャプチャに対応していません(2022-09-19現在)。OBS Studioにネイティブ実装されている仮想カメラ「OBS Virtual Camrera」はNV12形式で出力されているため、OpenCVSharpではキャプチャすることができません。
OBS Studio 27までは有志の方が作成されている仮想カメラプラグイン、「obs-virtual-cam」 https://github.com/Fenrirthviti/obs-virtual-cam が使用できましたが、OBS Studio 28からはGUIフレームワークQtのバージョンが5から6にアップデートされた影響で使用できなくなりました。

この記事では、ネイティブカメラとOpenCVSharpの間にffmpegを挟むことで映像をキャプチャすることを目的とします。

ffmpegのダウンロード

https://ffmpeg.org/download.html#build-windows

個人で利用する場合は意識する必要はありませんが、配布を考えている場合はライセンスの都合上、LGPL版ffmpeg + openh264の併用をおすすめします。
https://github.com/BtbN/FFmpeg-Builds/releases
https://github.com/cisco/openh264
※ffmpeg.exeとopenh264.dllは同じ場所に配置してください。

ffmpegのコマンド

ffmpeg -rtbufsize 204800000 -f dshow -i video="OBS Virtual Camera" -pix_fmt yuv420p -vcodec copy -f matroska "tcp://127.0.0.1:3205?listen=1"

ピクセルフォーマットをNV12からyuv420pに変換し、tcpで出力します(ポート番号は適当)。

OpenCVSharpのVideoCaptureでキャプチャ

/* ffmpegを実行 */
Process proc = new Process();
proc.StartInfo.FileName = "ffmpeg";
proc.StartInfo.CreateNoWindow = true;
proc.StartInfo.UseShellExecute = false;
proc.StartInfo.Arguments = $@"-rtbufsize 204800000 -f dshow -i video=""OBS Virtual amera"" -pix_fmt yuv420p -vcodec copy -f matroska ""tcp://127.0.0.1:3205?listen=1""";
proc.Start();

/* キャプチャ開始 */
VideoCapture videocap = VideoCapture.FromFile("tcp://127.0.0.1:3205");

/* ffmpeg終了(qコマンドだけでは終了しないことがあったのでkillも書いている) */
StreamWriter inputWriter = proc.StandardInput;
inputWriter.WriteLine("q");
proc.WaitForExit(1000);
proc.Kill();

※ffmpegの実行周りは実際にはこのままでは使いづらいと思います(私はProcessをグローバル変数みたく宣言するよろしくない方法を取りました。いい感じの書き方をご存じの方はぜひ… トップページに雑にフォームを置いてあります)

※tcpのオプションにnodelayがあるので必要そうであれば(?listen=1&tcp_nodelay=1)。使わなくても~0.1秒程度には収まるようです。
ただ、tcp送信開始~キャプチャ開始までの間に時間が空くとバッファが溜まるのか映像がとても遅れて表示されます。そのままキャプチャし続ければいずれ落ち着きますが、アプリケーションによっては注意する必要があるかもしれません。