はなちるのマイノート

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

【Unity】ComputeShaderでMaxプーリングをする【Q8】

Maxプーリングとは

Maxプーリングとは8*8ピクセルといったグリッド単位で画像を分割してその領域内を最大値で埋めることです。

f:id:hanaaaaaachiru:20200208215140p:plain

ほとんどの仕組みは前回の平均プーリングと同じなので、細かいところはそちらを参照してみてください。

コード

ComputeShaderはこちら。

#pragma kernel MaxPooling

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

[numthreads(8,8,1)]
void MaxPooling (uint3 id : SV_DispatchThreadID)
{
    float3 maxValue = float3(0,0,0);

    for(int i = 0; i < Grid.x; i++){
        for(int j = 0; j < Grid.y; j++){
            int2 index = int2((id.x * Grid.x) + i, (id.y * Grid.y) + j);
            maxValue.x = max(maxValue.x, Texture[index.xy].x);
            maxValue.y = max(maxValue.y, Texture[index.xy].y);
            maxValue.z = max(maxValue.z, Texture[index.xy].z);
        }
    }

    for(int i = 0; i < Grid.x; i++){
        for(int j = 0; j < Grid.y; j++){
            int2 index = int2((id.x * Grid.x) + i, (id.y * Grid.y) + j);
            Result[index.xy] = float4(maxValue.x, maxValue.y, maxValue.z, 1);
        }
    }
}


CPU側のコードはこちら。

using UnityEngine;
using UnityEngine.UI;

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

    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;
        }

        // グリッドを「8ピクセル*8ピクセル」で分割
        int[] grid = new int[] { 8, 8 };

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

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

        // 一つのグループの中に何個のスレッドがあるか
        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);
        _computeShader.SetInts("Grid", grid);

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

        // テクスチャを適応
        _renderer.texture = result;
    }
}

さいごに

最大値を求めるときにif文を用いる方法もありますが、HLSLではif文は遅いらしいです。

そこで今回はmaxという組み込み関数を用いています。(実際に早くなっているか分かりませんが)
HLSLリファレンスまとめ

次は画像処理の代表格?だと勝手に思っているフィルタ関連になっていきます。

たたみ込みなどの今までと違ってより理論的な分野になりますが、興味があるかたは是非みてみてください。

ではまた。