前一篇文章中,實現了 opengles 進行相機預覽的功能,基本的流程如下:
- 把相機的預覽數據做成紋理,綁定到opengles對應的紋理單元上
- 然後通過opengles 的內置函數 texture(),在片段着色器中根據紋理和紋理座標進行插值計算
- 直接將計算結果輸出到顏色緩衝區,顯示到屏幕的像素上。
給圖像添加濾鏡本質上就是圖片處理,也就是對圖片的像素進行計算,簡單來說,圖像處理的方法可以分爲三類:
- 點算子:當前像素的處理只和自身的像素值有關,和其他像素無關,比如灰度處理。
- 鄰域算子:當前像素的處理需要和相鄰的一定範圍內的像素有關,比如高斯模糊。
- 全局算子:在全局上對所有像素進行統一變換,比如幾何變換。
實現濾鏡的思路
濾鏡本質上就是對每個位置的顏色值進行調整,比如灰度效果,就是將彩色圖像的各個顏色分量的值變成一樣的。
對顏色值進行調整的時機應該是再上面步驟的2、3之間,這個時候已經拿到了顏色值,還沒有輸出到顏色緩衝區,這個時候我們對顏色值進行處理就可以實現濾鏡效果。
用上一篇的片段着色器代碼做個說明:
#version 300 es
#extension GL_OES_EGL_image_external_essl3 : require
precision mediump float;
in vec2 v_texCoord;
out vec4 outColor;
uniform samplerExternalOES s_texture;
void main(){
//拿到顏色值
vec4 tmpColor = texture(s_texture, v_texCoord);
//對顏色值進行處理
process(tmpColor);
//將處理後的顏色值輸出到顏色緩衝區
outColor = tmpColor;
}
點算子實現的濾鏡
灰度濾鏡
灰度濾鏡通過圖像的灰度化算法進行實現。
在 RBG顏色模型中,讓 R=G=B=grey, 即可將彩色圖像轉爲灰度圖像,其中 grey 叫做灰度值。
grey的計算方法有四種:分量法,最大值法,平均值法、加權平均法。
分量法
使用彩色圖像的某個顏色分量的值作爲灰度值。
- grey=R:R分量灰度圖
- grey=G:G分量灰度圖
- grey=B:B分量灰度圖
最大值法
將彩色圖像三個顏色分量中值最大的作爲灰度值。
grey = max(R,G,B)
平均值法
將彩色圖像的三個顏色分量值的平均值作爲灰度值
grey = (R+G+B)/3
加權平均法
在 RGB 顏色模型中,人眼對G(綠色)的敏感度最高,對B(藍色)的敏感的最低,所以對彩色圖像的三個顏色分量做加權平均計算灰度值效果比較好。
grey = 0.3R + 0.59G + 0.11*B
下面在片段着色器中用加權平均法實現灰度濾鏡。
#version 300 es
#extension GL_OES_EGL_image_external_essl3 : require
precision mediump float;
in vec2 v_texCoord;
out vec4 outColor;
uniform samplerExternalOES s_texture;
//灰度濾鏡
void grey(inout vec4 color){
float weightMean = color.r * 0.3 + color.g * 0.59 + color.b * 0.11;
color.r = color.g = color.b = weightMean;
}
void main(){
//拿到顏色值
vec4 tmpColor = texture(s_texture, v_texCoord);
//對顏色值進行處理
grey(tmpColor);
//將處理後的顏色值輸出到顏色緩衝區
outColor = tmpColor;
}
效果圖
原圖 | 灰度濾鏡 |
---|---|
黑白濾鏡
黑白濾鏡就是將圖像進行二值化處理,彩色圖像的顏色值經過處理之後,要麼是 0(黑色),要麼是255(白色)。
實際應用中使用的不多,從各大直播、美顏相機、短視頻app上就能發現,基本上沒有用黑白濾鏡,因爲不好看。
二值化方法主要有:全局二值化,局部二值化,局部自適應二值化。最影響效果的就是閾值的選取。
- 全局二值化是選定一個閾值,然後將大於該閾值的顏色值置爲255,小於該閾值的顏色置爲0。因爲使用的全局閾值,所以會喪失很多細節。
- 局部二值化:爲了彌補全局閾值化的缺陷,將圖像分爲N個窗口,每個窗口設定一個閾值,進行二值化操作,一般取該窗口顏色值的平均值。
- 局部自適應二值化:局部二值化的閾值選取方法仍然不能很好的將對應窗口的圖像進行二值化,在此基礎上,通過窗口顏色的平均值E、像素之間的差平方P、像素之間的均方根Q等能夠表示窗口內局部特徵的參數,設定計算公式計算閾值。
這裏使用最簡單的全局二值化做個示例
//黑白濾鏡
void blackAndWhite(inout vec4 color){
float threshold = 0.5;
float mean = (color.r + color.g + color.b) / 3.0;
color.r = color.g = color.b = mean >= threshold ? 1.0 : 0.0;
}
效果圖
原圖 | 黑白濾鏡 |
---|---|
反色濾鏡
RGB 顏色值的範圍是 [0,255],反色濾鏡的的原理就是將 255 與當前顏色的每個分量Rs,Gs,Bs值做差運算。
結果顏色爲 (R,G,B) = (255 - Rs, 255 - Gs, 255 - Bs);
//反向濾鏡
void reverse(inout vec4 color){
color.r = 1.0 - color.r;
color.g = 1.0 - color.g;
color.b = 1.0 - color.b;
}
效果圖
原圖 | 反色濾鏡 |
---|---|
亮度濾鏡
增加亮度有兩種方法:
- 再 rgb 顏色空間下,將各個顏色分量都加上一個值,可以達到圖像亮度增加的目的,但是這種方式會導致圖像一定程度上偏白。
- 將顏色值從 rgb 顏色空間轉換到 hsl 顏色空間上,因爲 hsl 更適合視覺上的描述,色相、飽和度、亮度,調整 l(亮度分量),即可實現圖像的亮度處理,然後將調整後的 hsl 值再轉換到 rgb 顏色空間上進行輸出。
下面以第2中方式爲例:
//rgb轉hsl
vec3 rgb2hsl(vec3 color){
vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
vec4 p = mix(vec4(color.bg, K.wz), vec4(color.gb, K.xy), step(color.b, color.g));
vec4 q = mix(vec4(p.xyw, color.r), vec4(color.r, p.yzx), step(p.x, color.r));
float d = q.x - min(q.w, q.y);
float e = 1.0e-10;
return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
}
//hsla轉rgb
vec3 hsl2rgb(vec3 color){
vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
vec3 p = abs(fract(color.xxx + K.xyz) * 6.0 - K.www);
return color.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), color.y);
}
//亮度
void light(inout vec4 color){
vec3 hslColor = vec3(rgb2hsl(color.rgb));
hslColor.z += 0.15;
color = vec4(hsl2rgb(hslColor), color.a);
}
原圖 | 亮度濾鏡 |
---|---|