10 行代碼搞定「熱成像」【shader風格化】

做者:這是上帝的傑做javascript

來源:https://zhuanlan.zhihu.com/p/344110917java


已通過去的 2020 是一個不怎麼順遂的一年,出入公共場所都須要體溫監測,而人流量密集的商場,通常會採用熱成像技術來快速測量體溫。那麼今天咱們就來講說如何讓一張普通圖片變成具備熱成像的效果。


最終效果以下:nginx

最終效果web

從本文中你能夠了解到:微信


  1. 發光的基本原理,如何給圖片施加不一樣顏色的光app

  2. 圖片模糊的原理,如何用高斯模糊解決重影問題編輯器

  3. colorRamp是什麼?texture2D的一些高級用法,如何使用colorRamp改變圖片的色彩風格函數

  4. 如何結合以上技術實現熱成像效果學習


本文再也不過多贅述webglAPI的使用。只關注片斷着色器,下面給出最基本的一個着色器:flex


precision mediump float;/*  變量申明*/varying vec2 uv; // 從頂點着色器傳入的UV座標uniform sampler2D u_texture0; // 全局變量,紋理1(就是空洞騎士的原圖)uniform sampler2D u_texture1; // 全局變量,紋理2(後續咱們要使用的ColorRamp)uniform float u_r; // 全局變量,ColorRamp中紅通道權重uniform float u_g; // 全局變量,ColorRamp中綠通道權重uniform float u_b; // 全局變量,ColorRamp中藍通道權重uniform float u_blurIntensity; // 全局變量,模糊強度uniform float u_negativeAmount; // 全局變量,負片程度uniform float u_colorRampIntensity; // 全局變量,ColorRamp光度強度uniform float u_glowAmount; // 全局變量,發光強度uniform vec3 u_glowColor; // 全局變量,發光顏色void main () { vec4 color = texture2D (u_texture0, uv); gl_FragColor =color;}


在第一部分變量生命中中咱們從web層傳入了一系列變量,如今看可能有點雲裏霧裏彆着急,咱們後面會逐個提到,在main函數中咱們獲取了紋理信息,並把圖片呈現出來:

發光效果(Glow)


在GLSL中顏色用一個四維向量表示:


vec4 color = vec4(0,0,1.0,0.5,1.0);

  • 四個份量分別表示RGBA是個通道

  • 若是要取出好比rgb構成一個三維向量,能夠簡寫成color.rgb

  • 顏色值範圍在[0,1]之間,對應web經常使用的顏色區間[0,255]


咱們要介紹的第一個效果是發光,發光簡單來講就是讓圖片變量。最原始的想法就是把顏色值變大:


void main () { vec4 color = texture2D (u_texture0, uv); color *= 2.0; gl_FragColor =color;}


和咱們期待的同樣果真變量了:

但可否不只僅想讓圖片發白光,而是能夠發任意顏色的光?很天然地咱們想到讓color乘以另外一個color:


....vec4 color = texture2D (u_texture0, uv); color.rgb *= vec3(1.0,0.0,0.5); //這是洋紅色....


圖片的確如咱們所想,畫面變紅了,可是卻變暗了,這是因爲在 vec3(1.0,0.0,0.5,); 的綠通道值爲0,向量相乘後,本來圖片中的綠通道信息所有丟失,變爲0,(讀者不妨驗證一下變黑幅度大的區域,是否就是原來比較綠的區域)


爲此咱們須要使用加和而不是乘積的形式:


color.rgb += vec3(1.0,0.0,0.5)*color.rgb;


最後咱們將顏色和強度替換成咱們,傳入的變量,發光的效果就實現了:


....  vec4 color = texture2D (u_texture0, uv); vec4 emission = color; emission.rgb *= u_glowAmount * u_glowColor; color.rgb += emission.rgb; color.rgb *= color.a; gl_FragColor =color;....

模糊(Blur)

接下來咱們來說第二種效果模糊,核心思路也很簡單,若是一個像素點的顏色信息摻雜了周圍點的顏色信息,那麼圖片就會模糊。


爲此咱們選擇講像素點上下左右四個點的信息融合:


.....void main () { vec4 color = texture2D (u_texture0, uv); float step = 0.005; // 申明一個變量,控制像素偏移的步幅 color += texture2D (u_texture0, uv+vec2(step,0.0)); // 正上方點 color += texture2D (u_texture0, uv+vec2(-step,0.0)); // 正下方點 color += texture2D (u_texture0, uv+vec2(0.0,step)); // 左側點 color += texture2D (u_texture0, uv+vec2(0.0,-step)); // 右側點 color /= 5.0; // 取平均.....

爲了讓效果更佳,咱們引入權重體系,讓本來的點在結果中貢獻最大設權重爲4,正方向的點權重爲2,45度方向的點權重爲1:

 vec4 color = 4.0*texture2D (u_texture0, uv); float step = 0.005; // 申明一個變量,控制像素偏移的步幅 color += 2.0*texture2D (u_texture0, uv+vec2(step,0.0)); // 正上方點 color += 2.0*texture2D (u_texture0, uv+vec2(-step,0.0)); // 正下方點 color += 2.0*texture2D (u_texture0, uv+vec2(0.0,step)); // 左側點 color += 2.0*texture2D (u_texture0, uv+vec2(0.0,-step)); // 右側點 color += 1.0*texture2D (u_texture0, uv+vec2(-step,-step)); // 第4象限點 color += 1.0*texture2D (u_texture0, uv+vec2(step,-step)); // 第3象限點 color += 1.0*texture2D (u_texture0, uv+vec2(-step,step)); // 第2象限點 color += 1.0*texture2D (u_texture0, uv+vec2(step,step)); // 第1象限點 color /= (4.0+4.0*2.0+4.0*1.0);        // 取平均


正當咱們滿心歡喜第把step改爲咱們傳入的變量u_blurIntensity(控制模糊強度時)悲劇發生了:


這是由於咱們雖然加上了權重,但點的影響值卻沒有隨 step 的怎大而遞減,舉個例子:就是當步幅爲 0.001 和 0.1 時正上方的點貢獻都是 2(而實際上 0.001 要比 0.1 有更大的權重,由於它更接近原始點)。


因此當step增大時很明顯就會出現重影。


爲此咱們參考高斯模糊,引入一個指數函數:

該函數隨着傳入的x的增大而減少,因而咱們改造上述代碼:


  • 將取值點由3*3增長到了8*8;

  • 採用遞減的指數函數:exp(-(x * x)/(2.0* bhqp * bhqp));

  • 控制了偏移的範圍(float(iy - halfIterations)*0.00390625);


float Blur_Gauss (float bhqp, float x) { return exp (-(x * x) / (2.0 * bhqp * bhqp));}vec4 Blur (vec2 uv, sampler2D source, float Intensity) { const int iterations = 16; // 常量才能夠進行for循環 int halfIterations = iterations / 2; float sigmaX = 0.1 + Intensity * 0.5; float sigmaY = sigmaX; float total = 0.0; vec4 ret = vec4 (0., 0., 0., 0.); float step = 0.00390625; // 增多到8*8個點 for (int iy = 0; iy < iterations; ++iy) { float fy = Blur_Gauss (sigmaY, float (iy - halfIterations)); float offsety = float (iy - halfIterations) * step; for (int ix = 0; ix < iterations; ++ix) { float fx = Blur_Gauss (sigmaX, float (ix - halfIterations)); float offsetx = float (ix - halfIterations) * step; total += fx * fy; vec4 a = texture2D (source, uv + vec2 (offsetx, offsety)); a.rgb *= a.a; ret += a * fx * fy; } } return ret / total;}.....color = Blur (uv, u_texture0, u_blurIntensity);.....

負片(negative)

這個多是本文最簡單的一個效果,就是讓圖片呈現出負片的效果,代碼以下:


color.rgb = abs(u_negativeAmount - color.rgb);


colorRamp

colorRamp 簡單理解就是色卡,就是下面這種:

在進一步說明 colorRamp 前,咱們先想一想如何把圖片變成黑白?


最直接的想法就是把R,G,B三個通道的顏色取平均:

......vec4 color = texture2D (u_texture0, uv); float p = color.r+color.g+color.b;color.rgb = vec3(p/3.0);......

其實這件事也能夠用ColorRamp實現,咱們想找一個由黑到白的色表,傳入着色器做爲u_texture1。


紋理最左側的點x座標就是0,最右側的點x座標就是1,所以黑白效果的代碼能夠寫成:

......  vec4 color = texture2D (u_texture0, uv);  float p = color.r+color.g+color.b; color.rgb = texture2D (u_texture1, vec2 (p/3.0, .0)).rgb;......


這裏浮點素P記錄了原始圖片(u_texture0)的信息,同時它制定了規則,輸出的顏色是R,G,B三個通道值取平均的結果。


固然一個黑白效果沒必要如此大費周章,生產環境中ColorRamp更加繽紛,下面咱們依舊使用(color.r+color.g+color.b)/3.0做爲取色規則,來看看一些coloRamp的效果:


固然咱們不會侷限於一種規則,爲此咱們使用u_r,u_g,u_b分別控制紅綠藍三色通道權重:

......  vec4 color = texture2D (u_texture0, uv);  float p = dot(color.rgb,vec3(u_r,u_g,u_b)); // 等同於color.r*u_r+color.g*u_g+color.b*u_b p += u_colorRampIntensity; // u_colorRampIntensity控制顏色統一貫colorRamp的左/右側移動 color.rgb = texture2D (u_texture1, vec2 (p, .0)).rgb;......


咱們一上圖中第二種紋理爲例,實現效果以下:


最總咱們使用下面這個風格的colorRamp:

最後就是見證奇蹟的是時刻了,咱們把上面所過的效果合起來(發光=>colorRamp=>負片=>模糊):

最終效果

固然這個 shader 也可用在真人身上,好比封面圖。其中 blurIntensity 越大風格越卡通,你們能夠本身實踐一下。


很經常使用的一張表情包



-- END --


進技術交流羣,掃碼添加個人微信:Byte-Flow



獲取視頻教程和源碼



推薦:

利用 OpenGL ES 給視頻播放器作個字符畫濾鏡

字節流動 OpenGL ES 技術交流羣來啦

Android OpenGL 渲染圖像讀取哪家強?

FFmpeg + OpenGL ES 實現 3D 全景播放器

一文掌握 YUV 圖像的基本處理

Android OpenGL ES 從入門到精通系統性學習教程

OpenGL ES 實現動態(水波紋)漣漪效果


以爲不錯,點個在看唄~

本文分享自微信公衆號 - 字節流動(google_developer)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索