最近,有位读者大人在后台反馈:在参加一场面试的时候,面试官要求他用 shader 实现图像格式 RGB 转 YUV ,他听了之后一脸懵,然后悻悻地对面试官说,他只用 shader 做过 YUV 转 RGB,不知道 RGB 转 YUV 是个什么思路。

针对他的这个疑惑,今天专门写文章介绍一下如何使用 OpenGL 实现 RGB 到 YUV 的图像格式转换,帮助读者大人化解此类问题。

YUV 看图工具推荐

有读者大人让推荐一个 YUV 看图软件,由于手头的工具没法分享出来,又在 Github 上找了一圈发现这一类开源软件用起来都不少 BUG 。

最后发现一款免费的商业软件 YUV Viewer ,用起来还行。

https://www.elecard.com/products/video-analysis/yuv-viewer

就是下载起来比较慢,我这里给读者大人已经下载好了,【字节流动】 后台回复关键字 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 排布:

(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  U11

YUV 渲染步骤:

  • 生成 2 个纹理,编译链接着色器程序;
  • 确定纹理坐标及对应的顶点坐标;
  • 分别加载 NV21 的两个 Plane 数据到 2 个纹理,加载纹理坐标和顶点坐标数据到着色器程序;
  • 绘制。

片段着色器脚本:

#version 300 es
precision mediump float;
in vec2 v_texCoord;
layout(location = 0) out vec4 outColor;
uniform sampler2D y_texture;
uniform sampler2D uv_texture;
void main()
{
vec3 yuv;
yuv.x = texture(y_texture, v_texCoord).r;
yuv.y = texture(uv_texture, v_texCoord).a-0.5;
yuv.z = texture(uv_texture, v_texCoord).r-0.5;
vec3 rgb =mat3( 1.0,       1.0,        1.0,                    0.0,        -0.344,     1.770,                  1.403,  -0.714,       0.0) * yuv;
outColor = vec4(rgb, 1);
}

y_texture 和 uv_texture 分别是 NV21 Y Plane 和 UV Plane 纹理的采样器,对两个纹理采样之后组成一个(y,u,v)三维向量,之后左乘变换矩阵转换为(r,g,b)三维向量。

上面 YUV 转 RGB shader 中,面试官喜欢问的问题(一脸坏笑):为什么 UV 分量要减去 0.5 啊?

(迷之自信)答曰:因为归一化。YUV 格式图像 UV 分量的默认值分别是 127 ,Y 分量默认值是 0 ,8 个 bit 位的取值范围是 0 ~ 255,由于在 shader 中纹理采样值需要进行归一化,所以 UV 分量的采样值需要分别减去 0.5 ,确保 YUV 到 RGB 正确转换。

**需要注意的是 OpenGL ES 实现 YUV 渲染需要用到 GL_LUMINANCE 和 GL_LUMINANCE_ALPHA 格式的纹理,其中 GL_LUMINANCE 纹理用来加载 NV21 Y Plane 的数据,GL_LUMINANCE_ALPHA 纹理用来加载 UV Plane 的数据,**这一点很重要,初学的读者大人请好好捋一捋。

关于 shader 实现 YUV 转 RGB (NV21、NV12、I420 格式图像渲染)可以参考文章: OpenGL ES 3.0 开发(三):YUV 渲染 和 FFmpeg 播放器视频渲染优化,本文主要重点讲 shader 如何实现 RGB 转 YUV 。

RGB 转 YUV

来到本文的重点,那么如何利用 shader 实现 RGB 转 YUV 呢?

前面小节已经提到,先说下一个简单的思路:先将 RGBA 按照公式转换为 YUV 如(YUYV),然后将 YUYV 按照 RGBA 进行排布,最后使用 glReadPixels 读取 YUYV 数据,由于 YUYV 数据量为 RGBA 的一半,需要注意输出 buffer 的大小,以及 viewport 的宽度(宽度为原来的一半)。

RGB to YUV 的转换公式:

开门见山,先贴实现 RGBA 转 YUV 的 shader 脚本:

#version 300 es
precision mediump float;
in vec2 v_texCoord;
layout(location = 0) out vec4 outColor;
uniform sampler2D s_TextureMap;//RGBA 纹理
uniform float u_Offset;//采样偏移//RGB to YUV
//Y =  0.299R + 0.587G + 0.114B
//U = -0.147R - 0.289G + 0.436B
//V =  0.615R - 0.515G - 0.100Bconst vec3 COEF_Y = vec3( 0.299,  0.587,  0.114);
const vec3 COEF_U = vec3(-0.147, -0.289,  0.436);
const vec3 COEF_V = vec3( 0.615, -0.515, -0.100);void main()
{vec2 texelOffset = vec2(u_Offset, 0.0);vec4 color0 = texture(s_TextureMap, v_texCoord);//偏移 offset 采样vec4 color1 = texture(s_TextureMap, v_texCoord + texelOffset);float y0 = dot(color0.rgb, COEF_Y);float u0 = dot(color0.rgb, COEF_U) + 0.5;float v0 = dot(color0.rgb, COEF_V) + 0.5;float y1 = dot(color1.rgb, COEF_Y);outColor = vec4(y0, u0, y1, v0);
}

shader 实现 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 时 width 变为原来的一半,同样 glReadPixels 时 width 也变为原来的一半。

实现 RGBA 转成 YUYV 要保证原图分辨率不变,建议使用 FBO 离屏渲染 ,这里注意绑定给 FBO 的纹理是用来容纳 YUYV 数据,其宽度应该设置为原图的一半。

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);glBindFramebuffer(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_RenderImage.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 数据:

glBindFramebuffer(GL_FRAMEBUFFER, 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 buffer = width * height * 2; 转换成 YUYV 时数据量减半,注意 buffer
uint8_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 时 width 变为原来的一半
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:

https://github.com/githubhaohao/NDK_OpenGLES_3_0

那么面试官的问题又来了(一脸坏笑): RGBA 转 YUV 的 shader 中 uv 分量为什么要加 0.5 ? 请读者大人结合上面文章给予有力回击。

技术交流

技术交流/获取视频教程可以添加我的微信:Byte-Flow

使用 OpenGL 实现 RGB 到 YUV 的图像格式转换相关推荐

  1. 实验二 RGB到YUV格式的转换

    前言 将BMP文件转换成YUV文件 RGB存储结构 本算法使用的RGB文件格式为像素形式的RGB24文件.一个像素用24个bit来表示,3个字节,R,G,B分量分别用8个bit来表示,取值范围为0-2 ...

  2. 一文读懂rawRGB、RGB和YUV数据格式与转换

    rawRGB 图像采集的过程为:光照在成像物体被反射 -> 镜头汇聚 -> Sensor光电转换-> ADC转换为rawRGB 因为sensor上每个像素只采集特定颜色的光的强度,因 ...

  3. RGB和YUV视频存储格式

    RGB和YUV视频存储格式 视频数据,图像数据在存储中的存储格式主要有RGB和YUV.RGB是多媒体数据进行数据存储采用的主流格式:YUV格式所需存储空间一般是RGB格式存储空间的一半,所以YUV格式 ...

  4. RGB与YUV转换以及存储格式(YIQ)(信号扫描线)(内插补点算法)(紧缩格式(packed formats))(平面格式(planar formats))

    文章目录 色彩空间与色彩模型 色彩空间 色彩模型 不同色彩空间的比较 1.RGB(采集与显示) 2. YUV YCbCr的两种常用格式 1)ITU-R BT.601 conversion 2)JPEG ...

  5. 最简单的视音频播放演示样例5:OpenGL播放RGB/YUV

    ===================================================== 最简单的视音频播放演示样例系列文章列表: 最简单的视音频播放演示样例1:总述 最简单的视音频 ...

  6. 最简单的视音频播放示例5:OpenGL播放RGB/YUV

    ===================================================== 最简单的视音频播放示例系列文章列表: 最简单的视音频播放示例1:总述 最简单的视音频播放示例 ...

  7. RAW、RGB 、YUV三种图像格式理解

    文章目录 1. 背景 2. 相关概念 2.1 颜色与色彩空间 2.2 RAW图像 2.3 RGB图像 2.4 YUV图像 3. 分类简图 RAW.RGB .YUV三种图像格式理解 1. 背景 在工作中 ...

  8. c语言bmp图像YUV转化成RGB,RGB与YUV图像格式的相互转换

    RGB与YUV图像格式的相互转换 (参考上的<RGB与YUV图像视频格式的相互转换>文章,做了些修改) RGB介绍:在记录计算机图像时,最常见的是采用RGB(红.绿,蓝)颜色分量来保存颜色 ...

  9. yuv与rgb图像格式转换

    一.前言 本次试验内容为将256*256,采样为4:2:0的yuv图像转为rgb格式.老师提供了rgb2yuv的源码,经过分析,发现源码已经极其优雅高效,命名方式合理,内存分配刚刚好,还运用了查找表的 ...

  10. RGB与YUV的转换

    计算机领域,RGB被称为基色分量,组合后能显示的颜色叫做颜色空间,一般取值范围从0-255(2^8,可以显示1600万多种颜色:现在有的显示器为10位位深,即2^10,约可以显示10亿种颜色) 还有一 ...

最新文章

  1. kangle php集成环境包,PHP探针-UPUPW环境集成包KANGLE专用版 | bftxjc.com contacts
  2. screen史上最全教程
  3. SSH实现论坛BBS系统
  4. Set 和 Map 数据结构
  5. shell脚本实例-判断主机存活 以及企业备份方案
  6. Spring,Reactor和ElasticSearch:使用伪造的测试数据进行标记
  7. L1-007 念数字 (10 分)—团体程序设计天梯赛
  8. Atitit xml xpath以及mybatis xml mapper脱机解析 目录 1.1. parseStt(String id, Element root) 1 1.2. parseStt
  9. word鼠标右下角有一个小方块_word
  10. 微信订阅消息模板消息推送-JAVA
  11. ipad和android同步照片,4种方式将照片从iPhone传输到iPad
  12. 关于阅读论文的一些感想
  13. Mac苹果键盘多个按键没响应该如何解决呢
  14. 用@ExceptionHandler 来进行异常处理
  15. js控制键盘按键(回车、空格)
  16. 时序数据库为什么选 Prometheus
  17. httprunner之业务解耦
  18. C#使用ffmpeg抽帧压缩mp4
  19. 如何取消计算机中的文件夹加密文件,文件夹加密码怎么设置怎么解除
  20. VC在X64模式下不支持__declspec(naked)

热门文章

  1. iphone个系列尺寸_iphone各机型尺寸
  2. Racket编程指南——17 创造语言
  3. archlinux 分区
  4. 正版授权| iObit Uninstaller 12 Pro 专业卸载器工具
  5. linux wifi信道,2.4G wifi 的频道/信道 20M 40M的概念,区别
  6. java鼠标乱跑_win7鼠标光标乱跳怎么办?解决win7鼠标光标乱跑的方法
  7. 服务器装不上无线网卡代码10,win10专业版无线网卡出现错误代码10怎么办?
  8. 移动端扫描vin码/车架号识别
  9. 解决VS2013 对话框界面编程Caption中文乱码办法
  10. 已知坐标增量求坐标方位角_数控机床绝对坐标与增量坐标编程运用,一学便会...