受Model 3注册量下降影响,一季度特斯拉在加州的汽车注册量同比小幅下降
06-18
面试者:请使用OpenGL实现RGB到YUV图像格式转换。我...最近有读者在后台反馈:参加面试时,面试官要求他使用shader将图像格式RGB转换为YUV。
听完后,他一脸困惑,然后愤怒地对面试官说,“他只用过shader将YUV转换为RGB,不知道RGB转换为YUV的想法是什么。”针对他的疑问,今天写一篇文章介绍如何使用OpenGL将RGB转换为YUV图像格式,帮助读者解决此类问题。
推荐YUV图像查看工具 有读者向我推荐一款YUV图像查看软件。由于手头的工具无法共享,我在Github上搜了一圈,发现这类开源软件有很多bug。
YUV查看工具 最后找到了一个免费的商业软件,YUV Viewer,用起来还可以。代码语言:txt复制,后台回复关键字yuvViewer即可获取。
好处 使用shader将RGB转YUV图像格式有哪些使用场景?它在生产环境中非常常用。上一篇文章介绍了Android OpenGL渲染图像的读取方法,分别是glReadPixels、PBO、ImageReader和HardwareBuffer。
glReadPixels经常用来读取RGBA格式的图像,那么我可以用它来读取YUV格式的图像吗?答案是肯定的,这需要使用shader将RGB图像格式转换为YUV图像格式。 glReadPixels 性能瓶颈通常出现在读取大分辨率图像时。
生产环境中常见的优化方法是在shader中将处理后的RGBA转换为YUV(通常是YUYV),然后根据RGBA格式读出YUV图像。这样,传输的数据量就会减少一半,性能会显着提升,而且不需要考虑兼容性问题。
YUV转RGB这一节首先简单介绍一下YUV转RGB的实现。在上一篇文章中,我介绍了OpenGL实现YUV渲染。
其实就是利用shader来实现YUV(NV21)到RGBA的转换,然后进行渲染。到屏幕上。
以渲染 NV21 格式的图像为例,以下是(4x4)NV21 图像的 YUV 布局: 代码语言:txt copy (0 ~ 3) Y00 Y01 Y02 Y03 (4 ~ 7) Y10 Y11 Y12 Y13 (8 ~ 11) Y20 Y21 Y22 Y23 (12 ~ 15) Y30 Y31 Y32 Y33 (16 ~ 19) V00 U00 V01 U01 (20 ~ 23) V10 U10 V11 U11YUV 渲染步骤:生成2张纹理,编译链接shader程序;确定纹理坐标和对应的顶点坐标;将NV21的两个Plane数据分别加载到2张纹理中,将纹理坐标和顶点坐标数据加载到shader程序中;画。片段着色器脚本:代码语言:txt copy #version es precisionmediump float;在 vec2 v_texCoord 中;布局(位置= 0)输出vec4 outColor;均匀采样器2D y_texture;均匀采样器2D uv_texture; void main() { vec3 yuv;yuv.x = 纹理(y_texture, v_texCoord).r; yuv.y = 纹理(uv_texture, v_texCoord).a-0.5;yuv.z = 纹理(uv_texture, v_texCoord).r-0.5;vec3 rgb =mat3( 1.0, 1.0, 1.0 , 0.0, -0., 1., 1., -0., 0.0) * yuv; outColor = vec4(rgb, 1);}y_texture 和 uv_texture 分别是 NV21 Y Plane 和 UV Plane 纹理的采样器。
纹理采样后形成一个(y,u,v)三维向量,然后将左乘变换矩阵转换为(r,g,b)三维向量。在上面的YUV到RGB着色器转换中,面试官喜欢问问题(带着坏笑):为什么UV分量需要减去0.5? (神秘的自信) 答:因为正常化。
YUV格式图像的UV分量的默认值为:Y分量的默认值为0,8位的取值范围为0~。由于纹理采样值需要在着色器中进行归一化,因此UV分量的采样值需要分别减去0.5,以确保正确的YUV到RGB转换。
需要注意的是,OpenGL ES需要使用GL_LUMINANCE和GL_LUMINANCE_ALPHA格式的纹理来实现YUV渲染。 GL_LUMINANCE纹理用于加载NV21 Y Plane的数据,GL_LUMINANCE_ALPHA纹理用于加载UV Plane的数据。
对于初学者来说,这一点非常重要。请仔细看一下。
YUV转RGB(NV21、NV12、I格式图像渲染)的shader实现可以参考文章:OpenGL ES 3.0开发(三):YUV渲染及FFmpeg播放器视频渲染优化。本文主要关注Shader如何实现RGB转YUV。
RGB到YUV的转换就来到了本文的重点,那么如何使用shader来实现RGB到YUV的转换呢?上一节提到,我们先从一个简单的思路开始:首先根据(YUYV)等公式将RGBA转换为YUV,然后根据RGBA排列YUYV,最后使用glReadPixels读取YUYV数据。由于YUYV数据量是RGBA大小的一半,所以需要注意输出缓冲区的大小和视口的宽度(宽度是原来的一半)。
RGB到YUV转换公式: RGB到YUV转换公式开门见山。首先贴上实现RGBA到YUV转换的shader脚本: 代码语言:txt copy #version es precision Mediump float;in vec2 v_texCoord;layout(location = 0) out vec4 outColor; uniform Sampler2D s_TextureMap;//RGBA纹理uniform float u_Offset;//采样偏移//RGB转YUV//Y = 0.R + 0.G + 0.B//U = -0.R - 0.G + 0 .B//V = 0.R - 0.G - 0.Bconst vec3 COEF_Y = vec3( 0., 0., 0.);const vec3 COEF_U = vec3(-0., -0., 0.) ; const vec3 COEF_V = vec3( 0., -0., -0.);void main(){ vec2 texelOffset = vec2(u_Offset, 0.0); vec4 color0 = 纹理(s_TextureMap, v_texCoord); //Offset偏移采样vec4 color1 =texture(s_TextureMap, v_texCoord + texelOffset); float y0 = 点(color0.rgb, COEF_Y);浮动 u0 = 点(color0.rgb, COEF_U) + 0.5;浮点数 v0 = 点(color0.rgb, COEF_V) + 0.5; float y1 = 点(color1.rgb, COEF_Y); outColor = vec4(y0, u0, y1, v0);} 着色器实现 RGB 转 YUV 示意图: 着色器实现 RGB 转 YUV 示意图 我们需要将 RGBA 转换为 YUYV ,数据量不到 RGBA 的一半,其中相当于将两个像素合并为一个像素。
如图所示,我们在shader中进行两次采样,RGBA像素(R0,G0,B0,A0)转换为(Y0,U0,V0),像素(R1,G1,B1,A1)转换为(Y1) ,然后组合成(Y0,U0,Y1,V0),这样8个字节表示的2个RGBA像素就转换成了4个字节表示的2个YUYV像素。转换为YUYV时,数据量减半,那么使用glViewPort时宽度就变成原来的一半,同样使用glReadPixels时宽度就变成原来的一半。
将RGBA转换为YUYV,保证原图分辨率不变,建议使用FBO离屏渲染。这里注意,FBO绑定的纹理是用来容纳YUYV数据的,其宽度要设置为原图的一半。
代码语言:txt copy bool RGB2YUVSample::CreateFrameBufferObj(){//创建并初始化FBO纹理 glGenTextures(1, &m_FboTextureId);glBindTexture(GL_TEXTURE_2D, m_FboTextureId);glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);glTexParameterf(GL_TEXTURE _2D、GL_TEXTURE_WRAP_T、GL_CLAMP_TO_EDGE );glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);glBindTexture(GL_TEXTURE_2D, GL_NONE);//创建并初始化FBOglGenFramebuffers(1, & m_FboId);glBindFrame缓冲区(GL_FRAMEBUFFER,m_FboId);glBindTexture(GL_TEXTURE_2D , m_FboTextureId);glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_FboTextureId, 0);//FBO纹理用于容纳YUYV数据,其宽度应设置为原图的一半 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_R enderImage .width / 2, m_RenderImage.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);if (glCheckFramebufferStatus(GL_FRAMEBUFFER)!= GL_FRAMEBUFFER_COMPLETE) {LOGCATE("RGB2YUVSample::CreateFrameBufferObj glCheckFramebufferStatus status != GL_FRAMEBUFFER_COMPLETE");return false;}glBindTexture(GL_TEXTURE_2D, GL_NONE);glBindFramebuffer(GL_FRAMEBUFFER, GL_NONE);return true;}离屏渲染并读取YUYV数据: 代码语言:txt复制 glBindFramebuffer(GL_FRAME BUFFER, m_FboId );//渲染成yuyv,像素宽度减半,glviewport宽度减半 glViewport(0, 0, m_RenderImage.width / 2, m_RenderImage.height);glUseProgram(m_FboProgramObj);glBindVertexArray(m_VaoIds[ 1]);glActiveTexture(GL_TEXTURE0) ;glBindTexture(GL_TEXTURE_2D, m_ImageTextureId);glUniform1i(m_FboSamplerLoc, 0);//参考原理图,偏移量要设置为1/(width / 2) * 1/2 = 1 / width; 理论上一半的纹素 float texelOffset = (float) (1.f / (float) m_RenderImage.width);GLUtils::setFloat(m_FboProgramObj, "u_Offset", texelOffset);glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, ( const void *)0);glBindVertexArray( 0);glBindTexture(GL_TEXTURE_2D, 0);//YUYV 缓冲区 = 宽度 * 高度 * 2;转换为YUYV时,数据量减半。注意 bufferuint8_t *pBuffer = new uint8_t[m_RenderImage.width * m_RenderImage.height * 2];NativeImage nativeImage = m_RenderImage;nativeImage.format = IMAGE_FORMAT_YUYV;nativeImage.ppPlane[0] = pBuffer;//当glReadPixels时,宽度变成原来的一半 glReadPixels(0, 0, m_RenderImage.width / 2 , nativeImage .height, GL_RGBA, GL_UNSIGNED_BYTE, pBuffer);DumpNativeImage(&nativeImage, "/sdcard/DCIM");delete []pBuffer;glBindFramebuffer(GL_FRAMEBUFFER, 0);完整代码参考下面工程,选择OpenGL RGB to YUV demo: 代码语言:txt 复制RGBA到YUV着色器,为什么UV分量需要加0.5?请读者根据上述文章给予强有力的反击。
版权声明:本文内容由互联网用户自发贡献,本站不拥有所有权,不承担相关法律责任。如果发现本站有涉嫌抄袭的内容,欢迎发送邮件 举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。
标签:
相关文章
06-17
06-18
06-17
06-18
最新文章
【玩转GPU】ControlNet初学者生存指南
【实战】获取小程序中用户的城市信息(附源码)
包雪雪简单介绍Vue.js:开学
Go进阶:使用Gin框架简单实现服务端渲染
线程池介绍及实际案例分享
JMeter 注释 18 - JMeter 常用配置组件介绍
基于Sentry的大数据权限解决方案
【云+社区年度征文集】GPE监控介绍及使用