はじめに
画像処理100本ノックの続きをやっていきましょう。
前回 -> 【Unity】ComputeShaderでMax-Minフィルタを実装してみる【Q13】 - はなちるのマイノート
次回 -> 【Unity】ComputeShaderでSobelフィルタを実装してみる【Q15】 - はなちるのマイノート
今回は微分フィルタについて取り組んでいきます。
微分フィルタとは
微分フィルタは輝度の急激な変化が起こっている部分のエッジを取り出すフィルタで、隣り合う画素同士の差を取ることで求められます。
横方向と縦方向の2つをやるところが今までと少し違いますね。
使うフィルタはこんな感じ。
コード
ComputeShader
はこんな感じ。
#pragma kernel DifferentialFilter RWTexture2D<float4> LengthResult; RWTexture2D<float4> WidthResult; Texture2D<float4> Texture; [numthreads(32,16,1)] void DifferentialFilter (uint3 id : SV_DispatchThreadID) { float3x3 lengthFilter = float3x3(0, 0, 0, 0, 1, 0, 0, -1, 0); float3x3 widthFilter = float3x3(0, 0, 0, -1, 1, 0, 0, 0, 0); float4 rgb2gray = float4(0.2126, 0.7152, 0.0722, 0); uint i; float lengthArray[9] = { dot(Texture[id.xy + int2(-1, -1)], rgb2gray) * lengthFilter[0][0], dot(Texture[id.xy + int2(0, -1)], rgb2gray) * lengthFilter[0][1], dot(Texture[id.xy + int2(1, -1)], rgb2gray) * lengthFilter[0][2], dot(Texture[id.xy + int2(-1, 0)], rgb2gray) * lengthFilter[1][0], dot(Texture[id.xy], rgb2gray) * lengthFilter[1][1], dot(Texture[id.xy + int2(1, 0)], rgb2gray) * lengthFilter[1][2], dot(Texture[id.xy + int2(-1, 1)], rgb2gray) * lengthFilter[2][0], dot(Texture[id.xy + int2(0, 1)], rgb2gray) * lengthFilter[2][1], dot(Texture[id.xy + int2(1, 1)], rgb2gray) * lengthFilter[2][2] }; float widthArray[9] = { dot(Texture[id.xy + int2(-1, -1)], rgb2gray) * widthFilter[0][0], dot(Texture[id.xy + int2(0, -1)], rgb2gray) * widthFilter[0][1], dot(Texture[id.xy + int2(1, -1)], rgb2gray) * widthFilter[0][2], dot(Texture[id.xy + int2(-1, 0)], rgb2gray) * widthFilter[1][0], dot(Texture[id.xy], rgb2gray) * widthFilter[1][1], dot(Texture[id.xy + int2(1, 0)], rgb2gray) * widthFilter[1][2], dot(Texture[id.xy + int2(-1, 1)], rgb2gray) * widthFilter[2][0], dot(Texture[id.xy + int2(0, 1)], rgb2gray) * widthFilter[2][1], dot(Texture[id.xy + int2(1, 1)], rgb2gray) * widthFilter[2][2] }; float lr = 0; float wr = 0; for(i = 0; i < 9; i++){ lr += lengthArray[i]; wr += widthArray[i]; } LengthResult[id.xy] = float4(lr, lr, lr, 1); WidthResult[id.xy] = float4(wr, wr, wr, 1); }
C#のコードはこちら。
using UnityEngine; using UnityEngine.UI; public class DifferentialFilter : MonoBehaviour { [SerializeField] private ComputeShader _computeShader; [SerializeField] private Texture2D _tex; [SerializeField] private RawImage _lengthRenderer; [SerializeField] private RawImage _widthRenderer; 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 lengthResult = new RenderTexture(_tex.width, _tex.height, 0, RenderTextureFormat.ARGB32); lengthResult.enableRandomWrite = true; lengthResult.Create(); var widthResult = new RenderTexture(_tex.width, _tex.height, 0, RenderTextureFormat.ARGB32); widthResult.enableRandomWrite = true; widthResult.Create(); // カーネルインデックスを取得 var kernelIndex = _computeShader.FindKernel("DifferentialFilter"); // 一つのグループの中に何個のスレッドがあるか 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, "LengthResult", lengthResult); _computeShader.SetTexture(kernelIndex, "WidthResult", widthResult); // GPUの処理を実行する _computeShader.Dispatch(kernelIndex, _tex.width / (int)threadSize.x, _tex.height / (int)threadSize.y, (int)threadSize.z); // テクスチャを適応 _lengthRenderer.texture = lengthResult; _widthRenderer.texture = widthResult; } }
さいごに
フィルタのfloat3x3
のところの[i][j]
のところってもしかして今まで間違ってた気がしたようなしなかったような。
とりあえず今回は確認したのでこの書き方で間違いないと思います。
float3x3(0, 0, 0, // row1 0, 1, 0, // row2 0, -1, 0); // row3
これが縦フィルタになっているのですが、上下反転していて少し見辛いですよね。
-1
の値が欲しいなら[2][1]
になるはずです。
まあ使っていれば慣れることを祈りましょう。
ではまた。