【続】Unityで3Dテレビ向けに視差映像を作ってみる【Unity×openFrameworks】

#はじめに
[前回](http://qiita.com/KentaKato/items/b3d16de8877c688acbd1)の続編（？）にあたります．
前回の投稿ではUnity内で画像の変換処理を行う過程で，動作が重くなってしまったので今回は別の方法で試した内容をまとめます．

今回はUnityで作成した映像をSyphonを利用してopenFrameworksに渡した後，OpenCVを用いて画像処理を行い，視差映像を作成します．
普段MacでopenFrameworksを動かしているので，このようにした方が個人的に便利なだけで，SyphonとOpenCVが使えれば問題ありません．

#Syphonとは
[Syphon](http://syphon.v002.info/)とはMac OS X上のアプリケーション間で画像をリアルタイムにやり取りするためのものです．
残念ながらWindows上では動きません．（SpoutというSyphonと似たようなことができるものはあるようです）

#環境
macOS Sierra バージョン 10.12.5
Unity バージョン 5.6.1f1 Personal
openFrameworks バージョン 0.9.8

#Unityの設定（サーバー側）
[Issues](https://github.com/Syphon/Unity3D/issues/26)を見た感じだと，公式が出しているUnity向けSyphonでは，OpenGL2が外されたUnityのバージョンでは動作しないようです．

Issuesにも書いてありますが，Keijiro Takahashiさんが作られたFunnelというSyphonサーバープラグインであれば動作するようです．
https://github.com/keijiro/Funnel

私が使用する環境では動きましたが，どのバージョンで動作するか，しないか，は調査していないので不明です．

上記URLのgithubからUnityPackageをダウンロードし展開します．
映像を他のアプリケーションに送信したいカメラ（今回はleftCamとrightCam）にFunnel.csをアタッチします．（必要であればScreen WidthとScreen heightを書き換えてください）
また，自動でMain CameraにSyphon.csがアタッチされるので確認してみてください．Main Cameraをシーンに配置されていない方は別途Syphon.csを適当なオブジェクトにアタッチする必要があります．

前回投稿のシーンで設定しています．
<img width="1000" alt="シーン画像.png" src="https://qiita-image-store.s3.amazonaws.com/0/153217/c75682e6-1cda-33ff-e491-abbfc21d7763.png">

#openFrameworksの設定（クライアント側）
https://github.com/astellato/ofxSyphon
こちらのofxSyphonというaddonを使用します．
ダウンロードしてaddonsに追加してください．

また今回はofPixelsからMatへ変換するために[ofxCv](https://github.com/kylemcdonald/ofxCv)というaddonも使います．

#実装する上での注意点
サンプルのプログラムを動かせばそのまま映像のやり取りを行うことができますが，今回は2つの映像を1つの視差映像として合成するのでプログラムを書いていきます．

ここで注意する必要があるのは[フォーラム](https://forum.openframeworks.cc/t/ofxsyphon-to-image/7791/4)に書かれているようにSyphonを利用して送られてきた映像は，ofTextureのように振る舞うだけなのでpixel単位での操作がこのままだと行えません．

そこでフォーラムにもあるように1度fboにdrawしてからであればpixelを読み出せるようなので，手間ではありますがそのように実装していきます．

今回は1枚の画像の上半分にleftCam，下半分にrightCamの画像が入っている状態になるので，1枚の画像から上半分と下半分を抜き出しながら新たな画像に視差画像を生成する流れになります．

#プログラム
```C++:UnityToOF.h
#pragma once

#include "ofMain.h"
#include "ofxCv.h"
#include "ofxOpenCv.h"
#include "ofxSyphon.h"

class ofApp : public ofBaseApp{
    
public:
    void setup();
    void update();
    void draw();
    
    ofxSyphonServerDirectory dir;
    ofxSyphonClient leftClient, rightClient;

    ofFbo fbo;

    cv::Mat parallax = cv::Mat::zeros(1080, 1920, CV_8UC3);
};
```

```C++:UnityToOF.cpp
#include "ofApp.h"

//--------------------------------------------------------------
void ofApp::setup(){
    fbo.allocate(1920, 1080, GL_RGB, 6);
    ofSetWindowTitle("Unity-To-openFrameworks");
    ofSetWindowShape(1920, 1080);
    ofSetFrameRate(60);
    
    dir.setup();
    leftClient.setup();
    rightClient.setup();
}

//--------------------------------------------------------------
void ofApp::update(){
    
    // Unityから画像が送られて来ていれば実行します
    if(dir.size() > 0){
        
        // fboにUnityから送られて来たleftCamとrightCamの画像を描画します
        fbo.begin();
        
        // 上半分にleftCamの画像を描画します
        leftClient.set(dir.getDescription(0));
        leftClient.draw(0, 0, 1920, 540);
        
        // 下半分にrightCamの画像を描画します
        rightClient.set(dir.getDescription(1));
        rightClient.draw(0, 540, 1920, 540);
        fbo.end();
        
        // fboに描画した画像をofPixelsにを経由してMatに変換します
        ofPixels mainPixels;
        fbo.readToPixels(mainPixels);
        
        cv::Mat main_img = cv::Mat::zeros(1080, 1920, CV_8UC3);
        main_img = ofxCv::toCv(mainPixels);
        
        // 左と右の画像を1行ずつ交互に並べ替えて視差画像を生成します
        int count = 0;
        for (int y = 0;y < parallax.rows;y++) {
            if(y%2 == 0){
                for(int x = 0;x < parallax.cols;x++){
                    if(count < main_img.rows/2){
                        parallax.at<cv::Vec3b>(y, x) = main_img.at<cv::Vec3b>(count, x);
                        parallax.at<cv::Vec3b>(y+1, x) = main_img.at<cv::Vec3b>(count + main_img.rows/2, x);
                    }
                }
                count = count + 1;
            }
        }
    }
}

//--------------------------------------------------------------
void ofApp::draw(){
    ofxCv::drawMat(parallax, 0, 0);
}
```

#実行結果
Unityでシーンを実行している状態でopenFrameworksのプロジェクトを実行するとこのようになります．
![UnityToOF.mov.gif](https://qiita-image-store.s3.amazonaws.com/0/153217/4b11c706-aa56-d60b-e320-dbd05f2ec1d8.gif)

ちなみに前回の方法での実行結果がこちら．
![ParallaxMovie.mov.gif](https://qiita-image-store.s3.amazonaws.com/0/153217/73626e18-903a-5d0f-67e1-6d827d589cb6.gif)

目に見えてわかるほど速くなっていますね．

#まとめ
openFrameworks上ではありますが，これでUnityで作成した映像に対し，"ほぼ"リアルタイムで画像処理を施すことができました．
今回は3Dテレビ向けの視差画像の生成というニッチな内容になってしまいましたが，これをもとにUnityの実行結果に対して画像処理をかけられれば面白いことができそうかなと思います．

#参考にしたサイト
本文中のリンクと重複しますが，以下に参考にしたサイトをまとめておきます．

Syphon Unity 5.5 / OpenGLCore problems #26
https://github.com/Syphon/Unity3D/issues/26

ofxSyphon to image
https://forum.openframeworks.cc/t/ofxsyphon-to-image/7791