GLSLで画像処理をする中でぼかした画像を使いくなった。
何を使ってどうしようかなと少し考えたので整理しつつ書いておきます。
ぼかし、平滑化 / blur, smoothing
処理したい座標の画素値と周囲の画素値を使ってノイズをとると考えます。
取ってくる画素値の範囲や各値の重みを変えることで色々なフィルタになります。
まずはシンプルに注目点と8近傍の平均。
gl-reactでは処理する点(注目点)がuv
で与えられます。
これは正規化されているので解像度を与えて除算することで隣接点を取ります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
precision highp float; varying vec2 uv; uniform sampler2D t; uniform vec2 resolution; vec4 textureBlur() { vec2 lr = vec2(1, 0)/resolution; vec2 tb = vec2(0, 1)/resolution; vec4 color = vec4(0); color += texture2D(t, uv); color += texture2D(t, uv+lr); color += texture2D(t, uv-lr); color += texture2D(t, uv+tb); color += texture2D(t, uv-tb); color += texture2D(t, uv+lr+tb); color += texture2D(t, uv+lr-tb); color += texture2D(t, uv-lr+tb); color += texture2D(t, uv-lr-tb); return vec4(color.rgb/color.w, 1); } void main() { gl_FragColor = textureBlur(); } |
画素取得の境界値超えも処理されるので無視しています。
8近傍から拡張することを考えます。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
vec4 textureBlur(float x) { vec2 lr = vec2(1, 0)/resolution; vec2 tb = vec2(0, 1)/resolution; vec4 color = vec4(0); for(float i=-10.;i<10.;i++) for(float j=-10.;j<10.;j++) { if(abs(i)<=x && abs(j)<=x)color += texture2D(t, uv+lr*i+tb*j); } return vec4(color.rgb/color.w, 1); } void main() { gl_FragColor = textureBlur(1.); } |
GLSL
ではfor
文の条件で変数値を使えないので若干変な処理になってます。
これは見ての通り最大21×21
平均まで対応できます。無駄が多いなぁ。
ガウシアンやバイラテラルフィルタのような実装で重みの異なるフィルタを簡単に変更できるように実装するにはどうすればいいんだろう。
ベクトルも行列も4次元までしかないしそれぞれ1から書かないといけなさそう。
面倒なので時間がかかりそうなのでこれはやめておきます。
ノイズ除去の方法で注目画素ごとに違う処理をするものとしてk近傍やメディアンフィルタなどがあります。外れ値がある場合に有効なやつです。
これらは範囲内の画素値のソートが必要になるんですがテストだしバブルソートで済ませています。for
条件の制限のせいでさらに負荷が増えてますが。
また比較する画素値はRGB
のどれかを使ってもいいんですが、基本的なグレースケール化をしています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
precision highp float; varying vec2 uv; uniform sampler2D t; uniform vec2 resolution; vec3 vg = vec3(0.299, 0.587, 0.114); void bsort(inout vec4 cs[9]) { vec4 t; for(int i=0; i<8; i++) for(int j=1; j<9; j++) if(j<9-i && dot(cs[j-1].rgb, vg)>dot(cs[j].rgb, vg)){ t=cs[j-1]; cs[j-1]=cs[j]; cs[j]=t; } } vec4 textureBlur() { vec2 lr = vec2(2, 0)/resolution; vec2 tb = vec2(0, 2)/resolution; vec4 color = vec4(0); vec4 cs[9]; cs[0] = texture2D(t, uv); cs[1] = texture2D(t, uv+lr); cs[2] = texture2D(t, uv-lr); cs[3] = texture2D(t, uv+tb); cs[4] = texture2D(t, uv-tb); cs[5] = texture2D(t, uv+lr+tb); cs[6] = texture2D(t, uv+lr-tb); cs[7] = texture2D(t, uv-lr+tb); cs[8] = texture2D(t, uv-lr-tb); bsort(cs); return cs[4]; } void main() { gl_FragColor = textureBlur(); } |
ちなみにtextureBlur
の返値をcs[0]
(最低値)にすると収縮フィルタ(erode)、cs[8]
(最大値)にすると膨張フィルタ(dilate)になります。
あとは高速フーリエ変換(FFT
)してノイズを除去するなどもありますが、GLSL
で書ける気がしないのでなにか見つけたら追記することにします。
先鋭化 / Sharpening
ぼかした画像を使って逆にくっきりした画像を作ることもできます。
いわゆるアンシャープマスクです。
8近傍のtextureBlur
の返値部分をこう考えるとそのまま先鋭化になります。
1 2 3 4 5 |
//ぼかしによって失われた画素値 return vec4(texture2D(t, uv).rgb-color.rgb/color.w, 1); //元画像にぼかしでなくなる画素値を足す return vec4(2.*texture2D(t, uv).rgb-color.rgb/color.w, 1); |
左から元画像、ぼかし、差分、先鋭化です。髪の部分は特にわかりやすい。
先鋭化は同じようにラプラシアンフィルタなどのエッジ検出結果を元画像に足すなどしても可能です。