のしメモ アプリ開発ブログ

Unityアプリとかロボットとか作ってるときに困ったこととかメモ

Unityで学ぶ画像処理【色の変換編】

Unityにはピクセルごとに色を取得することができるので、Unityは画像処理もできるのです。
以前の記事はこちらです。Texture2D書き換え周りの基本的な処理はこちらをご参考に。

今回はInterfaceの5月号の画像処理特集のアルゴリズムを読みながら、Unityでコードを書いて学んでいきたいと思います。

こんな人におすすめ

・画像処理の仕組みを学びたい
・様々なプラットフォームで、スタンドアロンで動作する画像処理がしたい

今回はUnityのTexture2Dを使った画像処理について紹介していきます。
リアルタイムで行う場合はPostProcessingやShaderで処理するとよいですが、今回は説明しません。

ちなみに、360度画像からおっさんを消す「VANISH360」もUnityのTexture2Dクラスを使っておっさんを消しています。

RGB色入れ替え

実行結果

R->G, G->B, B->R

f:id:noshipu:20170524215431p:plain

R->B, G->R, B->G

f:id:noshipu:20170524215920p:plain

コード

R->G, G->B, B->R

Color[] inputColors = inputTexture.GetPixels();
Color[] outputColors = new Color[width * height];
for (int y = 0; y < height; y++)
{
    for (int x = 0; x < width; x++)
    {
        var color = inputColors[(width * y) + x];
        outputColors[(width * y) + x] = new Color(color.g, color.b, color.r);
    }
}
outputTexture.SetPixels(outputColors);
outputTexture.Apply();

解説

RGBの色を入れ替えるので、結構大きく色調が変わるものの、大きくバランスが崩れることはない感じです。

モノクロエフェクト

実行結果

f:id:noshipu:20170524222618p:plain

コード

Color[] inputColors = inputTexture.GetPixels();
Color[] outputColors = new Color[width * height];
for (int y = 0; y < height; y++)
{
    for (int x = 0; x < width; x++)
    {
        var color = inputColors[(width * y) + x];
        float average = (color.r + color.g + color.b) / 3;
        outputColors[(width * y) + x] = new Color(average, average, average);
    }
}
outputTexture.SetPixels(outputColors);
outputTexture.Apply();

解説

RGB色の平均値を計算し、全ての色の同一の強さにすることでモノクロにすることができます。
画素値が同一の強さになることで、強調する色がなく、モノクロの状態にすることができます。

セピアエフェクト

実行結果

f:id:noshipu:20170524223254p:plain

コード

Color[] inputColors = inputTexture.GetPixels();
Color[] outputColors = new Color[width * height];
for (int y = 0; y < height; y++)
{
    for (int x = 0; x < width; x++)
    {
        var color = inputColors[(width * y) + x];
        float average = (color.r + color.g + color.b) / 3;
        outputColors[(width * y) + x] = new Color(average, average * 0.8f, average * 0.55f);
    }
}
outputTexture.SetPixels(outputColors);
outputTexture.Apply();

解説

モノクロの拡張版です。
モノクロの状態である平均値に対して、セピアの明度を各色に乗算してあげることでセピアな感じを出すことができます。
モノクロ画像をセピアにするには(Rx1, Gx0.8, Bx0.55)を乗算する。

ポスタリゼーションエフェクト

実行結果

split=5

f:id:noshipu:20170524231006p:plain

split=2

f:id:noshipu:20170524231114p:plain

split=3 && モノクロ

f:id:noshipu:20170524231607p:plain

コード

Color[] inputColors = inputTexture.GetPixels();
Color[] outputColors = new Color[width * height];

int split = 3;
for (int y = 0; y < height; y++)
{
    for (int x = 0; x < width; x++)
    {
        var color = inputColors[(width * y) + x];
        for (int i = 0; i < split; i++)
        {
            float col1 = i * (1f / (float)split);
            float col2 = (i + 1f) * (1f / (float)split);
            if(col1 <= color.r && color.r <= col2)
            {
                color.r = (col1 + col2) / 2f;
            }
            if (col1 <= color.g && color.g <= col2)
            {
                color.g = (col1 + col2) / 2f;
            }
            if (col1 <= color.b && color.b <= col2)
            {
                color.b = (col1 + col2) / 2f;
            }
        }
        outputColors[(width * y) + x] = color;
    }
}
outputTexture.SetPixels(outputColors);
outputTexture.Apply();

解説

ポスターっぽく。漫画エフェクトみたいな画像処理です。
色の段階を減らすことによって、色のバリエーションが少なくなり、ポスターやイラストっぽい雰囲気を出すことができます。
splitで0から1で設定できる色調を区切りをつけて設定させます。

ソラリゼーションエフェクト

実行結果

f:id:noshipu:20170525020839p:plain

コード

Color[] inputColors = inputTexture.GetPixels();
Color[] outputColors = new Color[width * height];
for (int y = 0; y < height; y++)
{
    for (int x = 0; x < width; x++)
    {
        var color = inputColors[(width * y) + x];
        if(color.r <= 0.5f)
        {
            color.r = 2 * color.r;
        }
        else
        {
            color.r = 1 * 2 - 2 * color.r;
        }
        if (color.g <= 0.5f)
        {
            color.g = 2 * color.g;
        }
        else
        {
            color.g = 1 * 2 - 2 * color.g;
        }
        if (color.b <= 0.5f)
        {
            color.b = 2 * color.b;
        }
        else
        {
            color.b = 1 * 2 - 2 * color.b;
        }
        outputColors[(width * y) + x] = color;
    }
}
outputTexture.SetPixels(outputColors);
outputTexture.Apply();

解説

画素値の差を強調できる処理で、画素値が高いものは色の反転を起こし一部がネガっぽい感じになっています。
複数回を折り返すパターンもできるので、結果がどのよう状態になるのか、予測がなかなかつかないので楽しいですね。

モザイクエフェクト

実行結果

size=11

f:id:noshipu:20170525025152p:plain

size=27

f:id:noshipu:20170525025333p:plain

size=151

f:id:noshipu:20170525025435p:plain

コード

Color[] inputColors = inputTexture.GetPixels();
Color[] outputColors = new Color[width * height];

int size = 151;
for (int y = (size - 1) / 2; y < height; y = y + size)
{
    for (int x = (size - 1) / 2; x < width; x = x + size)
    {
        float colorR = 0f;
        float colorG = 0f;
        float colorB = 0f;

        for (int j = y - (size - 1) / 2; j <= y + (size - 1) / 2; j++)
        {
            for (int i = x - (size - 1) / 2; i <= x + (size - 1) / 2; i++)
            {
                if (i >= 0 && j >= 0 && i < width && j < height)
                {
                    colorR += inputColors[(width * j) + i].r;
                    colorG += inputColors[(width * j) + i].g;
                    colorB += inputColors[(width * j) + i].b;
                }
            }
        }

        colorR = colorR / (size * size);
        colorG = colorG / (size * size);
        colorB = colorB / (size * size);

        for (int j = y - (size - 1) / 2; j <= y + (size - 1) / 2; j++)
        {
            for (int i = x - (size - 1) / 2; i <= x + (size - 1) / 2; i++)
            {
                if (i >= 0 && j >= 0 && i < width && j < height)
                {
                    outputColors[(width * j) + i] = new Color(colorR, colorG, colorB);
                }
            }
        }
    }
}
outputTexture.SetPixels(outputColors);
outputTexture.Apply();

解説

指定のピクセルから周辺の色を取得し、平均値を計算して周辺のピクセルに割り当ててあげることで、ブロック単位で色が変わりモザイクになります。

コードはGithubで公開してます

今回使用したUnityプロジェクトはGithubにのっけています。今後も更新予定です。

最後に

画像処理楽しい!
続きも近いうちにやります。