OpenGL YUV 和 RGB 图像转换出现偏色问题怎么解决?

2023-11-17 16:35:36 浏览数 (2)

早上知识星球里的一位同学,遇到 yuv2rgb 偏色问题,这个问题比较典型,今天展开说一下。

省流版

直接贴出来更精确的转换公式:

代码语言:javascript复制
vec3 rgb2yuv(vec3 rgb) {
    float y =  0.257 * rgb.r   0.504 * rgb.g   0.098 * rgb.b;
    float u = -0.148 * rgb.r - 0.291 * rgb.g   0.439 * rgb.b;
    float v =  0.439 * rgb.r - 0.368 * rgb.g - 0.071 * rgb.b;
    return vec3(y,u,v);
}


float y = texture2D(texture0, uv).r - 0.063;
float v = texture2D(texture1, uv).r - 0.502;
float u = texture2D(texture2, uv).r - 0.502;

vec3 yuv = vec3(y,u,v);

vec3 yuv2rgb(vec3 yuv) {
    float r = 1.164 * yuv.x   1.596 * yuv.z;
    float g = 1.164 * yuv.x - 0.392 * yuv.y - 0.813 * yuv.z;
    float b = 1.164 * yuv.x   2.017 * yuv.y;
    return vec3(r,g,b);
}

刨根问底版

理论上,rgb2yuv 和 yuv2rgb 的转换是可逆的,也就是说,它们可以完美地还原图像,不会引入信息损失,类似于纯粹的数学运算 1 2=3,3-2=1 。

但是在实际情况中,由于计算机表示的精度有限、采样误差以及浮点运算的限制,转换过程中会导致信息损失。但是这个误差要是控制在肉眼无法辨别的范围还是很容易的。

基于上面分析,偏色的根本原因其实就是转换时的精度误差,解决办法就是提高精度(小数点后多精确几位),让误差在人眼无法分辨的范围。

下面来做个试验,利用上面的公式,我们对一张图片反复做多次 rgb2yuv 和 yuv2rgb 转换,然后看下最终图像颜色的变化。

测试代码:

代码语言:javascript复制
#iChannel0 "https://img-baofun.zhhainiao.com/pcwallpaper_ugc_mobile/static/2ddf8479959f1f3d9f52d0d561d281fe.jpg"

vec3 rgb2yuv(vec3 rgb) {
    float y =  0.257 * rgb.r   0.504 * rgb.g   0.098 * rgb.b;
    float u = -0.148 * rgb.r - 0.291 * rgb.g   0.439 * rgb.b;
    float v =  0.439 * rgb.r - 0.368 * rgb.g - 0.071 * rgb.b;
    return vec3(y,u,v);
}

vec3 yuv2rgb(vec3 yuv) {
    float r = 1.164 * yuv.x   1.596 * yuv.z;
    float g = 1.164 * yuv.x - 0.392 * yuv.y - 0.813 * yuv.z;
    float b = 1.164 * yuv.x   2.017 * yuv.y;
    return vec3(r,g,b);
}

void mainImage(out vec4 fragColor, in vec2 fragCoord)
{
    vec2 uv = fragCoord / iResolution.xy;

    float N = 4000.0;

    vec4 col = texture2D(iChannel0, uv);;

    if(uv.x > 0.5) {
        while(N > 0.0) {
            N--;
            vec3 yuv = rgb2yuv(col.rgb);
            col.rgb = yuv2rgb(yuv);
        }
    }

    fragColor = col;
}

N=10,只做 10 次 yuv 和 rgb 的来回转换,效果如下,这时肉眼已经无法区分颜色的误差。

N=4000,做 4000 次 yuv 和 rgb 的来回转换放大误差,效果如下,这时由于误差不断累计,出现了明显的偏色。不过,转换 4000 次这种操作在实际情况下不太可能出现。

另外,除了小数点后多精确几位,shader 里面的 float 也要声明为高精度:

代码语言:javascript复制
precision highp float;

OpenGL ES 3.x GL_EXT_YUV_target 扩展,也提供了内置的颜色空间转换函数(推荐使用),精度更高,可以选择不同的转换标准,如:

代码语言:javascript复制
    yuvCscStandardEXT conv_standard = itu_601;
    yuvCscStandardEXT conv_standard = itu_601_full_range;
    yuvCscStandardEXT conv_standard = itu_709;

贴一个源码展示下内置颜色空间转换函数使用方法。

代码语言:javascript复制
#version 300 es
#extension GL_EXT_YUV_target: require
precision mediump float;
in vec2 v_texCoord;
layout(yuv) out vec4 outColor;
uniform sampler2D s_TextureMap;
void main()
{
    yuvCscStandardEXT conv_standard = itu_709;
    vec4 rgbaColor = texture(s_TextureMap, v_texCoord);
    vec3 rgbColor = vec3(rgbaColor.r, rgbaColor.g, rgbaColor.b);
    vec3 yuv = rgb_2_yuv(rgbColor, conv_standard);
    outColor = vec4(yuv, 1.0);
}

———————————————— 参考链接:https://blog.csdn.net/Kennethdroid/article/details/133244034 参考链接:https://stackoverflow.com/questions/17892346/how-to-convert-rgb-yuv-rgb-both-ways 参考链接:https://registry.khronos.org/OpenGL/extensions/EXT/EXT_YUV_target.txt

-- END --

获取相关资料和源码

推荐:

Android FFmpeg 实现带滤镜的微信小视频录制功能

全网最全的 Android 音视频和 OpenGL ES 干货,都在这了

一文掌握 YUV 图像的基本处理

抖音传送带特效是怎么实现的?

所有你想要的图片转场效果,都在这了

面试官:如何利用 Shader 实现 RGBA 到 NV21 图像格式转换?

我用 OpenGL ES 给小姐姐做了几个抖音滤镜

项目疑难问题解答、大厂内部推荐、面试指导、简历指导、代码指导、offer 选择建议、学习路线规划,可以点击找我一对一解答。

0 人点赞