色彩模型

RGB色彩模型。

RGB是依据人眼识其他色彩界说出的空间,可表明大部分色彩。是图画处理中最基本、最常用、面向硬件的色彩空间,是一种光混合的体系。

能够看到RGB色彩方式用三维空间中的一个点表明一种色彩,每个点有三个重量,别离表明红、绿、蓝的亮度值,亮度值限定为【0,1】。在RGB模型的立方体中,原点对应的色彩为黑色,它的三个重量值都为0;距离原点最远的顶点对应的色彩为白色,它的三个重量值都为1。从黑色到白色的灰度值分布在这两个点的连线上,该虚线称为灰度线;立方体的其他各点对应不同的色彩,即三原色红、绿、蓝及其混合色黄、品红、青色。

HSI色彩模型

HSI色彩空间是从人的视觉体系动身,用色彩(Hue)、饱满度(Saturation或Chroma)和亮度 (Intensity或Brightness)来描绘色彩。

  • H: 表明色彩的相位角。红、绿、蓝别离相隔120度;互补色别离相差180度,即色彩的类别。
  • S: 表明成所选色彩的纯度和该色彩最大的纯度之间的比率,规模:[0, 1],即色彩的深浅程度。
  • I: 表明色彩的亮堂程度,规模:[0, 1],人眼对亮度很灵敏!

能够看到HSI色彩空间和RGB色彩空间只是同一物理量的不同表明法,因而它们之间存在着转换联系:HSI色彩方式中的色彩运用色彩类别表明,饱满度与色彩的白光亮光亮度刚好成反比,代表灰色与色彩的份额,亮度是色彩的相对明暗程度。

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

CMYK是一种依靠反光的色彩方式,咱们是怎样阅览报纸的内容呢?是由阳光或灯光照射到报纸上,再反射到咱们的眼中,才看到内容。它需求有外界光源,假如你在黑暗房间内是无法阅览报纸的。只要在屏幕上显现的图画,便是RGB方式表现的。只要是在印刷品上看到的图画,便是CMYK方式表现的。大多数在纸上沉积五颜六色颜料的设备,如五颜六色打印机和复印机,要求输入CMY数据,在内部进行RGB到CMY的转换。

青色Cyan、品赤色Magenta、黄色Yellow是光的二次色,是颜料的色彩。而K取的是black最终一个字母,之所以不取首字母,是为了避免与蓝色(Blue)稠浊。当红绿蓝三原色被混合时,会发生白色,当混合青色、品赤色、黄色三原色时会发生黑色。从理论上来说,只需求CMY三种油墨就足够了,可是由于现在制作工艺还不能造出高纯度的油墨,CMY相加的成果实践是一种暗赤色。

RGB 图画灰度化

RGB转换为灰度图有如下几种办法:

  1. 重量法:任选一通道作为 gray
gray = R or gray=G or gray=B
  1. 最大值法:RGB 中最大值作为 gray
gray=max(R,G,B)
  1. 均匀值法:RGB 中均匀值作为 gray
gray=mean(R,G,B)
  1. 加权均匀值法:RGB 中加权均匀值作为 gray, 由于人眼对红绿蓝三色的灵敏程度不同,所以核算灰度的时分要加权均匀。这个系数主要是依据人眼对R,G,B三原色的灵敏性不同而导出的系数.
gray = 0.299 * R + 0.578 * G + 0.114 * B
//该权重是RGB转YUV的BT709亮堂度转换公式,是依据人眼感知的图画灰度处理公式
gray = 0.2125 * R + 0.7154 * G + 0.0721 * B
static const GLchar * const kGrayscaleFragmentShader = STRINGIZE
(
 precision highp float;
 varying highp vec2 v_texcoord;
 uniform sampler2D texture;
 const highp vec3 W = vec3(0.2125, 0.7154, 0.0721);
 void main()
 {
     lowp vec4 textureColor = texture2D(texture, v_texcoord);
     float luminance = dot(textureColor.rgb, W);
     gl_FragColor = vec4(vec3(luminance), textureColor.a);
 }
 );

色相

Hue即色相,便是咱们平时所说的红、绿,蓝。假如你分的更细的话可能还会有洋红、草绿等等;在HSV模型中,用度数来描绘色相,其间赤色对应0度,绿色对应120度,蓝色对应240度。

怎么调整色相:通过将 RGB 空间转换到 YIQ/YCbCr 空间,调整其间的 chroma(I&Q, or Cb&Cr) 重量。

  1. 将 RGB 转换到 YIQ
  2. 核算色相值,单位弧度: hue = atan(Q, I);
  3. 核算长边 chroma = sqart(I * I + Q * Q);
  4. 旋转色相,调整 hue 弧度值
  5. 旋转后的 hue 弧度值,利用正玄/余玄,核算 I/Q; Q = sin(hue) * chroma, I = cos(hue) * chroma
  6. 将 YIQ 转换到 RGB
static const GLchar *const kHueFragmentShader = STRINGIZE
(
 precision highp float;
 varying highp vec2 v_texcoord;
 uniform sampler2D texture;
 uniform mediump float hueAdjust;
 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);
 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 ()
 {
     // Sample the input pixel
     highp vec4 color = texture2D(texture, v_texcoord);
     // Convert to YIQ
     highp float YPrime  = dot(color, kRGBToYPrime);
     highp float I      = dot(color, kRGBToI);
     highp float Q      = dot(color, kRGBToQ);
     // Calculate the hue and chroma
     highp float hue     = atan(Q, I);
     highp float chroma  = sqrt(I * I + Q * Q);
     // Make the user's adjustments
     hue += (-hueAdjust); //why negative rotation?
     // Convert back to YIQ
     Q = chroma * sin(hue);
     I = chroma * cos(hue);
     // Convert back to RGB
     highp vec4 yIQ = vec4(YPrime, I, Q, 0.0);
     color.r = dot(yIQ, kYIQToR);
     color.g = dot(yIQ, kYIQToG);
     color.b = dot(yIQ, kYIQToB);
     // Save the result
     gl_FragColor = color;
 }
 );

亮度

亮度表明色彩的亮堂程度,明度越高,色彩越亮。

关于灰度图画来说,每个像素点只要1个重量,且在0~255之间,0表明黑色,最暗,1表明白色,最亮。

对五颜六色图画来说,每个像素点有3个重量(RGB),每个重量的值在0~255之间,RGB的各个值越小,亮度越小;RGB的各个值越大,亮度越大,所以想要操控亮度,咱们只要操控RGB值巨细即可

怎么调整亮度:通过添加和削减像素重量的值,亮度调整参数 brightness 取值规模 -1.0 to 1.0,

static const GLchar *const kBrightnessFragmentShader = STRINGIZE
( 
 precision highp float;
 varying highp vec2 v_texcoord;
 uniform sampler2D texture;
 uniform float brightness;
 void main()
 {
     vec4 textureColor = texture2D(texture, v_texcoord);
     gl_FragColor = vec4((textureColor.rgb + vec3(brightness)), textureColor.w);
 }
 );

饱满度

图画的饱满度是指色彩的艳丽程度,也称色彩的纯度。饱满度取决于该色中含色成分和消色成分(灰色)的份额。含色成分越大,饱满度越大;消色成分越大,饱满度越小。纯的色彩都是高度饱满的,如鲜红,鲜绿。稠浊上白色,灰色或其他色彩的色彩,是不饱满的色彩,如绛紫,粉红,黄褐等。彻底不饱满的色彩根本没有色彩,如是非之间的各种灰色

往一种色彩中添加大量的是非灰色,当该色彩中的是非灰色的量远大于原色彩的量时,能够认为该色彩的灰度值便是该色彩饱满度为0时的RGB值。因而,当一个画面饱满度为0时,得到的应该是该画面对应的灰度图。

怎么调整饱满度:饱满度调整参数 saturation 取值规模 0.0 (fully desaturated) to 2.0 (max saturation), with 1.0 as the normal level

  1. 核算灰度值:0.2125 * R + 0.7154 * G + 0.0721 * B
  2. 运用 mix 或许 lerp 函数,将原色与灰度值按份额混合
`mix`是一个特别线性插值函数,两个参数值依据第三个参数插值`genType mix(genType x,genType y,float a)`,即`(x*(1-a)+y*a)`。简单了解便是`a`的值决议了`x``y`的强弱联系。`a`取值规模在`[0,1]`之间,`a`值越大,成果值中`y`占比会越大;`a`值越小,成果值中`y`占比会越小;
`lerp`函数的用法:`lerp(x, y, a): 用于插值,公式界说 x*(1-a) + y*a .af为百分数(取值规模[0,1])
static const GLchar *const kSaturationFragmentShader = STRINGIZE
(
 varying highp vec2 v_texcoord;
 uniform sampler2D texture;
 uniform lowp float saturation;
 // Values from "Graphics Shaders: Theory and Practice" by Bailey and Cunningham
 // 该权重是RGB转YUV的BT709亮堂度转换公式,是依据人眼感知的图画灰度处理公式
 const mediump vec3 luminanceWeighting = vec3(0.2125, 0.7154, 0.0721);
 void main()
 {
     lowp vec4 textureColor = texture2D(texture, v_texcoord);
     lowp float luminance = dot(textureColor.rgb, luminanceWeighting);
     lowp vec3 grayScaleColor = vec3(luminance);
     // mix(x, y, a) = x * (1 - a) + y * a
     gl_FragColor = vec4(mix(grayScaleColor, textureColor.rgb, saturation), textureColor.w);
 }
 );

对比度

图画的对比度是指图画中明暗区域最亮的白和最暗的黑之间不同亮度层级的丈量,即图画灰度反差的巨细,浅显一点来说便是最大亮度与最小亮度之比,对比度越大,图画越清晰,色彩也越艳丽;对比度越小,画面越显现的灰蒙蒙。

怎么调整对比度:纹路RGB减去一半色值后和contrast值相乘,最终再加上被减去的一半色,contrast的调节规模在[0,2]之间。当contrast为1.0时是原图对比度,contrast越大,画面最亮和最暗像素之间差值越大;contrast越小,画面最亮和最暗像素之间差值越小;

static const GLchar * const kContrastFragmentShader = STRINGIZE
(
precision highp float;
varying highp vec2 v_texcoord;
uniform sampler2D texture;
uniform float contrast;
void main()
{
  vec4 textureColor = texture2D(texture, v_texcoord);
  gl_FragColor = vec4(((textureColor.rgb - vec3(0.5)) * contrast + vec3(0.5)), textureColor.w);
}
);

曝光度

曝光度与亮度相似,调整 RGB 值巨细,越大曝光度越高。但数值改变方式不一样,亮度是全方位的线性添加色值,而曝光度是依据原色值的指数型叠加(红的会更红,绿的会更绿,蓝的会更蓝,白光的会更光)

static const GLchar *const kExposureFragmentShader = STRINGIZE
(
 varying highp vec2 v_texcoord;
 uniform sampler2D texture;
 uniform highp float exposure;
 void main()
 {
     highp vec4 textureColor = texture2D(texture, v_texcoord);
     gl_FragColor = vec4(textureColor.rgb * pow(2.0, exposure), textureColor.w);
 }
 );

色阶

色阶表明图画亮度强弱的指数规范,也便是咱们说的色彩指数,在数字图画处理教程中,指的是灰度分辨率(又称为灰度级分辨率或许起伏分辨率.)。图画的色彩丰满度和精密度是由色阶决议的。色阶是指亮度,和色彩无关,但最亮的只要白色,最暗的只要黑色。

伽马校对

下面给出物理线性亮度改变和人类察觉的线性亮度改变:

图画基本特点调整

上一行的人类感知线性亮度更像是咱们眼睛看到的亮度改变。然而当咱们考虑物理亮度数值(光子层面)时,下一行反而是正确的亮度改变。所以实在的物理亮度和咱们看见的视觉亮度是不一样的。

物理上的亮度是依据单位面积光子的数量界说的,1-100的物理亮度便是严厉的线性递增;而人眼并不是这样,人眼通过“对比”来感触亮度的改变,0-1的改变能被人很明显地感触到,可是100-101的改变就不行了,所以说人眼感知的亮度和物理亮度必定是不对等的(依据韦伯定律)

假如咱们直接将图片RGB以物理亮度保存,那么大约物理亮度的0.2就代表了心理上0.5的亮度,这样一来暗部只能占0-0.2这一小规模,假如图片依照每通道8bit方式存储,那么暗部只能占到大约50个阶,这样会导致偏暗的图画丢失很多细节。为了有用利用有限的空间,存储心理上的亮度值是更好的做法,这样能让亮部暗部各占一半;

物理亮度和视觉亮度的联系大约能够拟组成幂函数联系,咱们将幂记为编码Gamma。幂函数的幂一般在1.8-2.5之间,

为了有用利用空间,咱们存储心理上的亮度值,可是显现器可不是人,它只知道严厉的物理量,所以咱们将图片交由显现器显现时,需求再次将亮度值转换为物理亮度,转换用的幂值记为解码Gamma

最早的CRT显现器的输入电压和显现亮度的联系也不是线性的,他们的联系大约像幂函数,幂大约在2.0-3.0之间。

能够发现,两个函数曲线是近似对称的,那就能够约定一个相同的幂值来统合编码和解码Gamma,这个值便是常见的2.2,存储编码时运用1/2.2作为幂,然后直接输入给显现器(显现器内部以2.2为幂解码),就能够到达比较理想的作用。虽然现代液晶显现器现已没有了前期CRT显现器的问题,可是为了兼容,一般也会设有2.2左右的默许Gamma值。

图画基本特点调整

伽马校对:咱们日常中运用的大部分图片(比方sRGB规范)都是通过Gamma编码的,存储的是通过非线性映射的RGB值,那么就存在一个问题,假如咱们将这种图片用作纹路贴图,然后需求对他们进行一些比方光照核算、含糊等处理的时分,假如咱们直接用存储的RGB值来核算,就会出现过错的作用,由于做这些核算时咱们依据的是实在物理量,可是存在图片中的RGB并不是

所以咱们需求先将图片中的通过编码后的RGB值先解码实在物理值,然后依据需求对它们做核算处理,全部完成今后再将成果数据重新编码。

static const GLchar *const kGammaFragmentShader = STRINGIZE
(
 precision highp float;
 varying highp vec2 v_texcoord;
 uniform sampler2D texture;
 uniform lowp float gamma;
 void main()
 {
    lowp vec4 textureColor = texture2D(texture, v_texcoord);
    gl_FragColor = vec4(pow(textureColor.rgb, vec3(1/2.2)), textureColor.w);
}
 );

反色

关于图片反色的界说仍是很容易了解的,咱们现已知道在GL中色彩是用r,g,b,a表明的,r,g,b,a 的规模是0.0f~1.0f,若染色color = vec4(r,g,b,a),则反色的色彩

所以反色的fragment shader 自然而然便是下面了

static const GLchar *const kColorInvertFragmentShader = STRINGIZE
(
 precision highp float;
 varying highp vec2 v_texcoord;
 uniform sampler2D texture;
 void main()
 {
     vec4 textureColor = texture2D(texture, v_texcoord);
     gl_FragColor = vec4((1.0 - textureColor.rgb), textureColor.w);
 }
 );

色彩矩阵

可通过用 44 矩阵乘以这些色彩矢量将线性改换(旋转和缩放等)使用到色彩矢量中。可是,您不能运用 44 矩阵进行平移(非线性)。假如在每个色彩矢量中再添加一个虚拟的第 5 坐标(例如,数字 1),则可运用 55 矩阵使用任何组合方式的线性改换和平移。由线性改换组成的后跟平移的改换称为仿射改换。

ColorMatrix是怎样完成色彩的缩放、旋转、剪切及平移的?靠这些功能能完成图画的哪些作用?或许说,某种作用能用ColorMatrix完成吗?

ColorMatrix 矩阵

R G B A V
m11 m12 m13 m14 m15
m21 m22 m23 m24 m25
m31 m32 m33 m34 m35
m41 m42 m43 m44 m45
m51 m52 m53 m54 m55

ColorMatrix 单位矩阵

R G B A V
1.0 0.0 0.0 0.0 0.0
0.0 1.0 0.0 0.0 0.0
0.0 0.0 1.0 0.0 0.0
0.0 0.0 0.0 1.0 0.0
0.0 0.0 0.0 0.0 1.0

关于色彩的每个重量R、G、B、A来说,运用ColorMatrix后所得到的实践值r、g、b、a,用公式表明为:

r = R * m11 + G * m21 + B * m31 + A * m41 + m51 * 255;
g = R * m12 + G * m22 + B * m32 + A * m42 + m52 * 255;
b = R * m13 + G * m23 + B * m33 + A * m43 + m53 * 255;
a = R * m14 + G * m24 + B * m34 + A * m44 + m54 * 255;
static const GLchar *const kColorMatrixFragmentShader = STRINGIZE
(
 precision highp float;
 varying highp vec2 v_texcoord;
 uniform sampler2D texture;
 uniform lowp mat4 colorMatrix;
 uniform lowp float intensity;
 void main()
 {
    lowp vec4 textureColor = texture2D(texture, v_texcoord);
    lowp vec4 outputColor = textureColor * colorMatrix;
    gl_FragColor = outputColor;
}
 );

色彩缩放

色彩缩放:色彩缩放很简单,便是依照给定的份额值,在图画像素现有A、R、G、B各重量数值基础上核算出新的重量值。这个份额值便是ColorMatrix主对角线除m55外的其它4个值。比方某像素的RGBA值现在别离为255、128、64和255,而主对角线m11 – m44的值别离为0.8、0.5、-1及0.5,那么该像素新的rgba值应该是

r = R * m11 = 255 * 0.8 = 204;
g = G * m22 = 128 * 0.5 = 64;
b = B * m33 = 64 * -1 = 192; // 64 * -1应该等于-64。没错,64用32位数表明为0xFFFFFFC0,无符号字节饱满取整,取最终8位0xC0,等于192
a = A * m44 = 255 * 0.5 = 128;

色彩剪切

一般说来,图画像素R、G、B各重量依照与另一种色彩重量成份额的量来添加或削减色彩重量便是剪切。其实这种表述并不彻底,像素的A重量也是参加其间的

以赤色重量R举例,假如要按绿色重量G进行剪切,那么m21便是剪切份额值,m21 * G就得到了G对R的剪切量。同理,m31 * B、m41 * A可别离得到B和A对R的剪切量,将这些剪切量加起来,便是R总的剪切量。用公式表明为:

r = G * m21 + B * m31 + A * m41;
g = R * m12 + B * m32 + A * m42;
b = R * m13 + G * m23 + A * m43;
a = R * m14 + G * m24 + B * m34;

色彩旋转

色彩旋转的描绘比较杂乱,便是在图画像素中,用其间的2个重量依照一定的角度环绕另外1个重量作运算的成果,便是色彩的旋转。以赤色重量R和绿色重量G环绕蓝色重量G旋转60度为例:

m11 = cos(60) = 0.5, m12 = sin(60) = 0.866, m21 = -sin(60) = -0.866, m22 = cos(60) = 0.5,那么,R和G 所得到的旋转量别离为:

r = R * m11(0.5) + G * m21(-0.866);
g = R * m12(0.866) + G * m22(0.5);

色彩平移

上面的缩放、剪切和旋转归于色彩的线性改换(都是乘法运算的累积和),而平移是色彩的非线性改换,便是对色彩各重量做一个加法而已:图画像素各重量的平移量用所谓的虚拟位,即第5行的各个值来表明,各重量加上地点列的虚拟行的值便是色彩平移,其实质便是非线性地调整了该重量的亮度值。用公式表明各重量的平移量:

r = R + m51 * 255;
g = G + m52 * 255;
b = B + m53 * 255;
a = A + m54 * 255;

白平衡

直方图

写在最终

岗位职责

  1. 担任剪映视频编排方向的研制、优化与架构规划作业;
  2. 担任剪映东西方向的研制,包含视频编排、导出、资料办理等模块,处理杂乱编排操作及架构上所面临的严峻应战,打造极致的东西体验;
  3. 担任项目要点、难点的技能攻坚任务,持续优化产品,提升产品质量;
  4. 共同建设抖音&西瓜的视频内容创作生态,在职业之间树立技能壁垒。

岗位要求

  1. 具有扎实的 Objective-C 或许 Swift 语言基础,了解常用的数据结构和算法;
  2. 了解 iOS 体系运行机制及内核,了解移动终端特性和处理方案;
  3. 优异的编码习惯,关于规划方式等常见的编码技巧有很好的认知;
  4. 有较强的技能好奇心、自驱力,具有优异的处理问题和逻辑思维能力;
  5. 有多媒体相关开发经历者优先,不要求有视频修改开发经历。 |

简历投递链接: job.toutiao.com/s/idEgpQr7