Opengl ES中YUV420转RGB

Opengl ES中YUV420转RGB

一、先了解一个概念“灰度图”

1、灰度图的定义:

把白色与黑色之间按对数关系分为若干等级,称为灰度。灰度分为256阶。

2、举例:

老式黑白电视机的图像,即灰度图像

3、灰度值与RGB的计算公式:

Y = 0.299R + 0.587G + 0.114*B

4、这里有一个opengl es把彩色图片转化为灰度图的案例,效果如下:

Opengl ES中YUV420转RGB_第1张图片

Opengl ES中YUV420转RGB_第2张图片

转化的shader代码如下:

precision mediump float;
varying vec2 vTextureCoord;
uniform sampler2D sTexture;

void main() {
         vec4 color=texture2D(sTexture, vTextureCoord);
         //
         float col=color.r*0.299+color.g*0.587+color.b*0.114;
         //
         color.r=col;

         color.g=col;

         color.b=col;
         //
         gl_FragColor =color;
}

代码中,将RGB色彩通道的数值设置为了计算出的灰度值。

二、YUV数据格式

1、YUV

Y:就是灰度值;
UV:用来指定像素的颜色。

2、使用YUV的好处:

  • (1)、传输信号向后兼容老式黑白电视机(用于优化彩色视频信号的传输,使其向后相容老式黑白电视)
  • (2)、YUV420占用的带宽少(为什么,后边来介绍)

YUV420与RGB视频信号传输相比,它最大的优点在于只需占用极少的频宽(后面来介绍)

3、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

三、YUV444和YUV420

YUV444中,一个像素点对应一个Y一个U一个V,如下图所示:
Opengl ES中YUV420转RGB_第3张图片

YUV420中,一个像素点对应一个Y;四个像素点对应一个U一个V(Y、U、V没有一一对应,图像有颜色损失,但是占用的带宽也少了)

Opengl ES中YUV420转RGB_第4张图片

四、YUV420转RGB

YUV420转RGB要分两个步骤,

  • 第一个步骤YUV420转YUV444;
  • 第二个步骤YUV444转RGB。

1、YUV420转YUV444

要把YUV420转为YUV444就得把“图4 YUV420”中U与V中“?”的部分填满。
这里可以采用线性差值的方式,把像素点与Y、U、V一一对应,差值计算如下:

U01 = (U00 + U02)/2;
U10 = (U00 + U20)/2;
U11 = (U00 + U02 + U20 + U22)/4;

//######################
V01 = (V00 + V02)/2;
V10 = (V00 + V20)/2;
V11 = (V00 + V02 + V20 + V22)/4;

2、YUV444转RGB

直接用YUV转RGB的公式:

R = Y + 1.140*V
G = Y - 0.394*U - 0.581*V
B = Y + 2.032*U

说明:一、二、三、四,这四点介绍的是YUV转RGB的基本原理,下边是具体实现。

五、OpenGL ES中YUV420P转RGB

1、YUV420p的数据格式

YUV420p的数据格式如下图所示(为一个byte[]):
Opengl ES中YUV420转RGB_第5张图片
其中数据的4/6为Y;1/6为U;1/6为V。

2、YUV420sp的数据格式(YUV420sp转RGB这里不做介绍)

YUV420sp的数据格式如下图所示(为一个byte[]):
Opengl ES中YUV420转RGB_第6张图片
其中数据的4/6为Y;1/6为U;1/6为V。

3、YUV转RGB

  • (1)、将一帧数据中的Y取出生成一张灰度图像、将U的数据取出生成一张图像、V的数据取出生成一张图像。
  • (2)、将三张图像生成三张纹理图片传入“片元着色器”,YUV纹理如下图所示:

Opengl ES中YUV420转RGB_第7张图片

  • (3)设置纹理采样方式为:线性采样
    设置线性采样的作用即: 线性采样出U、V纹理中“?”部分的颜色值。这样就就可以拿到一一对应的YUV数据。

对应代码实现:

/**
	 * 
	 * @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;
	}
	

代码说明:

已上代码便是将传入的帧数据byte frameData[],转为三张纹理图的代码。代码的16行、5051行、7576行分别为从byte frameData[]中分别取出Y、U、V数据的代码。代码5659行、代码8184行分别为设置U、V纹理的采样方式为线性采样的代码。以上代码运行结束,内存中会生成三张纹理图像。将三张纹理图像传入“片元着色器”执行下一步骤。

  • (3)按照YUV转RGB的公式,将Y、U、V一一对应的取出,进行YUV转RGB操作,生成一个像素点。

对应代码:

precision mediump float;

uniform sampler2D sTexture_y;
uniform sampler2D sTexture_u;
uniform sampler2D sTexture_v;

varying vec2 vTextureCoord;

//公式转码
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
 	float y = texture2D(sTexture_y, vTextureCoord).r;
    float u = texture2D(sTexture_u, vTextureCoord).r;
    float v = texture2D(sTexture_v, vTextureCoord).r;
	// 转码公式
	getRgbByYuv(y, u, v, r, g, b);
	
	// 最终颜色赋值
	gl_FragColor = vec4(r,g,b, 1.0); 
}

代码说明:
代码26~28行便是,一一对应的采样出Y、U、V的数据,代码中getRgbByYuv(y,u,v,r,g,b)便是根据公式进行YUV转RGB操作的代码。

========== THE END ==========

你可能感兴趣的:(OpenGL,ES,移动音视频)