19、OpenGL ES视频滤镜:Lookup Table(LUT)滤镜、卷积滤镜(模糊/锐化)、美颜滤镜基础
视频滤镜,说白了就是给每一帧画面“化妆”。
我在做视频SDK的时候,发现很多同学一上来就怼美颜算法,结果连最基础的LUT和卷积都没搞明白。嗯,今天我们就从这三个核心方向入手,把视频滤镜的底子打扎实。
19.1 Lookup Table(LUT)滤镜:颜色映射的“作弊器”
LUT滤镜,本质上是一个颜色映射表。你输入一个颜色值,它输出另一个颜色值。整个过程就是一个查表操作,速度极快。
核心原理:
对于RGBA每个通道(通常是0-255),LUT表预先存储了映射后的结果。渲染时,像素的原始颜色作为索引,直接取出目标颜色。
我个人习惯把LUT分为两类:
- 1D LUT:每个通道独立映射。简单,但无法处理通道间的交叉影响。
- 3D LUT:将RGB三个通道组合成一个三维坐标,查表得到新的RGB。效果更丰富,电影级调色基本都用它。
在OpenGL ES中实现LUT滤镜,核心就是一张纹理。我们把LUT数据(通常是64x64或16x16的格子图)加载到纹理中,然后在片段着色器里用原始颜色去采样这张纹理。
我曾经在项目里踩过一个坑:LUT纹理的采样精度。如果你用GL_NEAREST,颜色过渡会有明显的断层。后来我改成了GL_LINEAR,效果就平滑多了。
避坑指南:
我曾经在低端机上用3D LUT,发现帧率掉得厉害。后来排查发现是纹理采样次数太多。优化方案:把3D LUT预计算成2D纹理,或者降低LUT的分辨率(比如从64x64降到32x32),肉眼几乎看不出区别。
19.2 卷积滤镜:模糊与锐化的数学魔法
卷积滤镜,说白了就是用一个“小窗口”在图像上滑动,每个像素的新值由它周围像素加权平均得到。这个“小窗口”就是卷积核(Kernel)。
为什么卷积能实现模糊和锐化?你想想看:
- 模糊:卷积核里所有权重都是正数,且加起来等于1。相当于把周围像素“拉”过来平均,边缘自然就模糊了。
- 锐化:卷积核中心权重很大,周围权重为负。这样能突出像素与周围的差异,边缘就变得更锐利。
在OpenGL ES里实现卷积,我一般用texelFetch或者textureOffset来采样周围像素。但要注意,纹理坐标的偏移量需要根据纹理尺寸计算,否则会采样到错误的位置。
下面是一个3x3高斯模糊的片段着色器核心代码:
// 高斯模糊 3x3 卷积核
const float kernel[9] = float[](
1.0/16, 2.0/16, 1.0/16,
2.0/16, 4.0/16, 2.0/16,
1.0/16, 2.0/16, 1.0/16
);
void main() {
vec2 texOffset = 1.0 / uTextureSize; // 根据纹理尺寸计算偏移
vec3 color = vec3(0.0);
for(int i = 0; i < 3; i++) {
for(int j = 0; j < 3; j++) {
vec2 coord = vTexCoord + vec2(i-1, j-1) * texOffset;
color += texture2D(uTexture, coord).rgb * kernel[i*3+j];
}
}
gl_FragColor = vec4(color, 1.0);
}
注意:
卷积核越大,性能开销越大。5x5的卷积需要采样25次,7x7就是49次。我在项目中通常把大卷积核拆成两个1D卷积(水平和垂直),这样采样次数从N²降到2N,性能提升非常明显。
19.3 美颜滤镜基础:从磨皮到美白
美颜滤镜,说白了就是模糊+颜色调整的组合拳。但直接全图模糊会丢失细节,所以需要“保边滤波”。
我刚开始做美颜时,直接上了高斯模糊,结果人脸像塑料一样。后来才明白,真正的美颜需要做三件事:
- 磨皮(皮肤平滑):用保边滤波(如双边滤波、引导滤波)只平滑皮肤区域,保留眼睛、眉毛等细节。
- 美白(肤色调整):通过颜色映射或曲线调整,让皮肤更白皙。
- 祛痘/去瑕疵:用中值滤波或更高级的修复算法。
在OpenGL ES里实现基础美颜,我一般用双边滤波。它和高斯模糊的区别在于:不仅考虑空间距离,还考虑像素值的差异。这样在皮肤区域平滑,在边缘处停止。
下面是一个简化的双边滤波片段着色器:
uniform sampler2D uTexture;
uniform vec2 uTexOffset;
uniform float uSigmaSpatial; // 空间标准差
uniform float uSigmaRange; // 值域标准差
void main() {
vec3 center = texture2D(uTexture, vTexCoord).rgb;
vec3 sum = vec3(0.0);
float totalWeight = 0.0;
for(int i = -2; i <= 2; i++) {
for(int j = -2; j <= 2; j++) {
vec2 coord = vTexCoord + vec2(i, j) * uTexOffset;
vec3 sample = texture2D(uTexture, coord).rgb;
float spatialDist = float(i*i + j*j);
float rangeDist = length(sample - center);
float weight = exp(-spatialDist / (2.0 * uSigmaSpatial * uSigmaSpatial))
* exp(-rangeDist / (2.0 * uSigmaRange * uSigmaRange));
sum += sample * weight;
totalWeight += weight;
}
}
gl_FragColor = vec4(sum / totalWeight, 1.0);
}
个人经验:
双边滤波在GPU上性能开销较大。我后来改用“快速双边滤波”方案:先降采样,再滤波,最后上采样。这样在1080p视频上也能跑到60fps。另外,美颜强度不要拉满,否则会失去真实感。我一般把磨皮强度控制在0.3-0.6之间。
19.4 本章知识体系
下面这张图总结了三种滤镜的核心逻辑和关系:
从图中可以看出,三种滤镜虽然应用场景不同,但底层都离不开纹理采样和像素运算。LUT是查表映射,卷积是加权平均,美颜是保边滤波+颜色调整。理解了这些,你就能组合出更复杂的滤镜效果。
好了,这一章的内容就到这里。LUT、卷积、美颜这三个方向,每一个都值得深入。下一章我们会继续深入OpenGL ES的视频渲染实战,敬请期待。
公众号:蓝海资料掘金营,微信deep3321