はじめに
今日も画像処理100本ノックをしていきましょう。
前回 -> 【Unity】ComputeShaderで大津の2値化をしてみる【Q4】 - はなちるのマイノート
次回 -> 【Unity】ComputeShaderで減色処理(ポスタライズ)をしてみる【Q6】 - はなちるのマイノート
今回はHSV変換について取り組んでいきましょう。
HSV変換とは
RGBは赤・緑・青で色を表現していましたが、HSVは色相・彩度・明度で色を表現したものです。
RGB -> HSV
の変換式はこちら。
HSV -> RGB
の変換式はこれ。
コード
今回はRGB -> HSV
,Hを反転(180加算),HSV -> RGB
をすることを目的としています。
ComputeShader
はこちら。
#pragma kernel InvertHue RWTexture2D<float4> Result; Texture2D<float4> Texture; [numthreads(8,8,1)] void InvertHue (uint3 id : SV_DispatchThreadID) { //---------------- //- RGB -> HSV - //---------------- float h,s,v; float r = Texture[id.xy].x; float g = Texture[id.xy].y; float b = Texture[id.xy].z; float maxValue = max(r, max(g, b)); float minValue = min(r, min(g, b)); if(maxValue == minValue){ h = 0; } else if(minValue == b){ h = 60 * (g - r) / (maxValue - minValue) + 60; } else if(minValue == r){ h = 60 * (b - g) / (maxValue - minValue) + 180; } else if(minValue == g){ h = 60 * (r - b) / (maxValue - minValue) + 300; } s = maxValue - minValue; v = maxValue; //----------------- //- 色相を反転させる - //----------------- h = fmod(h + 180, 360); //---------------- //- HSV -> RGB - //---------------- float c = s; float h2 = h / 60; float x = c * (1 - abs(fmod(h2, 2) - 1)); r = v - c; g = v - c; b = v - c; if(h2 < 1){ r += c; g += x; } else if(h2 < 2){ r += x; g += c; } else if(h2 < 3){ g += c; b += x; } else if(h2 < 4){ g += x; b += c; } else if(h2 < 5){ r += x; b += c; } else if(h2 < 6){ r += c; b += x; } Result[id.xy] = float4(r, g, b,1); }
C#のコードはこちら。
using UnityEngine; using UnityEngine.UI; public class HSVConverter : 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(); // InvertHueのカーネルインデックス(0)を取得 var kernelIndex = _computeShader.FindKernel("InvertHue"); // 一つのグループの中に何個のスレッドがあるか 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; } }
さいごに
実装は数式通りにすればいいので簡単でしたが、if分が多くなってしまったのでパフォーマンス的にあんまりよくないのかもしれません。
またcomputeShader
はhlsl
を使っているらしいのですが、おそらくメソッドからメソッドを呼ぶこともできますよね。
そこでGPU側のコードもみやすくするために分割した方が良さそうな気もします。
とりあえず今回はこれくらいで。
ではまた。