ux00ff

ビールとプログラミングと

processing.videoの代わりにOpenCVのVideoCaptureを利用する

ところで現在の Processing には、昔あった processing.video.* がありません。このあたりのコミットで delete video library now in processing-video 見ての通りばっさりやられています。

公式によると、

Video – we’ve removed the QuickTime for Java video library and are using a modified version of Andres Colubri’s GSVideo library instead. On Linux, you’ll need to install gstreamer to use the new library. On Windows and Mac OS X, you should not need to install it, however we’re working out a few kinks in the whole process.

とのことで、なるほどです。もちろん、公式の Video ライブラリをインストールしても問題ありません。サンプルコードも問題なく利用できます

f:id:ux00ff:20170201094556p:plain

しかし、それでは少し面白みがないので、ここでは OpenCVを直接利用してみることにしました。Mac OS、Processingは3系、OpenCVは3.2.0です。

OpenCVのインストール

ここでは homebrew で入れました。

$ brew install opencv3 --with-contrib --with-java --with-gstreamer

--with-java を指定することで、Java向けのバインディングがついでにインストールされます。

$ ls -la /usr/local/Cellar/opencv3/3.2.0/share/OpenCV/java/
total 3024
drwxr-xr-x  4 ma2saka  admin      136  1 23 01:52 .
drwxr-xr-x  9 ma2saka  admin      306  1 23 01:52 ..
-rwxr-xr-x  1 ma2saka  admin  1108648  1 23 01:52 libopencv_java320.dylib
-rw-r--r--  1 ma2saka  admin   434977  1 23 01:52 opencv-320.jar

この opencv-320.jar を、プロジェクトにインポートしておきましょう。

f:id:ux00ff:20170201100157p:plain

OpenCV VideoCapture の利用

ビデオキャプチャ自体は org.opencv.videoio.VideoCapture を利用すればすんなりできるんですが、表示しようとすると一手間必要です。取得できるデータ形式Mat 型となっています。MatはMatrixです。データ構造とか使われ方はこのあたりを cv::Matの基本処理 - OpenCV-CookBook 参照してもらうとして、Processing でスムーズに取り扱うには、Matで取り出された画像データを PImage に変換する必要があります。

ひとまず、ライブラリをインポートしておきます。

import java.nio.ByteBuffer
import org.opencv.core.{Core, Mat}
import org.opencv.videoio.VideoCapture

Mat から PImage への変換

ここで何も考えずに取り出されたデータ列を 32 ビットずつでまとめて PImage#pixels に流し込んでもうまくいきません。色合いが妙なことになります。これは、Matのデータ形式は三原色が BGR(Blue, Green, Red) の並びで格納されているからです。PImageはRGBの並びです。そこで、いったん取り出した後、順序を入れ替えて PImage のピクセルに書き込みます。

以下のような簡単な変換メソッドを書きました。

  def mat2PImage(mat: Mat): PImage = {
    val w = mat.width()
    val h = mat.height()

    val data8 = new Array[Byte](w * h * 3)
    mat.get(0, 0, data8)

    var i:Int = 0
    val data8byte: ByteBuffer = ByteBuffer.wrap(data8)
    val image: PImage = createImage(w, h, ARGB)

    val a:Byte = -1
    while (data8byte.position() < data8byte.limit()) {
      // see: http://opencv.jp/cookbook/opencv_mat.html
      val b = data8byte.get()
      val g = data8byte.get()
      val r = data8byte.get()

      image.pixels(i) = ByteBuffer.wrap(Array(a, r, g, b)).getInt()
      i += 1
    }
    
    return image
  }

PImage を RGB ではなく ARGB で作って決め打ちのアルファ値を設定しているのが気になると思います。これは、完全に処理の上でサボっているもので、32bit 単位だと int に粛々とパックすればいいけど 24bit 単位だとずれていくので面倒くさかったのです。

while の中はフレームレート x ピクセル数分呼ばれるのでなるべくシンプルな形に落ち着きました。

呼ぶ側

mat2PImage を利用して処理する側はこんな感じになりました。ただカメラの映像を表示するだけでは面白くないので、左上にフレームレートを表示しています。

  var cap: VideoCapture = null

  override def setup(): Unit = {
    // 0番目のビデオデバイスのオープン
    cap = new VideoCapture(0)
  }

  override def draw(): Unit = {
    background(0)

    if (cap.isOpened) {
      val camera_image = new Mat();
      cap.read(camera_image)
      val img = mat2PImage(camera_image)
      image(img, 0, 0, 500, 300)
    }

    fill(255)
    textSize(20)
    textAlign(LEFT,TOP)
    text("%f".format(frameRate),0,0)
  }

まとめ

と、いうことでこれで直接OpenCVを利用してビデオ処理をProcessingで書けるようになりました。よしよし。満足したけど、とはいえ OpenCVの他のフィーチャーを必要としない場合、processing-video 使うような気はします。なお、この記事は ProcessingのプログラムをIntelliJとScalaで開発するの流れです。