自習室

こもります

ycapture で OpenCV での処理結果を Windows のビデオソースとしてブラウザに流し込む

はじめに

WebRTC を使ってビデオチャットアプリを作る際に、ビデオ画像にエフェクトをかけたくなりました。モザイクとか、ブラーとか、漫画カメラとか。

ブラウザ上でJavaScriptでごりごり画像処理をするのはさすがに厳しそうですので、画像処理を行うのは別のアプリケーションで行うことにしました。

最終的に WebRTC で MediaStream として扱うことを考えると、画像処理の結果が通常のWebカメラと同様に navigator.getUserMedia() できれば一番手っ取り早いんじゃないかなーと思っていろいろ手法を探してみました。

便利なソフトを教えていただいた

しかし調べれば調べるほど、Windows上だと DirectShowのお勉強しなきゃとかつらそうな気配が漂っていたのですが、Twitterに泣きついたところ、@jnakano さんが ycapture(わいきゃぷちゃ) の存在を教えて下さいました。

ycapture(わいきゃぷちゃ)

説明から引用

ycaptureとは、WindowsのVideo Capture Sourceのような振る舞いをするDLL(の基本ソース)です。このDLLを使用すると、あたかもビデオキャプチャデバイスのように、任意のプログラムからビデオデータを供給することができます。

これだー!と言うことで、今回試してみました。開発者の谷沢さん、ありがとうございます。

開発の前に

今回やりたいこと

f:id:AMANE:20150111151631p:plain

  1. openFrameworks 上で、OpenCVを使ってカメラ画像を処理し、
  2. ycapture.dll からWindowsのビデオデバイスとして流します。
  3. ブラウザで navigator.getUserMedia() でストリームを取得します

注意

今回のアプローチがベストかどうかはケースバイケースだと思うので、手法の一つとしてご参考にしていただければと思います

環境

ycaptureのビルドは、

の2つの組み合わせで確認しています。openFrameworks に関しては、Visual Studio 版が 2012(v110) にしか対応していないため、今回のシステム全体としては、Visual Studio 2012 を利用しています。

ycapture の準備

公式に載っている利用法に従って下さい。公式ページでは VS2008で、と書いてありますが上記の通りの環境でもビルド & 利用可能でした。以下、ポイントだけメモっておきます。

Release か Debug か。ビルドの種類とパスの設定に注意。

Visual Studio は ライブラリ類のパスを通すのが面倒なので、私は Release ビルドに統一してやっています。Visual Studioのプロパティをいじる際は、毎回 Configuration の表示に気をつけましょう。

f:id:AMANE:20150111092217p:plain

Windows SDK を導入し、DirectShowの baseclasses をビルドして静的ライブラリを作る

Windows SDK アーカイブ – Windows デベロッパー センター

今回はこちらの Windows 7 および .NET Framework 3.5 SP1 用 Windows SDK を使っています。

baseclasses ライブラリのソリューションを Visual Studio 2012(2013) で開こうとすると、変換の際に大量の警告が出ますが、ビルドは出来ます。

配布されている ycapture プロジェクトのビルド準備

ycaptureプロジェクトのプロパティで、インクルードやリンクがうまくいくよう設定を変更します。先述の DirectShow/baseclasses の在処を教えてあげます(配布された状態だと、Windows SDKのバージョンが違ったりしています)

私の場合は以下の通り

  • 追加のインクルードディレクトリ
    C:\Program Files\Microsoft SDKs\Windows\v7.0\Samples\multimedia\directshow\baseclasses;

  • 追加のライブラリディレクトリ
    C:\Program Files\Microsoft SDKs\Windows\v7.0\Samples\multimedia\directshow\baseclasses\Release;

これでビルドが出来ます。

ycaptureclient と testclienet のビルド

この二つは、配布状態のままでビルド可能です。

testclientは、ソリューション内でycaptureclientに対してプロジェクトの依存関係が組まれているので、特にライブラリの場所とか気にしなくて大丈夫なようです。

ycapture.dll の登録

regsvr32.exe を使ってdllを登録する際は、 powershell/cmd を管理者権限で立ち上げないと怒られます。

>regsvr32.exe ycapture.dll

サンプルを試す

ycapture で配布されている testclient で、以下の流れが確認出来ます。

  1. testclient を起動します。
  2. 受け側として、ブラウザで https://simpl.info/getusermedia/sources/ を開きます。 navigator.getUserMedia のサンプルアプリです。
    受け側はSkypeなどのアプリでももちろん大丈夫です。
  3. 画面に、testclientが流している赤い帯が表示されれば成功です

f:id:AMANE:20150111151857p:plain

testclient が生成するカメラが見つからないときがある

ドライバの読み込み順の関係かキャッシュか正確なことは分かりませんが、作った仮想カメラがクライアントから見つからないことがあります。こういうときは、ページのリロードや、既に刺さっているウェブカムの抜き差しなどをすると、見つかるようになります。

ycapture を oF 内で利用する

ycapture を oF 内で利用する事で、oF での描画結果やCVを使った画像処理の結果を、仮想カメラデバイスとして流すことが出来るようになります。

プロジェクトの準備

ここでは Visual Studio での oF プロジェクトの生成等については割愛いたします。

以下の点をプロジェクトのプロパティに設定していきます

  • baseclasses/ycapture のビルドにあわせて、configuration を Release にする
  • 追加のインクルードディレクトリに、ycaptureとycaptureclient のソースコードがある場所を追加する
<ycaptureをおいた場所>\ycapture-src-0.1.1\ycapture\ycaptureclient
<ycaptureをおいた場所>\ycapture-src-0.1.1\ycapture\ycapture
  • 追加のライブラリディレクトリに、ycapture/ycaptureclientのビルド結果がある場所を追加する
<ycaptureをおいた場所>\ycapture-src-0.1.1\ycapture\Release
  • ライブラリの入力に、以下の物を追加する
ycapture.lib
ycaptureclient.lib

コード

ycapture さんで配布されているtestclient を参考に書いていきます。

// ofApp.h

// 前略
#include "ofxCv.h"
#include "CaptureSender.h"
#include "ycapture.h"

class ofApp : public ofBaseApp {

public:
  // 中略
  ofVideoGrabber cam;
  ofImage raw;
  ofImage blur;
  CaptureSender *sender;  // ycaptureのオブジェクト
};
// ofApp.cpp
int CAM_WIDTH = 640;
int CAM_HEIGHT = 480;
unsigned long avgTimePF = 1000 / 30;    // 30fps ストリームのタイミングを作るのに使うらしいが詳細不明

void ofApp:setup(){
  cam.initGrabber(CAM_WIDTH, CAM_HEIGHT); // カメラの初期化
  sender = new CaptureSender(CS_SHARED_PATH, CS_EVENT_WRITE, CS_EVENT_READ); // ycaptureのセットアップ
  // 後略
}

void ofApp::update(){
  cam.update();

  if(cam.isFrameNew()){
    raw.setFromPixels(cam.getPixels(), CAM_WIDTH, CAM_HEIGHT, OF_IMAGE_COLOR); // カメラ画像をコピー
    raw.mirror(1, 0); // そのまま送るとブラウザ側で垂直方向に反転する
  
    ofxCv::blur(raw, blur, 51); // ブラーをかけてみるテスト
    HRESULT hr = sender->Send(counter * avgTimePF, CAM_WIDTH, CAM_HEIGHT, blur.getPixels() ); //★★ここがポイント★★
    // 後略
  }
}

void ofApp::draw() {
  cam.draw(0, 0);
}

要するに、何かしらの方法で画像を作った後、 RGBの順の unsigned char 配列にして、sender->Send() すれば良いようです。oFやCVには、RGBの順の unsigned char 配列 への変換関数が用意されているので楽ちんです。

さいごに

ウェブブラウザ上で、oFで処理した結果の画像が見られれば完成です。oFのウィンドウに表示されているカメラのスルー画と並べて見ても、遅れは感じられません。良い感じ。

f:id:AMANE:20150111104337j:plain

次は、せっかくブラウザ上でやっているので、ブラウザでのユーザの入力を受け付けて、エフェクトを変更したりする仕組みを作ってみようと思います。

他プラットフォーム

Linuxの場合

余り詳しくないので間違いがあるといけないのですが、、Linux環境だと、gstreamer や V4L2loopback といったモジュールを使うと、割と簡単に動画ストリームをビデオデバイスとして取り扱うことが出来るようです。実際、openFrameworksの addons に arturoc/ofxGstV4L2Sink · GitHub と言う物があって、リポジトリのREADMEによれば、

Uses v4l2sink gst element to create a virtual device which can be used as a camera in any application that supports v4l2

とのことで、今回狙っていることが出来そうです。ただし私は試していません。

Mac OSX の場合

rsodre/ofxFakam · GitHub と言う物があり、これも

ofxFakam is an Open Frameworks addon that streams screen data to a fake camera driver.

ということで、狙っていることが出来そうです。ただし私は試していません。