[Kinesis Video Streams] OpenCVのビデオソースにGStreamerを使用してみました。

2020.02.16

1 はじめに

CX事業本部の平内(SIN)です。

MLなどを扱う時、OpenCVによる画像処理が併せて利用される場面があるようです。

Kinesis Video Streamsでは、通常、エッジデバイスからの送信をGStreamerのシンクから行っています。今回は、ビデオ入力をOpenCVで処理し、それをGStreamerで扱うことをイメージして、OpenVCとGStreamerの統合を試してみました。

2 環境

作業した環境は、RaspberryPiです。

Raspberry Piは、Model 4B(メモリ4G)で、OSは、昨年9月の最新版(Raspbian GNU/Linux 10 (buster) 2019-09-26-raspbian-buster-full.img です。

$ cat /proc/cpuinfo  | grep Revision
Revision    : c03112

$ lsb_release -a
No LSB modules are available.
Distributor ID: Raspbian
Description:    Raspbian GNU/Linux 10 (buster)
Release:    10
Codename:   buster

$ uname -a
Linux raspberrypi 4.19.75-v7l+ #1270 SMP Tue Sep 24 18:51:41 BST 2019 armv7l GNU/Linux

3 OpenCVの構築

通常配布されている、OpenCVのバイナリは、入力ソースが、FFMPEG、V4L2あたりになっています。(GStreamerは、NOになっている)

$ python
(略)
>>> import cv2
>>> print(cv2.getBuildInformation())

General configuration for OpenCV 3.2.0 =====================================

(略)

  Video I/O:
    DC1394 1.x:                  NO
    DC1394 2.x:                  YES (ver 2.2.5)
    FFMPEG:                      YES
      avcodec:                   YES (ver 58.35.100)
      avformat:                  YES (ver 58.20.100)
      avutil:                    YES (ver 56.22.100)
      swscale:                   YES (ver 5.3.100)
      avresample:                YES (ver 4.0.0)
    GStreamer:                   NO
    V4L/V4L2:                    NO/YES
    gPhoto2:                     YES
(略)

GStreamerを入出力に使用するためには、再構築の必要があります。

(1) コンパイルの環境について

ディスク

最近のRaspbianは、初回起動時に、自動的にディスクの拡張が行われますので、16G以上のSDカードなら容量は気にする必要はありません。

メモリ

下記は、make中にfreeコマンドで確認しているようですが、使用メモリが1.1Gを超えてきています。

$ free -m
              total        used        free      shared  buff/cache   available
Mem:           3906        1123        2493           8         289        2651
Swap:            99           0          99

今回使用したRasPi 4Bは、搭載メモリが4Gなので、何も問題は無かったのですが、Raspbianの配布イメージのデフォルトのスワップサイズは、100Mになっていますので、Model 3B など、搭載メモリが1Gの場合は、メモリ不足でmakeが途中でエラーとなります。

この場合、/etc/dphys-swapfileを編集して、サイズを上げて下さい。1G程度あれば充分なはずです。(最悪、不足した場合は、その時点で上げて、続きからmakeすればいいでしょう)

  • /etc/dphys-swapfileを1Gに編集
$ sudo vi /etc/dphys-swapfile
#CONF_SWAPSIZE=100
CONF_SWAPSIZE=1024
  • SWAPのリスタート
$ sudo /etc/init.d/dphys-swapfile restart
  • 確認
$ swapon
NAME      TYPE  SIZE USED PRIO
/var/swap file 1024M   0B   -2

(2) gstreamerのインストール

OpenCVのコンパイル前に、GStreamerを入れておく必要があります。

$ sudo apt install cmake libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev gstreamer1.0-tools libgtk2.0-dev gstreamer1.0-omx=1.0.0.1-0+rpi12+jessiepmg gstreamer1.0-plugins-bad gstreamer1.0-plugins-good

(3) OpenCV のダウンロード

一番新しそうな、4.2.0を入れてみました。

$ mkdir OpenCV && cd OpenCV
$ wget https://github.com/opencv/opencv/archive/4.2.0.zip
$ unzip 4.2.0.zip
$ cd opencv-4.2.0

(4) cmake

WITH_GSTREAMER=ONに設定して、cmakeを実行します。(競合すると不安定との情報があったので、WITH_FFMPEGは、OFFとしました)

$ mkdir build && cd build
$ cmake -D CMAKE_BUILD_TYPE=RELEASE -D CMAKE_INSTALL_PREFIX=/usr/local -DINSTALL_PYTHON_EXAMPLES=ON -D INSTALL_C_EXAMPLES=ON -D PYTHON_EXECUTABLE=/usr/bin/python3 -D BUILD_EXAMPLES=ON -D WITH_GTK=ON -D WITH_GSTREAMER=ON -D WITH_FFMPEG=OFF -D WITH_QT=OFF ..

cmakeの出力で、Video I/OでGStreamerが有効になっていることを確認できます。

--   Video I/O:
--     DC1394:                      NO
--     GStreamer:                   YES (1.14.4)
--     v4l/v4l2:                    YES (linux/videodev2.h)

(5) make & install

Raspberry Pi 4B や 3B+であれば、クアッドコアなので、とりあえず、make -j4で良いと思うのですが、途中で止まってしまうことがあったので、何回かやり直す(止まったところから再開できます)事になりました。

$ make -j4

途中で、止まると、オブジェクトファイルが中途半端に出来てしまって、次回、file not recognized: file truncated のエラーとなることがありました。

下記では、Mesh.cpp.oが壊れています。

CMakeFiles/example_tutorial_pnp_detection.dir/tutorial_code/calib3d/real_time_pose_estimation/src/Mesh.cpp.o: file not recognized: file truncated
collect2: error: ld returned 1 exit status

このエラーは、当該オブジェクトファイルを消してしまって、再度、makeを実行する事で回避できます。

対応例)

$ find . -name Mesh.cpp.o
./samples/cpp/CMakeFiles/example_tutorial_pnp_detection.dir/tutorial_code/calib3d/real_time_pose_estimation/src/Mesh.cpp.o
$ rm ./samples/cpp/CMakeFiles/example_tutorial_pnp_detection.dir/tutorial_code/calib3d/real_time_pose_estimation/src/Mesh.cpp.o

コンパイルは、なにやかんやで2時間ぐらいかかりました。終了したら、インストールします。

$ sudo make install

(6) 確認

動作を確認している様子です。Video I/0でGStreamerがYESになっていれば、OKです。

$ python
Python 2.7.16 (default, Oct 10 2019, 22:02:15)
[GCC 8.3.0] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import cv2
>>> print(cv2.getBuildInformation())

General configuration for OpenCV 4.2.0 =====================================
  Version control:               unknown

(略)

Video I/O:
    DC1394:                      NO
    GStreamer:                   YES (1.14.4)
    v4l/v4l2:                    YES (linux/videodev2.h)

4 appsink

GStreamerで、テスト入力をウインドウに表示する場合のコマンドです。

$ gst-launch-1.0 videotestsrc ! videoconvert ! autovideosink

※ videoconvertは、上記の場合、必須ではありませんが、appsinkに送るために必要となります。比較しやすいように、敢えてvideoconvertを入れています

GStreamerで出力先となっているautovideosinkappsinkに変更することで、OpenCVの入力ソースとして扱うことが出来ます。

下記は、OpenCVで、その入力をウインドウに表示しているコードです。

import cv2

src = 'videotestsrc ! videoconvert ! appsink'

cap = cv2.VideoCapture(src)

while(cap.isOpened()):
    ret, frame = cap.read()

    if frame is None:
        break
    cv2.imshow('frame',frame)
    cv2.waitKey(1)

cap.release()
cv2.destroyAllWindows()

ウインドウのタイトルがframeとなっており、OpenCVで表示されていることが分かります。

5 appsrc

v4l2srcをソースとして取得したWevカメラの映像をTCPストリームで送信する場合、GStreamerのコマンドラインは、以下のようになります。

$ gst-launch-1.0 v4l2src ! gdppay ! tcpserversink host={ホスト側のアドレス}

参考:[Kinesis Video Streams] Raspberry PiからGStreamerを使用してTCPストリーム配信してみました。

OpenCVのソースでGStreamerを有効にした場合、この v4l2src を単純に appsrc に置き換えることで、OpenCVのcv2.VideoWriter()からの入力を受け渡すことが可能になります。

sink = 'appsrc ! gdppay ! tcpserversink host=10.0.0.10'

Raspberry Pi上のOpenCVからGstreamerでTCPストリームのサーバを作成し、Macから確認してみました。

Raspberry Pi側のコードは、以下のとおりです。

RasPi

import cv2

cap = cv2.VideoCapture(0)
cap.set(3,320) # WIDTH
cap.set(4,240) # HEIGHT
cap.set(1,15) # FPS

framerate = 15/1

sink = 'appsrc ! gdppay ! tcpserversink host=10.0.0.10'
out = cv2.VideoWriter(sink, 0, framerate, (320, 240))

while( cap.isOpened() ):
    ret, frame = cap.read()

    if frame is None:
        break

    out.write(frame)
    cv2.imshow('frame',frame)
    cv2.waitKey(1)

cap.release()
cv2.destroyAllWindows()

正常に起動すれば、4963でLISTENとなります。

$ netstat -an | grep 4953
tcp        0      0 10.0.0.10:4953          0.0.0.0:*               LISTEN

これをMac側で受信して確認してみました。

Mac

$ gst-launch-1.0 -v tcpclientsrc host=10.0.0.10 ! gdpdepay ! videoconvert ! autovideosink sync=false

※sync=falseを付けないと、OpenCVの処理によって、データ待ちが発生して止まってしまう。

左が、RaspberryPiの画面で、右が、Mac上の画面です。

6 最後に

今回は、GStreamerのエレメントとして、OpenCVの入出力を利用できるようにしてみました。 これで、OpenCVを使用した画像処理を、Kinesis Video Streamsと統合できるような気がしてます。