天天看點

OpenGL.Shader:志哥教你寫一個濾鏡直播用戶端(7)視覺濾鏡:亮度、曝光度、飽和度、色調OpenGL.Shader:志哥教你寫一個濾鏡直播用戶端(7)

OpenGL.Shader:志哥教你寫一個濾鏡直播用戶端(7)

最近在忙人生另一半的事,還有個小外單,确實是有些忙不過來。雖然是有點忙,但還是能勉強維持每月1~2篇原創知識文章的産量給大家。廢話不說,這篇文章主要是簡單介紹一些圖像處理的基本方法。内容講解基于GPU.Shader語言,但不妨礙認識層面上的了解。在OpenCV和其他圖像處理SDK當中同樣适用。事不宜遲,show the code.

1、亮度

介紹三維光照的時候已經接觸過,隻需要在原RGB三色通道的基礎上+光照值就可以了,shader代碼如下:

precision mediump float;

varying highp vec2 textureCoordinate; // 從頂點着色傳過來的紋理坐标

uniform sampler2D SamplerY; // Y分量

uniform sampler2D SamplerU; // U分量

uniform sampler2D SamplerV; // V分量

mat3 colorConversionMatrix = mat3(

                   1.0, 1.0, 1.0,

                   0.0, -0.39465, 2.03211,

                   1.13983, -0.58060, 0.0);

vec3 yuv2rgb(vec2 pos)

{

   vec3 yuv;

   yuv.x = texture2D(SamplerY, pos).r;

   yuv.y = texture2D(SamplerU, pos).r - 0.5;

   yuv.z = texture2D(SamplerV, pos).r - 0.5;

   return colorConversionMatrix * yuv;

}

// yuv三分量轉rgb的方法。

uniform lowp float brightness; // 光照強度。

void main()

{

    vec4 textureColor = vec4(yuv2rgb(textureCoordinate), 1.0);

    gl_FragColor = vec4((textureColor.rgb + vec3(brightness)), textureColor.w); // 原rgb分量基礎上+brightness,保留w

}

//光照強度建議控制在  -0.5~0.5,1的效果已經就是泛白了
void setAdjustEffect(float percent) {
    mBrightness = range(percent * 100.0f, -0.5f, 0.5f);
}
float range(float percentage, float start, float end) {
    return (end - start) * percentage / 100.0f + start;
}
           

亮度的片元着色器代碼如上,效果比較簡單就不提供上來了,大家可以在demo工程上嘗試嘗試。 具體代碼參照 https://github.com/MrZhaozhirong/NativeCppApp     cpp/gpufilter/filter/GpuBrightnessFilter.hpp

2、曝光度

曝光度和亮度的原理基本上是一緻的,亮度是全方位的線性增加色值,而曝光度是基于原色值的指數型疊加(紅的會更紅,綠的會更綠,藍的會更藍,白光的會更光)shader代碼如下:

precision mediump float;

varying highp vec2 textureCoordinate;

uniform sampler2D SamplerY;

uniform sampler2D SamplerU;

uniform sampler2D SamplerV;

mat3 colorConversionMatrix = mat3(

                   1.0, 1.0, 1.0,

                   0.0, -0.39465, 2.03211,

                   1.13983, -0.58060, 0.0);

vec3 yuv2rgb(vec2 pos)

{

   vec3 yuv;

   yuv.x = texture2D(SamplerY, pos).r;

   yuv.y = texture2D(SamplerU, pos).r - 0.5;

   yuv.z = texture2D(SamplerV, pos).r - 0.5;

   return colorConversionMatrix * yuv;

}

uniform lowp float exposure; // 曝光度效果值

void main()

{

    vec4 textureColor = vec4(yuv2rgb(textureCoordinate), 1.0);

    gl_FragColor = vec4((textureColor.rgb * pow(2.0, exposure)), textureColor.w);  // rgb * 2^效果值

}

曝光度也應該有上下限,防止過度曝光。

void setAdjustEffect(float percent) {
    // (0.0 as the default)
    mExposure = range(percent * 100.0f, -5.0f, 5.0f);
}
           

曝光度的片元着色器代碼如上,效果比較簡單就不提供上來了,大家可以在demo工程上嘗試嘗試。 具體代碼參照 https://github.com/MrZhaozhirong/NativeCppApp     cpp/gpufilter/filter/GpuExposureFilter.hpp

3、飽和度

接觸了兩個比較簡單的基礎處理方法之後,接下來看一個入門級别的。  首先來了解一下什麼叫色彩飽和度:飽和度是指色彩的鮮豔程度,也稱色彩的純度。飽和度取決于該色中含色成分和消色成分(灰色)的比例。含色成分越大,飽和度越大;消色成分越大,飽和度越小。純的顔色都是高度飽和的,如鮮紅,鮮綠。混雜上白色,灰色或其他色調的顔色,是不飽和的顔色,如绛紫,粉紅,黃褐等,完全不飽和的顔色根本沒有色調,如黑白之間的各種灰色。 

概念總結:飽和度 = X·原色 + Y·灰階值,其中(x+y=1)

其中使用Luma算法求算灰階:Gray = R*0.2125 + G*0.7154 + B*0.0721

precision mediump float;

varying highp vec2 textureCoordinate;

uniform sampler2D SamplerY;

uniform sampler2D SamplerU;

uniform sampler2D SamplerV;

mat3 colorConversionMatrix = mat3(

                   1.0, 1.0, 1.0,

                   0.0, -0.39465, 2.03211,

                   1.13983, -0.58060, 0.0);

vec3 yuv2rgb(vec2 pos)

{

   vec3 yuv;

   yuv.x = texture2D(SamplerY, pos).r;

   yuv.y = texture2D(SamplerU, pos).r - 0.5;

   yuv.z = texture2D(SamplerV, pos).r - 0.5;

   return colorConversionMatrix * yuv;

}

uniform float saturation; // 飽和度比值

const mediump vec3 luminanceWeight = vec3(0.2125, 0.7154, 0.0721);  // Luma算子

void main()

{

    vec4 textureColor = vec4(yuv2rgb(textureCoordinate), 1.0);

    lowp float luminance = dot(textureColor.rgb, luminanceWeight);  // 求算灰階值

    lowp vec3 greyScaleColor = vec3(luminance);

    gl_FragColor = vec4(mix(greyScaleColor, textureColor.rgb, saturation), textureColor.w);

    // GLSL内置函數 mix(x,y,a) = x*(1-a)+y*a,剛好滿足飽和度的公式定義。

}

 從定義公式可知,saturation飽和度比值的正常可取範圍是(0~1),既然有正常的範圍,那肯定就有不正常的範圍,大家可以恰當的調整上下限,比較其實際效果。

void setAdjustEffect(float percent) {
    mSaturation = range(percent * 100.0f, 0.0f, 1.0f); // 2.0f
}
           

飽和度的片元着色器代碼如上,效果比較簡單就不提供上來了,大家可以在demo工程上嘗試嘗試。 具體代碼參照 https://github.com/MrZhaozhirong/NativeCppApp     cpp/gpufilter/filter/GpuSaturationFilter.hpp

到這裡先運作我引導前言知識,數字圖像處理當中的三大色彩模型:RGB、HSI、CMYK(注意!這裡不是格式,是色彩模型)

(1)最常用的RGB色彩模型。

RGB是依據人眼識别的顔色定義出的空間,可表示大部分顔色。是圖像進行中最基本、最常用、面向硬體的顔色空間,是一種光混合的體系。

OpenGL.Shader:志哥教你寫一個濾鏡直播用戶端(7)視覺濾鏡:亮度、曝光度、飽和度、色調OpenGL.Shader:志哥教你寫一個濾鏡直播用戶端(7)

可以看到RGB顔色模式用三維空間中的一個點表示一種顔色,每個點有三個分量,分别表示紅、綠、藍的亮度值,亮度值限定為【0,1】。在RGB模型的立方體中,原點對應的顔色為黑色,它的三個分量值都為0;距離原點最遠的頂點對應的顔色為白色,它的三個分量值都為1。從黑色到白色的灰階值分布在這兩個點的連線上,該虛線稱為灰階線;立方體的其餘各點對應不同的顔色,即三原色紅、綠、藍及其混合色黃、品紅、青色。

(2)HSI色彩模型,視覺傳輸傳播使用

HSI色彩空間是從人的視覺系統出發,用色調(Hue)、飽和度(Saturation或Chroma)和亮度 (Intensity或Brightness)來描述色彩。

H——表示顔色的相位角。紅、綠、藍分别相隔120度;互補色分别相差180度,即顔色的類别。

S——表示成所選顔色的純度和該顔色最大的純度之間的比率,範圍:[0,  1],即顔色的深淺程度。

I——表示色彩的明亮程度,範圍:[0, 1],人眼對亮度很敏感!

OpenGL.Shader:志哥教你寫一個濾鏡直播用戶端(7)視覺濾鏡:亮度、曝光度、飽和度、色調OpenGL.Shader:志哥教你寫一個濾鏡直播用戶端(7)

可以看到HSI色彩空間和RGB色彩空間隻是同一實體量的不同表示法,因而它們之間存在着轉換關系:HSI顔色模式中的色調使用顔色類别表示,飽和度與顔色的白光光亮亮度剛好成反比,代表灰色與色調的比例,亮度是顔色的相對明暗程度。

(3)CMYK模型,用于印刷品依靠反光的色彩模式

CMYK是一種依靠反光的色彩模式,我們是怎樣閱讀報紙的内容呢?是由陽光或燈光照射到報紙上,再反射到我們的眼中,才看到内容。它需要有外界光源,如果你在黑暗房間内是無法閱讀報紙的。隻要在螢幕上顯示的圖像,就是RGB模式表現的。隻要是在印刷品上看到的圖像,就是CMYK模式表現的。大多數在紙上沉積彩色顔料的裝置,如彩色列印機和影印機,要求輸入CMY資料,在内部進行RGB到CMY的轉換。

OpenGL.Shader:志哥教你寫一個濾鏡直播用戶端(7)視覺濾鏡:亮度、曝光度、飽和度、色調OpenGL.Shader:志哥教你寫一個濾鏡直播用戶端(7)

青色Cyan、品紅色Magenta、黃色Yellow是光的二次色,是顔料的顔色。而K取的是black最後一個字母,之是以不取首字母,是為了避免與藍色(Blue)混淆。當紅綠藍三原色被混合時,會産生白色,當混合青色、品紅色、黃色三原色時會産生黑色。從理論上來說,隻需要CMY三種油墨就足夠了,但是由于目前制造技術還不能造出高純度的油墨,CMY相加的結果實際是一種暗紅色。

以上三種顔色模型是一系列顔色的數學表現形式。三種最流行的顔色模型是RGB(用于計算機圖形);YIQ,YUV或YCbCr(用于視訊系統)和CMYK(用于彩色列印)。但是,這三種顔色沒有一種和我們直覺概念上的色調,飽和度,亮度有直接的聯系。這就使我們暫時去追尋其它的模型,它們能簡化程式設計,處理和終端使用者操作。

4、色調(色相)

最後一個色調,算是入門以上的圖像處理手法。理論部分較多且深,要慢慢了解。先來了解什麼是色調,如何定義色調。

色調不是指顔色的性質,是對一幅繪畫作品的整體評價。譬如一幅畫中畫面色彩的總體傾向,是大的色彩效果,這幅畫作品當中,雖然可能用了多種顔色,但總體有一種色調,是偏藍或偏紅,是偏暖或偏冷等等。這種在不同顔色的物體上,籠罩着某一種色彩,使不同顔色的物體都帶有同一色彩傾向,這樣的色彩現象就是色調。

在三大色彩模型當中,HSI色彩模型是能較好接近直覺概念上的色調,飽和度,亮度的聯系。在這方面rgb格式不友善計算,是以要先轉成為友善色調、飽和度、亮度計算的YIQ\YUV格式。這裡選用YIQ格式,有關YIQ格式的疑問,可以看這篇譯文https://www.hisour.com/zh/yiq-color-space-26084/   轉換格式之後,然後還要轉換顔色模型,從RGB模型轉為HSI模型,友善線性調整色調。

說那麼多屁話,還是看代碼吧!

precision mediump float;

varying highp vec2 textureCoordinate;

uniform sampler2D SamplerY;

uniform sampler2D SamplerU;

uniform sampler2D SamplerV;

mat3 colorConversionMatrix = mat3(

                   1.0, 1.0, 1.0,

                   0.0, -0.39465, 2.03211,

                   1.13983, -0.58060, 0.0);

vec3 yuv2rgb(vec2 pos)

{

   vec3 yuv;

   yuv.x = texture2D(SamplerY, pos).r;

   yuv.y = texture2D(SamplerU, pos).r - 0.5;

   yuv.z = texture2D(SamplerV, pos).r - 0.5;

   return colorConversionMatrix * yuv;

}

uniform mediump float hueAdjust; // 使用者調整值

// RGB to YIQ的矩陣向量

const highp vec4 kRGBToYPrime = vec4 (0.299, 0.587, 0.114, 0.0);

const highp vec4 kRGBToI = vec4 (0.595716, -0.274453, -0.321263, 0.0);

const highp vec4 kRGBToQ = vec4 (0.211456, -0.522591, 0.31135, 0.0);

// YIQ to RGB的矩陣向量

const highp vec4 kYIQToR = vec4 (1.0, 0.9563, 0.6210, 0.0);

const highp vec4 kYIQToG = vec4 (1.0, -0.2721, -0.6474, 0.0);

const highp vec4 kYIQToB = vec4 (1.0, -1.1070, 1.7046, 0.0);

void main()

{

    //# 提取RGB

    vec4 textureColor = vec4(yuv2rgb(textureCoordinate), 1.0);

    //# 轉換為YIQ

    highp float YPrime = dot(textureColor, kRGBToYPrime);

    highp float I = dot(textureColor, kRGBToI);

    highp float Q = dot(textureColor, kRGBToQ);

    //# 依據HSI模型,提取色調色度

    highp float hue = atan(Q, I);

    highp float chroma = sqrt(I * I + Q * Q);

    //# 外部動态調整色調

    hue -= hueAdjust;

    //# 轉變回HSI模型的YIQ格式數值

    Q = chroma * sin (hue);

    I = chroma * cos (hue);

    //# 再轉換回RGB 用于顯示

    highp vec4 yIQ = vec4 (YPrime, I, Q, 0.0);

    textureColor.r = dot(yIQ, kYIQToR);

    textureColor.g = dot(yIQ, kYIQToG);

    textureColor.b = dot(yIQ, kYIQToB);

    gl_FragColor = textureColor;

}

以上代碼都帶上注釋了,還有不明白的歡迎私信。效果視訊以後再上傳。大家可以在demo工程上嘗試嘗試。 具體代碼參照 https://github.com/MrZhaozhirong/NativeCppApp     cpp/gpufilter/filter/GpuHueFilter.hpp

That is All.

興趣讨論群:703531738。暗号:志哥13567