はなちるのマイノート

Unityをメインとした技術ブログ。自分らしくまったりやっていきたいと思いますー!

【Unity】ComputeShaderで2値化(Binarization)してみる【Q3】

はじめに

相変わらずこちらの画像処理100本ノックをしています。

github.com

前回 -> 【Unity】ComputeShaderでグレースケールをしてみた【Q2】 - はなちるのマイノート

次回 -> 【Unity】ComputeShaderで大津の2値化をしてみる【Q4】 - はなちるのマイノート


今回は画像の2値化について取り上げていきたいとおもいます。

f:id:hanaaaaaachiru:20200203214805p:plain

2値化とは

2値化とは白黒のみで画像を表現することです。

ただどんな条件で白,黒にさせるかはいろんな種類の方法があったりします。

今回は以下の手順で実現したいと思います。

まずはピクセルの色をグレースケール化します。

Y = 0.2126 R + 0.7152 G + 0.0722 B

この情報をもとに、ここでのグレースケールは0 ~ 1なので128/255より小さければを0,それ以上なら1とみなします

※イメージ
if(Y < 128 / 255){
    B = 0;
}else{
    B = 1;
}

コード

ComputeShaderはこんな感じ。

#pragma kernel Binarization

RWTexture2D<float4> Result;
Texture2D<float4> Texture;

[numthreads(8,8,1)]
void Binarization (uint3 id : SV_DispatchThreadID)
{
    float gray = 0.2126 * Texture[id.xy].x + 0.7152 * Texture[id.xy].y + 0.0722 * Texture[id.xy].z;

    gray = (gray < 128.0 / 255.0) ? 0 : 1;

    Result[id.xy] = float4(gray, gray, gray, 1);
}


つぎにC#のCPU側のコードはこんな感じ。

using UnityEngine;
using UnityEngine.UI;

public class Binarization : MonoBehaviour
{
    [SerializeField] private ComputeShader _computeShader;
    [SerializeField] private Texture2D _tex;
    [SerializeField] private RawImage _renderer;
    private RenderTexture _result;

    struct ThreadSize
    {
        public uint x;
        public uint y;
        public uint z;

        public ThreadSize(uint x, uint y, uint z)
        {
            this.x = x;
            this.y = y;
            this.z = z;
        }
    }

    private void Start()
    {
        if (!SystemInfo.supportsComputeShaders)
        {
            Debug.LogError("Comppute Shader is not support.");
            return;
        }

        // RenderTextueの初期化
        _result = new RenderTexture(_tex.width, _tex.height, 0, RenderTextureFormat.ARGB32);
        _result.enableRandomWrite = true;
        _result.Create();

        // Binarizationのカーネルインデックス(0)を取得
        var kernelIndex = _computeShader.FindKernel("Binarization");

        // 一つのグループの中に何個のスレッドがあるか
        ThreadSize threadSize = new ThreadSize();
        _computeShader.GetKernelThreadGroupSizes(kernelIndex, out threadSize.x, out threadSize.y, out threadSize.z);

        // GPUにデータをコピーする
        _computeShader.SetTexture(kernelIndex, "Texture", _tex);
        _computeShader.SetTexture(kernelIndex, "Result", _result);

        // GPUの処理を実行する
        _computeShader.Dispatch(kernelIndex, _tex.width / (int)threadSize.x, _tex.height / (int)threadSize.y, (int)threadSize.z);

        // テクスチャを適応する
        _renderer.texture = _result;

    }

    private void OnDestroy()
    {
        _result = null;
    }
}

さいごに

2値化はグレースケールさえできていれば簡単でしたね。

またHLSLでは条件分岐は遅いらしいので、3項演算子の方がいいらしいです。(本当かはよく分かっていません)

次は大津の二値化なので、もう少し理論的なものになってきます。

もし興味があればそちらもどうぞ。

ではまた。