はじめに
画像処理100本ノックの続きをやっていきましょう。
前回 -> 【Unity】ComputeShaderでメディアンフィルタを実装してみる【Q10】 - はなちるのマイノート
次回 -> 【Unity】ComputeShaderでモーションフィルタを実装してみる【Q12】 - はなちるのマイノート
今回は移動平均フィルタ(平均化)フィルタについて取り組んでいきます。
移動平均フィルタとは
これは注目する画素とその周辺の画素の画素値の平均を出力値にするフィルタです。
以下のようなフィルタを使えばOKです。
コード
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; } }
さいごに
これは空間フィルタリングのやり方さえ覚えていれば簡単だったかもしれません。
ここらへんからフィルタ関係が続くのであまりコードに変化はありませんが、良かったらお付き合いください。
ではまた。