早上知识星球里的一位同学,遇到 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 选择建议、学习路线规划,可以点击找我一对一解答。