先说为什么会有这一篇文章: 这篇文章之前叫做 Opengl ES中YUV420转RGB 是一个技术标题。整理时,发现用这个标题,大家实际是不知道这个技术有什么用,因此换了这个比较醒目的名字。 Opengl ES中YUV420转RGB 这项技术主要是实现视频高效、节省带宽的回显视频图像。
这里通过以下几个方面具体说明Opengl ES中YUV420转RGB 这项技术的实现方式:
一、先了解一个概念“灰度图”这里先了解一下灰度 Y 的概念。不知道大家是否看过老式的黑白电视机?
1.1、灰度图的定义:把白色与黑色之间按对数关系分为若干等级,称为灰度。灰度分为256阶。 1.2、灰度值Y与RGB的计算公式:Y = 0.299R + 0.587G + 0.114*B 电视台发出信号时,将RGB数据这样转化为Y 数据。老式黑白电视机接收到Y信号,就能展示图象了。 1.4、将“彩色图转”转化为“灰度图”shader实现这里说一个技术实现,在OpenGL ES中,如何用shader片元着色器,把一个彩色纹理图转化为一个灰度图? 效果如下: 转化当然要用到我们上边说道的RGB 转 Y的公式,下边我们看具体的片源着色器 shader 代码实现: // shader 片元着色器 precision mediump float; varying vec2 vTextureCoord; uniform sampler2D sTexture; void main() { // 从纹理图sTexture 读取当前片元的RGB颜色 vec4 color=texture2D(sTexture, vTextureCoord); // 公式计算灰度值 float col=color.r*0.299+color.g*0.587+color.b*0.114; // 将生成的Y 灰度值设置给RGB通道 color.r=col; color.g=col; color.b=col; // 传给片源着色器 gl_FragColor =color; } 在shader实现中,我特意加了注释。 二、YUV数据格式上边我们了解了灰度图的实现,这里我们介绍一个YUV数据格式。
2.1、YUVYUV的具体定义如下: Y:就是灰度值; UV:用来指定像素的颜色。 对于UV现在有些懵没关系,我们继续往下看 2.2、YUV与RGB转换公式// RGB转YUV Y= 0.299*R + 0.587*G + 0.114*B U= -0.147*R - 0.289*G + 0.436*B = 0.492*(B- Y) V= 0.615*R - 0.515*G - 0.100*B = 0.877*(R- Y) //############################################ // YUV转RGB R = Y + 1.140*V G = Y - 0.394*U - 0.581*V B = Y + 2.032*U 2.3、使用YUV的好处:
YUV420与RGB视频信号传输相比,它最大的优点在于只需占用极少的频宽(后面来介绍) 2.4、YUV444和YUV420前边我们一直说的YUV数据,其实是YUV420数据格式。YUV420数据格式在传输上UV色彩是有损传输,而YUV444 其实是一种无损的数据格式。 首先介绍YUV444 数据格式:
YUV444 中YUV通道一一对应,理解简单。下边这个是YUV420数据格式,UV数据有损。
具体数据格式如下: 从上图可以看到,UV色彩通道是有损失的,这也是为什么YUV420在展示时,占用的带宽更少一下。 a、Y、U、V没有一一对应,图像有颜色损失 因为占用的流量较少,对色彩展示几乎没有影响,因此广泛应用于各中视频通话场景,视频回显场景等。 三、YUV420转RGB哇去,基础知识终于说完了,这里说到我们的核心技术点:YUV420转RGB
为什么要把YUV420转为YUV444? 先说 YUV420 转 YUV444 3.1、YUV420转YUV444要把YUV420转为YUV444就得把“上图 YUV420” U与V中 “?” 的部分填满。 通过YUV420数据中,已有的U 与 Y数据,通过差值计算的方式,填补上空缺的部分。以下是差值运算的具体实现公式,差值计算如下(建议参照YUV420数据格式图来看,要不容易懵): U01 = (U00 + U02)/2; // 利用已有的 U00、U02来计算U01 U10 = (U00 + U20)/2; // 利用已有的 U00、U20来计算U10 U11 = (U00 + U02 + U20 + U22)/4;// 利用已有的 U00、U02、U20、U22来计算U11 //###################### V01 = (V00 + V02)/2; // 利用已有的 V00、V02来计算V01 V10 = (V00 + V20)/2; // 利用已有的 V00、V20来计算V10 V11 = (V00 + V02 + V20 + V22)/4; // 利用已有的 V00、V02、V20、V22来计算V11 经过以上公式,YUV420转YUV444 完成(数据补全成功),下边来说YUV444如何转RGB。 3.2、YUV444转RGBYUV444转RGB是有现成公式的,我们直接拿来用就行了,YUV转RGB的公式: R = Y + 1.140*V G = Y - 0.394*U - 0.581*V B = Y + 2.032*U 公式有了,那具体的代码实现是怎么实现的呢? 注: 四、OpenGL ES中YUV420P转RGB这一节介绍具体技术实现,但开始时,还是要介绍两种数据格式(哎、我知道你们都烦了,我其实也烦,但还是得说)
4.1、YUV420p的数据格式YUV420p的数据格式如下图所示(为一个byte[]): 其中数据的4/6为Y;1/6为U;1/6为V。 4.2、YUV420sp的数据格式(YUV420sp转RGB这里不做介绍)YUV420sp的数据格式如下图所示(为一个byte[]): 其中数据的4/6为Y;1/6为U;1/6为V。 4.3、YUV420sp 转RGB其实大概原理就是:
以下为YUV三张纹理图效果图: YUV420转YUV444 这里如何补全YUV420数据中UV部分的颜色数据? 这里有一个讨巧的方式: 对应的Java代码如下: /** * * @param w * @param h * @param date * 数据 * @param textureY * @param textureU * @param textureV * @param isUpdate * 是否为更新 */ public static boolean bindYUV420pTexture(int frameWidth, int frameHeight, byte frameData[], int textureY, int textureU, int textureV, boolean isUpdate) { if (frameData == null || frameData.length == 0) { return false; } Log.d(TAG, "----bindYUV420pTexture-----"); if (isUpdate == false) { /** * 数据缓冲区 */ // Y ByteBuffer buffer = LeBuffer.byteToBuffer(frameData); // GLES20.glActiveTexture(GLES20.GL_TEXTURE0); GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureY); GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);// GL_LINEAR_MIPMAP_NEAREST GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR); GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); /** * target 指定目标纹理,这个值必须是GL_TEXTURE_2D; level * 执行细节级别,0是最基本的图像级别,n表示第N级贴图细化级别; internalformat * 指定纹理中的颜色组件,可选的值有GL_ALPHA,GL_RGB,GL_RGBA,GL_LUMINANCE, * GL_LUMINANCE_ALPHA 等几种; width 指定纹理图像的宽度; height 指定纹理图像的高度; border * 指定边框的宽度; format 像素数据的颜色格式,可选的值参考internalformat; type * 指定像素数据的数据类型,可以使用的值有GL_UNSIGNED_BYTE * ,GL_UNSIGNED_SHORT_5_6_5,GL_UNSIGNED_SHORT_4_4_4_4 * ,GL_UNSIGNED_SHORT_5_5_5_1; pixels 指定内存中指向图像数据的指针; * */ GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE, frameWidth, frameHeight, 0, GLES20.GL_LUMINANCE, GLES20.GL_UNSIGNED_BYTE, buffer); /** * */ // U buffer.clear(); buffer = LeBuffer.byteToBuffer(frameData); buffer.position(frameWidth * frameHeight); // // GLES20.glActiveTexture(GLES20.GL_TEXTURE1); GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureU); GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);// GL_LINEAR_MIPMAP_NEAREST GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR); GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE, frameWidth / 2, frameHeight / 2, 0, GLES20.GL_LUMINANCE, GLES20.GL_UNSIGNED_BYTE, buffer); /** * */ // V buffer.clear(); buffer = LeBuffer.byteToBuffer(frameData); buffer.position(frameWidth * frameHeight * 5 / 4); // // GLES20.glActiveTexture(GLES20.GL_TEXTURE2); GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureV); GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);// GL_LINEAR_MIPMAP_NEAREST GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR); GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE, frameWidth / 2, frameHeight / 2, 0, GLES20.GL_LUMINANCE, GLES20.GL_UNSIGNED_BYTE, buffer); } else { /** * Y */ ByteBuffer buffer = LeBuffer.byteToBuffer(frameData); GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureY); GLES20.glTexSubImage2D(GLES20.GL_TEXTURE_2D, 0, 0, 0, frameWidth, frameHeight, GLES20.GL_LUMINANCE, GLES20.GL_UNSIGNED_BYTE, buffer); /** * U */ // buffer.clear(); buffer = LeBuffer.byteToBuffer(frameData); buffer.position(frameWidth * frameHeight); // GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureU); GLES20.glTexSubImage2D(GLES20.GL_TEXTURE_2D, 0, 0, 0, frameWidth / 2, frameHeight / 2, GLES20.GL_LUMINANCE, GLES20.GL_UNSIGNED_BYTE, buffer); /** * V */ // buffer.clear(); buffer = LeBuffer.byteToBuffer(frameData); buffer.position(frameWidth * frameHeight * 5 / 4); // GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureV); GLES20.glTexSubImage2D(GLES20.GL_TEXTURE_2D, 0, 0, 0, frameWidth / 2, frameHeight / 2, GLES20.GL_LUMINANCE, GLES20.GL_UNSIGNED_BYTE, buffer); } return true; } 代码说明: YUV444转RGB YUV一一对应的纹理有了,这里该介绍如何实现YUV444转RGB了: 按照YUV转RGB的公式,将Y、U、V一一对应的取出,进行YUV转RGB操作,生成像素点。 对应片元着色器 shader 代码实现: recision mediump float; // 片元着色器中 输入了Y U V三张纹理 uniform sampler2D sTexture_y; uniform sampler2D sTexture_u; uniform sampler2D sTexture_v; varying vec2 vTextureCoord; //YUV 转 RGB的 shader 实现 void getRgbByYuv(in float y, in float u, in float v, inout float r, inout float g, inout float b){ // y = 1.164*(y - 0.0625); u = u - 0.5; v = v - 0.5; // r = y + 1.596023559570*v; g = y - 0.3917694091796875*u - 0.8129730224609375*v; b = y + 2.017227172851563*u; } void main() { // float r,g,b; // 从YUV三张纹理中,采样出一一对应的YUV数据 float y = texture2D(sTexture_y, vTextureCoord).r; float u = texture2D(sTexture_u, vTextureCoord).r; float v = texture2D(sTexture_v, vTextureCoord).r; // YUV 转 RGB getRgbByYuv(y, u, v, r, g, b); // 最终颜色赋值 gl_FragColor = vec4(r,g,b, 1.0); } 五、完事大吉源码真的是懒得整理,所以,大家还是理解了实现原理,自己动手去敲吧,不要找我要代码了!!! ========== THE END ========== |
|