はなちるのマイノート

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

【Unity】ComputeShaderで移動平均フィルタ(平均化フィルタ)を実装してみる【Q11】

はじめに

画像処理100本ノックの続きをやっていきましょう。

github.com


前回 -> 【Unity】ComputeShaderでメディアンフィルタを実装してみる【Q10】 - はなちるのマイノート

次回 -> 【Unity】ComputeShaderでモーションフィルタを実装してみる【Q12】 - はなちるのマイノート


今回は移動平均フィルタ(平均化)フィルタについて取り組んでいきます。

f:id:hanaaaaaachiru:20200214201948p:plain

移動平均フィルタとは

これは注目する画素とその周辺の画素の画素値の平均を出力値にするフィルタです。

以下のようなフィルタを使えばOKです。

f:id:hanaaaaaachiru:20200214202552p:plain

コード

ComputeShaderはこちら。

#pragma kernel AveragingFilter

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

[numthreads(8,8,1)]
void AveragingFilter (uint3 id : SV_DispatchThreadID)
{
    float3x3 filter = (1.0 / 9.0) * float3x3(1, 1, 1, 1, 1, 1, 1, 1, 1);

    float4 upperLeft = Texture[id.xy + int2(-1, 1)] * filter[0][2];
    float4 up = Texture[id.xy + int2(0, 1)] * filter[1][2];
    float4 upperRight = Texture[id.xy + int2(1, 1)] * filter[2][2];
    float4 left = Texture[id.xy + int2(-1, 0)] * filter[0][1];
    float4 middle = Texture[id.xy] * filter[1][1];
    float4 right = Texture[id.xy + int2(1, 0)] * filter[2][1];
    float4 lowerLeft = Texture[id.xy + int2(-1, -1)] * filter[0][0];
    float4 down = Texture[id.xy + int2(0, -1)] * filter[1][0];
    float4 lowerRight = Texture[id.xy + int2(1, -1)] * filter[2][0];

    Result[id.xy] = upperLeft + up + upperRight + left + middle + right + lowerLeft + down + lowerRight;
}


CPU側のコードはこちら。

using UnityEngine;
using UnityEngine.UI;

public class AveragingFilter : 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;
        }

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

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

        // 一つのグループの中に何個のスレッドがあるか
        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;
    }
}

さいごに

これは空間フィルタリングのやり方さえ覚えていれば簡単だったかもしれません。

ここらへんからフィルタ関係が続くのであまりコードに変化はありませんが、良かったらお付き合いください。

ではまた。