openFrameworks + GLSLでクロマキー合成

画像やムービー素材を背景に合成して表示したい場合、もとの素材がPNG形式などでアルファ値で透明度が指定されている場合は問題ないのだけれど、背景が黒や緑などの単色ベタ塗りになっていたときには、いわゆる「[クロマキー](https://ja.wikipedia.org/wiki/%E3%82%AF%E3%83%AD%E3%83%9E%E3%82%AD%E3%83%BC)」的な処理をする必要がある。

Shader(GLSL)を知らなかった頃は、画像の全てのPixel情報をとり出して、特定のチャンネルを描画しないなどの力技でやろうとしてみたけど、CPUへの負荷が尋常でないので、大きな画像素材では現実的ではない。

そこで、openFrameworks + GLSLでクロマキー合成をする方法を調べてみた。

WebGL用に実現しているブログがとてもわかり易く理解できた。

- [wgld.org | 動画テクスチャでクロマキー合成](https://wgld.org/d/webgl/w080.html)

こちらを参考に、openFrameworks用に移植してみた。

まずはShaderのソース。vertex shaderは、こんな感じ。

- chromaKey.vert

<pre>
#version 120

varying vec2 texCoordVarying;

void main()
{
  texCoordVarying = gl_MultiTexCoord0.xy;
  gl_Position = ftransform();
}
</pre>

こちらは、テクスチャーの情報をfragment shaderに渡してるだけ。

次にfragment shader。

- chromaKey.frag

```glsl
#version 120

uniform sampler2DRect tex0;     // ソースのテクスチャ
uniform float threshold;        // 閾値
uniform vec3 chromaKeyColor;    // キーの色
varying vec2 texCoordVarying;

void main()
{
    // テクスチャーの色を取得
    vec4 texel0 = texture2DRect(tex0, texCoordVarying);
    // キーの色との差分を計算
    float diff = length(chromaKeyColor - texel0.rgb);
    if(diff < threshold){
        // もしキーの色より差分が少なかったら描画しない
        discard;
    }else{
        // キーの色より差分が多かったら、そのまま描画
        gl_FragColor = texel0;
    }
}
```
キーの色と閾値をuniformにして、外部から指定できるようにしているのがポイント。これで汎用性のあるクロマキー合成用Shaderとして活用できる。

これをopenFrameworks側で利用する方法はこんな感じ。

- ofApp.h

```cpp
#pragma once
#include "ofMain.h"

class ofApp : public ofBaseApp{

public:
    void setup();
    void update();
    void draw();

    ofShader shader; // クロマキー合成Shader
    ofImage image; // ソースのイメージ
};
```

- ofApp.cpp

```cpp
#include "ofApp.h"

void ofApp::setup(){
    image.load("source.jpg"); // ソース画像
    shader.load("shader/chromaKey"); // クロマキー合成GLSL
}

void ofApp::update(){

}

void ofApp::draw(){
    ofBackgroundGradient(ofColor(128), ofColor(31), OF_GRADIENT_LINEAR);

    // マウスのX座標で閾値を設定
    float threshold = ofMap(mouseX, 0, ofGetWidth(), 0.0, 2.0);

    shader.begin();
    // キーの色を設定(green)
    shader.setUniform3f("chromaKeyColor", ofVec3f(0.0, 1.0, 0.0));
    // 閾値設定
    shader.setUniform1f("threshold", threshold);
    // クロマキー合成する画像を描画
    image.draw(0, 0);
    shader.end();

    ofDrawBitmapString("difference = " + ofToString(difference), 20, 20);
}
```

閾値を設定することで、例えばちょっと背景との境界がボケている画像でも。

<a href="http://yoppa.org/wp-content/uploads/2016/02/source.jpg" rel="attachment wp-att-6707"><img src="http://yoppa.org/wp-content/uploads/2016/02/source-300x169.jpg" alt="source" width="300" height="169" class="alignleft size-medium wp-image-6707" /></a>

こんな感じできれに合成できる!

<a href="http://yoppa.org/wp-content/uploads/2016/02/screenshot_198.jpg" rel="attachment wp-att-6709"><img src="http://yoppa.org/wp-content/uploads/2016/02/screenshot_198-300x174.jpg" alt="screenshot_198" width="300" height="174" class="alignleft size-medium wp-image-6709" /></a>

もちろん動画でも簡単!

- ofApp.h

```cpp 
#pragma once
#include "ofMain.h"

class ofApp : public ofBaseApp{

public:
    void setup();
    void update();
    void draw();

    ofShader shader;
    ofVideoPlayer video;
};
```

- ofApp.cpp

```cpp
#include "ofApp.h"

void ofApp::setup(){
    video.load("source.mp4");
    video.play();
    shader.load("shader/chromaKey");
}

void ofApp::update(){
    video.update();
}

void ofApp::draw(){
    ofBackgroundGradient(ofColor(128), ofColor(31), OF_GRADIENT_LINEAR);
    float threshold = ofMap(mouseX, 0, ofGetWidth(), 0.0, 2.0);
    shader.begin();
    shader.setUniform3f("chromaKeyColor", ofVec3f(0.0, 1.0, 0.0));
    shader.setUniform1f("threshold", threshold);
    video.draw(0, 0);
    shader.end();
}
```

爆発もきれいに合成できました!

<img src="http://yoppa.org/wp-content/uploads/2016/02/screenshot_199-1024x594.jpg" alt="screenshot_199" width="1024" height="594" class="alignleft size-large wp-image-6712" />

- [ソースコード (openFrameworks 0.9.0)](https://drive.google.com/open?id=0BzyVHU69QO3mR1k3VGl6dFdnLTA)

### addon版 - ofxChromaKey

すぐに使えるように、addon化してみた! (openFrameworks 0.9.0 〜)

- [ofxChromaKey on github](https://github.com/tado/ofxChromaKey)