最近在忙人生另一半的事,还有个小外单,确实是有些忙不过来。虽然是有点忙,但还是能勉强维持每月1~2篇原创知识文章的产量給大家。废话不说,这篇文章主要是简单介绍一些图像处理的基本方法。内容讲解基于GPU.Shader语言,但不妨碍认识层面上的理解。在OpenCV和其他图像处理SDK当中同样适用。事不宜迟,show the code.
介绍三维光照的时候已经接触过,只需要在原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
曝光度和亮度的原理基本上是一致的,亮度是全方位的线性增加色值,而曝光度是基于原色值的指数型叠加(红的会更红,绿的会更绿,蓝的会更蓝,白光的会更光)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
接触了两个比较简单的基础处理方法之后,接下来看一个入门级别的。 首先来了解一下什么叫色彩饱和度:饱和度是指色彩的鲜艳程度,也称色彩的纯度。饱和度取决于该色中含色成分和消色成分(灰色)的比例。含色成分越大,饱和度越大;消色成分越大,饱和度越小。纯的颜色都是高度饱和的,如鲜红,鲜绿。混杂上白色,灰色或其他色调的颜色,是不饱和的颜色,如绛紫,粉红,黄褐等,完全不饱和的颜色根本没有色调,如黑白之间的各种灰色。
概念总结:饱和度 = 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是依据人眼识别的颜色定义出的空间,可表示大部分颜色。是图像处理中最基本、最常用、面向硬件的颜色空间,是一种光混合的体系。
可以看到RGB颜色模式用三维空间中的一个点表示一种颜色,每个点有三个分量,分别表示红、绿、蓝的亮度值,亮度值限定为【0,1】。在RGB模型的立方体中,原点对应的颜色为黑色,它的三个分量值都为0;距离原点最远的顶点对应的颜色为白色,它的三个分量值都为1。从黑色到白色的灰度值分布在这两个点的连线上,该虚线称为灰度线;立方体的其余各点对应不同的颜色,即三原色红、绿、蓝及其混合色黄、品红、青色。
(2)HSI色彩模型,视觉传输传播使用
HSI色彩空间是从人的视觉系统出发,用色调(Hue)、饱和度(Saturation或Chroma)和亮度 (Intensity或Brightness)来描述色彩。
H——表示颜色的相位角。红、绿、蓝分别相隔120度;互补色分别相差180度,即颜色的类别。
S——表示成所选颜色的纯度和该颜色最大的纯度之间的比率,范围:[0, 1],即颜色的深浅程度。
I——表示色彩的明亮程度,范围:[0, 1],人眼对亮度很敏感!
可以看到HSI色彩空间和RGB色彩空间只是同一物理量的不同表示法,因而它们之间存在着转换关系:HSI颜色模式中的色调使用颜色类别表示,饱和度与颜色的白光光亮亮度刚好成反比,代表灰色与色调的比例,亮度是颜色的相对明暗程度。
(3)CMYK模型,用于印刷品依靠反光的色彩模式
CMYK是一种依靠反光的色彩模式,我们是怎样阅读报纸的内容呢?是由阳光或灯光照射到报纸上,再反射到我们的眼中,才看到内容。它需要有外界光源,如果你在黑暗房间内是无法阅读报纸的。只要在屏幕上显示的图像,就是RGB模式表现的。只要是在印刷品上看到的图像,就是CMYK模式表现的。大多数在纸上沉积彩色颜料的设备,如彩色打印机和复印机,要求输入CMY数据,在内部进行RGB到CMY的转换。
青色Cyan、品红色Magenta、黄色Yellow是光的二次色,是颜料的颜色。而K取的是black最后一个字母,之所以不取首字母,是为了避免与蓝色(Blue)混淆。当红绿蓝三原色被混合时,会产生白色,当混合青色、品红色、黄色三原色时会产生黑色。从理论上来说,只需要CMY三种油墨就足够了,但是由于目前制造工艺还不能造出高纯度的油墨,CMY相加的结果实际是一种暗红色。
以上三种颜色模型是一系列颜色的数学表现形式。三种最流行的颜色模型是RGB(用于计算机图形);YIQ,YUV或YCbCr(用于视频系统)和CMYK(用于彩色打印)。但是,这三种颜色没有一种和我们直觉概念上的色调,饱和度,亮度有直接的联系。这就使我们暂时去追寻其它的模型,它们能简化编程,处理和终端用户操作。
最后一个色调,算是入门以上的图像处理手法。理论部分较多且深,要慢慢理解。先来了解什么是色调,如何定义色调。
色调不是指颜色的性质,是对一幅绘画作品的整体评价。譬如一幅画中画面色彩的总体倾向,是大的色彩效果,这幅画作品当中,虽然可能用了多种颜色,但总体有一种色调,是偏蓝或偏红,是偏暖或偏冷等等。这种在不同颜色的物体上,笼罩着某一种色彩,使不同颜色的物体都带有同一色彩倾向,这样的色彩现象就是色调。
在三大色彩模型当中,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