给世界上色——滤镜底层原理

前言

滤镜最早应用在电视影视业,对剧和电影作品后期进行调色效果。如今拍照、修图都离不开滤镜效果。我们在微博、朋友圈、电视、影院里看到的照片视频,无一没有滤镜的效果,滤镜已经深入我们生活的方方面面。

这里浅略地揭秘一下当前图像处理中滤镜底层的原理。
在这里插入图片描述
在这里插入图片描述

RGB颜色

RGB色彩模式是工业界的一种颜色标准,是通过对红®、绿(G)、蓝(B)三个颜色通道的变化以及它们相互之间的叠加来得到各式各样的颜色的,RGB即是代表红、绿、蓝三个通道的颜色,这个标准几乎包括了人类视力所能感知的所有颜色,是运用最广的颜色系统之一。
在这里插入图片描述
对于一张图片的每一个像素,都可以由唯一的颜色(R,G,B)表示,其中RGB的范围是0~255。0代表最暗,255代表最亮。
在这里插入图片描述

颜色查找表

滤镜的本质就是颜色的变换。我们可以把一个像素的颜色(R,G,B)看成是一个三维空间上的一个坐标点。颜色变换相当于是三维空间的 [一个坐标点][另一个坐标点] 的映射关系。也就是:

    (old R,old G,old B)  --->   (new R,new G,new B)

例如:

    (23,77,134)  --->      (122,34,255)

    (189,65,138)  --->      (5,65,21)

    (0,0,2)  --->      (33,0,1)

            ………………

在这里插入图片描述

即每一个(R,G,B)都有一个一一对应的目标颜色,那么一个完整的RGB颜色查找表总共有 256×256×256 = 2^24 条。

显然 2^24 这个数字有点太大了,存储它需要至少16Mb的空间,加载到内存也至少需要吃掉16Mb的内存
(问题1:这里可以先思考一下为什么是16Mb?后面会给出解释)

因此我们在实际查找表应用中一般使用 64×64×64 的查找表,RGB每个通道都将 [连续的4个坐标] 映射成 [1个目标坐标]

例如:

    (0,X,X)    --->   (34,X,X)

    (1,X,X)    --->   (34,X,X)

    (2,X,X)    --->   (34,X,X)

    (3,X,X)    --->   (34,X,X)



    (4,X,X)    --->   (128,X,X)

    (5,X,X)    --->   (128,X,X)

    (6,X,X)    --->   (128,X,X)

    (7,X,X)    --->   (128,X,X)

但显然这样会导致原始图片颜色精度的丢失(例如上面的0~3的亮度映射后都变成了无差别的34),那么就需要想办法降低这种精度丢失的问题
(问题2:这里可以先思考一下有哪些补偿精度的方法?后面会给出解答)

LUT图

有了颜色映射表,接下来需要考虑如何去表达这些映射关系了。有一个笨办法是用文本去存储这 64×64×64 这么多条 (old R,old G,old B) —> (new R,new G,new B)这样的映射关系。聪明的你肯定意识到了这个文本的大小会是一个天文数字。那么有没有更好的表达方法呢?优秀的前辈们发现可以用一个图片去表示这个映射关系,这就是LUT图:
在这里插入图片描述
这个LUT图的尺寸是 512×512 ,正好等于 64×64×64(查找表的数量),也就是刚好每一个像素可以表示一条 (old R,old G,old B) —> (new R,new G,new B)这样的映射关系!

我们可以用 [图片的像素坐标] 表示(old R,old G,old B),用这个坐标所在的 [像素的颜色] 表示(new R,new G,new B),这样就实现了映射表的一一对应关系。

我们可以先从比较简单的开始看,为什么用这个坐标所在的 [像素的颜色] 表示(new R,new G,new B)?因为图片本身每个像素就是由(R,G,B)组成,并且都是0~255,刚好可以完美表示一个(new R,new G,new B)。

再来看看比较复杂一丁点的,如何用 [图片的像素坐标] 表示(old R,old G,old B)?因为一个图片的坐标是二维坐标(x,y),要如何表示一个三维的(old R,old G,old B)呢?

可以看到这个图片每一行有8个小正方形,每一列也有8个小正方形,总共有64个小正方形。每个小正方形都是一张 64×64的图片。

我们先从一个小正方形开始看。其实每一个小正方形都代表了完整的(old R,old G) —> (new R,new G)的映射。其中像素Target坐标(x,y)代表(old R,old G),像素Target的颜色的RG值就代表(new R,new G)。

例如下面这张图片:

1.假设(old R,old G) = (100,150),那么Target的坐标就是(100 / 4,150 / 4)= (25,37)

2.如果这个图片里坐标是(25,37)的Target的像素值是(213, 88),那么(new R,new G)=(213, 88)

2.即完成了(old R,old G) = (100,150) —> (new R,new G)=(213, 88) 的映射关系!
在这里插入图片描述
至此已经完成了R通道和G通道的映射,那么如何确定B通道的映射关系呢?前面说到一个完整LUT图有8×8=64个小正方形,这64个小正方形就是用来确定B通道的映射关系的。

我们把这64个小正方形按从左到右从上到下排列,编号0~63,我们就可以把(old B)映射到其中的某个小正方形格子。

拿下面这个示例图比较能说明过程:

假如(old B) = (50),那么最终的颜色落在第(50 / 4)=(12)个格子上。

但注意,这里的(12)并不是(new B),(12)仍然只是图片的 [坐标],因此它代表的其实还是(old B),仅仅 / 4 了而已。

我们回到上面一步的步骤,确定了在是哪个小正方形,就可以在这个小正方形里根据(old R,old G)确定最终的Target。那么(new B)就等于 像素Target颜色的B值!
在这里插入图片描述

聪明的你也许已经意识到了,在这64个小正方形里,每个小正方形相同(x,y)坐标所对应Target像素颜色的(R,G)都是一样的,仅仅只有B不一样。这也就是为什么B颜色最终是根据计算 [在哪个小正方形里] 来确定的。

我们再来完整走一遍LUT图颜色映射的全过程。

1.假如一个像素点原始的颜色是 (old R,old G,old B)=(38,201,88)

2.根据(old B)确定在哪个小正方形:88 / 4 = 22

3.在第22个小正方形里,根据(old R,old G)确定最终Target的坐标:(38 / 4,201 / 4)=(9,50)

4.假如第22个小正方形中,(9,50)所对应的Target像素的颜色是(50,3,126)

5.那么最终的颜色(new R,new G,new B)=(50,3,126),至此完成一个像素点颜色的映射。

6.遍历原始图片的每一个像素,都走一遍1~5的过程,完成对整张图片的滤镜效果。

这里先解答一下上面 [问题1] 和 [问题2] 的解答:

问题1:为什么是16Mb?

因为映射关系都用LUT图表示,每个像素代表一条映射,那么64×64×64 = 2^18,一张 2^18 个像素的无损图片(一般是.png)大小至少是256Kb,而 256×256×256 = 2^24 个像素的无损图片大小至少是16Mb。

问题2:有哪些补偿精度的方法?

注意到上面精度的丢失是因为像素颜色从 256 --> 64,我们上面在做除法的时候丢失了小数点,例如(38 / 4,201 / 4)=(9,50),但其实应该是(38 / 4,201 / 4)=(9.5,50.25),在实际运用的时候我们并不会抛弃小数点。

在计算的时候,如果计算得到的坐标不是位于一个像素的正中心,那么在取色时,会对相邻的几个像素进行加权平均,更靠近的像素权重越大。直观地说就是,理谁越靠近,那么谁就对最终的颜色有更重要的影响。

例如下面这个图,在最终确定颜色时,会考虑相邻的4个像素点的颜色。这就是双线性插值法,除此之外也有单线性插值法,有兴趣的朋友欢迎交流。
在这里插入图片描述
双线性插值法示意图:
在这里插入图片描述

如何制作一个LUT图?

1.打开Photoshop,打开一个你想调色的图片

在这里插入图片描述

2.通过各种调节,达到你所期待的颜色

在这里插入图片描述

3.载入一张原始LUT图

在这里插入图片描述
什么是原始LUT图:就是经过这个LUT颜色变化之后,还是原来的颜色,也就是 [什么颜色都不变]

它的映射关系:

    (0,0,0) ---> (0,0,0)

    (0,0,1) ---> (0,0,1)

            ………………

    (254,255,255) ---> (254,255,255)

    (255,255,255) ---> (255,255,255)

4.对这张LUT图也应用上对刚才图片的调色效果

在这里插入图片描述
至此一张LUT滤镜图就做好了:
在这里插入图片描述

怎么理解这个过程?

我的理解是,我们对图片进行 [调色的一系列操作],再 [作用在原始LUT图上],就相当于让这张原始LUT图记录下了 [这一系列操作],记下来之后就可以拿去对任意的图片进行滤镜效果了。

最后附一个效果图

请添加图片描述

OpenGL是一个用于渲染2D和3D图形的行业标准应用程序编程接口。要在OpenGL中给圆上色,首先你需要准备以下几个步骤: 1. **设置着色器**:OpenGL使用着色器语言(如GLSL)来编写程序,控制图形的颜色和光照。创建顶点着色器(Vertex Shader)来处理几何变换,片段着色器(Fragment Shader)来指定颜色。 2. **创建顶点数组对象(VAO)和缓冲区对象(VBO)**:VAO用来管理顶点数据,VBO存储顶点位置、纹理坐标和法线等信息。对于圆形,需要提供圆心位置和半径。 3. **绘制圆形**:使用`glBegin(GL_TRIANGLE_FAN)`开始一个三角扇形,并传入顶点数。顶点从圆心开始,向外延伸形成扇形,然后每次增加的角度对应于一个三角形。 4. **着色**:在片段着色器里,通过`gl_FragColor`来设置每个像素的颜色。你可以使用循环遍历扇形的每个片段,根据需要应用不同的颜色,比如使用一个渐变色或者固定的色块。 5. **绑定并启用**:在绘制之前,记得将VAO、VBO、着色器关联到当前上下文,并启用它们。 下面是一个简化的伪代码示例: ```glsl // Fragment shader (in shaders/fragment.glsl) uniform vec4 circleColor; void main() { gl_FragColor = circleColor; } // In your C++ code ... glUseProgram(shaderProgram); glUniform4fv(circleColorLocation, 1, colorArray); // colorArray is an array of desired colors // Draw the circle glEnableVertexAttribArray(vertexPositionAttribute); glVertexAttribPointer(vertexPositionAttribute, 2, GL_FLOAT, GL_FALSE, 0, vertexData); // vertexData is the VBO for positions glDrawArrays(GL_TRIANGLE_FAN, 0, numVertices); // numVertices is the number of vertices in the fan ```
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值