OpenCV
gstreamer

OpenCVのGstreamerバックエンドで高度な動画キャプチャを実現する

はじめに

OpenCVのキャプチャ機能(VideoCaptureクラス)は対応形式がとても多く、Wifiウェアラブルカメラなどのストリーミングも扱うことができて面白いです。しかし、デコード関連の柔軟性が乏しいところもありもう一つと感じることもあります。
最近になってgstreamerバックエンドを使ってみたところ、非常に便利だったので紹介したいと思います。

2018/05/22追記:インストール方法の関連ライブラリの一覧を追記。その他細かい修正。

準備

OpenCVの公式配布バイナリでは、動画の入出力にffmpegバックエンドが使われており、gstreamerバックエンドは有効化されていません。使うには自前でビルドする必要があります。

  • Ubuntu/Macではgstreamer1.0と関連ライブラリをインストールしてある状態でcmakeを実行すれば、自動的に有効化されます。( 関連ライブラリ:libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-libav )

  • Windowsでは自動解釈はされないので、少し手を加える必要があります。といってもgstreamer1.0と関連ライブラリをインストールした状態で、cmakeオプションGSTREAMER_DIRを正しく指定すればOKです(ググるとpkg-configを入れてCMakeListにパッチを当てるとか出てくるんですが、調査の結果それは不要で、上記の方法で十分でした)。

gstreamerについて

基本的な使い方の記事は他にもありますので割愛しますが、まずはgst-launch-1.0コマンドでパイプライン書いて色々試せるぐらいの理解度で大丈夫だと思います。SDKを使いこなすところまでは不要です。OpenCVとの連携においてはcapsフィルター(video/x-raw,...のような部分)を抑えておくと理解しやすいと思います。
なおgstreamer0.10系とgstreamer1.0系でパイプラインの記述方法が違う部分があります。gstreamerとだけ書いてある場合は0.10系の場合があるので、情報収集の際は気をつけましょう。

基本形

まずは基本的です。Pythonで書いていますがC++でも同様のコードで使えます。
gst-launch-1.0などと大体同じパイプラインが動きます。最後のsinkエレメントをappsinkとすればOKです。

gstreamer_display.py
import cv2

cap = cv2.VideoCapture("videotestsrc ! videoconvert ! appsink")

while True:
    ret, img = cap.read()
    if not ret:
        break

    cv2.imshow("",img)
    key = cv2.waitKey(1)
    if key==27: #[esc] key
        break

便利な使いかたいろいろ

ネットワークカメラのレイテンシーを制御する

"rtspsrc location=rtsp://ip_address/stream latency=0 ! decodebin ! videoconvert ! appsink"

素のffmpegにはレイテンシーを制御するようなオプションがあったと思いますが、VideoCaptureクラスからそれらオプションを指定する方法はありません。

gstreamerバックエンドでは上記のようにlatency=0とすることで解決できます。

画像サイズやフレームレートを制御する

"filesrc location=test.mp4 ! decodebin ! videoscale ! video/x-raw,width=320,height=240 ! videorate ! video/x-raw,framerate=1/1 ! appsink"

画像認識などでは、画像サイズやフレームレートを落とすことが常套手段ですが、これがリアルタイム処理を行うときに厄介です。とくに正確なフレームレートの制御が案外ややこしく、waitKey()で時間を指定すれば処理時間分だけズレてしまいますし、フレームをスキップするにしても1フレームの処理が33ms以上かかる場合に予測しにくい動きになります。マルチスレッドにすれば解決できますが煩雑です。

gstreamerバックエンドを使えばスマートに解決できます。

無駄な色変換を抑制する(OpenCV3.3から)

"filesrc location=test.mp4 ! decodebin ! video/x-raw,format=I420 ! appsink"

YUVを直接扱いたい場合に有用です。
BGRでキャプチャしてからYUVに変換する場合、デコード直後の色変換も含めると無駄な色変換をしているので、効率化のため直接YUVでキャプチャしたいところです。
appsinkの対応フォーマットは、OpenCV3.2まではBGR,GRAY8,BAYER,JPEGでしたが、OpenCV3.3からはUYVY,YUY2,YVYU,NV12,NV21,YV12,I420,BGR,GRAY8に対応しています。
ドキュメントがないので細かい部分を確認したい場合はソースコードを参照してください:
https://github.com/opencv/opencv/blob/master/modules/videoio/src/cap_gstreamer.cpp
互換性のためフォーマットを明示しない場合はBGR,GRAY8が優先的につかわれるようにしています。画像サイズ・チャンネル数についてはcvtColor関数の実装を参考にして決めています。

ハードウェアデコーダーを指定する

"filesrc location=test.mp4 ! qtdemux ! omxh264dec ! videoconvert ! appsink"

これまでの例ではdecodebinに任せていたデコード処理ですが、より細かく書くこともできます。例えばRaspberryPiにおけるomxh264decプラグインを使えば、ハードウェアデコーダーを明示的に利用することができます。

その他

gstreamerは様々なプラグインの組み合わせで機能を構成できますし、実はVideoWriterクラスにもgstreamerパイプラインを書くことができますので、これも組み合わせるといろいろ面白い使い方ができるのではないでしょうか。
面白い利用例やプラグインがありましたらお気軽にコメントください。