はじめに
画像処理100本ノックの続きをやっていきましょう。
前回 -> 【Unity】ComputeShaderでPrewittフィルタを実装してみる【Q16】 - はなちるのマイノート
次回 -> 【Unity】ComputeShaderでEmbossフィルタを実装してみる【Q18】 - はなちるのマイノート
今回はLaplacianフィルタについて取り組んでいきます。
Laplacianフィルタとは
ラプラシアンフィルタは2次微分を利用してエッジを検出するフィルタになります。
最近はフィルタの説明を結構省き気味だったので、今回は少し詳しく説明しようと思います。
高校数学で習う微分ですが、こんな公式で表されます。
ただし画像の世界ではピクセルで表されているので、h->0
というのは「隣同士」が限界になってしまいます。
それを考慮すると、むしろ小学生でも分かりそうなほど簡単な式で表されます。
f'(x) = f(x+1) - f(x)
これを画像処理の分野では、f
をI
に置き換えて、x方向に微分したものをIx
,y方向に微分したものをIy
と表現します。
次は大学で習うラプラシアンですが、公式はこちら。
偏微分が使われているので習っていないと難しく感じるかもしれませんが、複数の変数(f(x,y)みたいな関数で)のうち一つの変数だけが変化するものとみなし、残りの変数を定数として扱う手法です。
あんまり難しく考えずに微分のお兄ちゃん的なものだと思えばOKです。
これを画像処理の世界で表すと、
になります。
これをフィルタに表すとこちらになります。
コード
ComputeShader
はこちら。
using UnityEngine; using UnityEngine.UI; public class LaplacianFilter : 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(); // カーネルインデックスを取得 var kernelIndex = _computeShader.FindKernel("LaplacianFilter"); // 一つのグループの中に何個のスレッドがあるか 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; } }
C#側のコードはこちら。
#pragma kernel LaplacianFilter RWTexture2D<float4> Result; Texture2D<float4> Texture; [numthreads(32,16,1)] void LaplacianFilter (uint3 id : SV_DispatchThreadID) { float3x3 filter = float3x3(0, 1, 0, 1, -4, 1, 0, 1, 0); float4 rgb2gray = float4(0.2126, 0.7152, 0.0722, 0); uint i; float array[9] = { dot(Texture[id.xy + int2(-1, -1)], rgb2gray) * filter[0][0], dot(Texture[id.xy + int2(0, -1)], rgb2gray) * filter[0][1], dot(Texture[id.xy + int2(1, -1)], rgb2gray) * filter[0][2], dot(Texture[id.xy + int2(-1, 0)], rgb2gray) * filter[1][0], dot(Texture[id.xy], rgb2gray) * filter[1][1], dot(Texture[id.xy + int2(1, 0)], rgb2gray) * filter[1][2], dot(Texture[id.xy + int2(-1, 1)], rgb2gray) * filter[2][0], dot(Texture[id.xy + int2(0, 1)], rgb2gray) * filter[2][1], dot(Texture[id.xy + int2(1, 1)], rgb2gray) * filter[2][2] }; float tmp = 0; for(i = 0; i < 9; i++){ tmp += array[i]; } Result[id.xy] = float4(tmp, tmp, tmp, 1); }
さいごに
フィルタがどのように設計されているかを紹介しましたが、実際のコードはほとんどいつものと同じものです。
いままでの様々なフィルタにも色んな仕組みがあってフィルタが設計されているというわけですね。
ただ今回のLaplacianフィルタは結構数学のとっかかりとしては良いと思うので覚えておくと良いかもしれません。
ではまた。