在图像的世界里,一般使用RGB作为存储格式。而在视频的世界里,一般使用YUV作为压缩存储格式。有时候面试官会问:为什么视频使用YUV来压缩存储,而不用RGB?YUV与RGB有什么区别,两者如何转换的?常见的RGB格式有哪些,常见的YUV格式又有哪些?手机摄像头的预览格式是什么,如何转换为YUV420P的?我们带着这些问题,来揭开RGB与YUV格式的面纱。

目录

一、RGB格式

1、RGBA8888

2、RGB565

3、图像的像素阵列

二、YUV格式

1、YUV420p

2、YUV420sp

3、NV21

三、RGB与YUV转换

四、NV21转换为YUV420p

五、YUV旋转


一、RGB格式

RGB是一种图像存储格式,也是三原色,取值范围[0, 255]。R代表Red红色,G代表Green绿色,B代表Blue蓝色。在openCV中,一般使用BGR格式。在图像中,一般使用32位色的ARGB(或RGBA)代表一个像素,其中A代表Alpha透明度。常见的RGB格式有RGB888、RGBA8888、RGB565等。

1、RGBA8888

关于RGBA8888格式,每个通道占8位,即一个字节。四个通道构成一个像素,总共占32位。排列顺序如下图所示:

2、RGB565

关于RGB565格式,其中R占5位,G占6位,B占5位。三个通道构成一个像素,总共占16位。排列顺序如下图所示:

3、图像的像素阵列

一张图像由宽x高的像素阵列构成,为了内存对齐,会使用stride来填充。如下图所示,由4x3构成的像素阵列,其中P代表pixel:

二、YUV格式

YUV是一种视频压缩存储格式。其中Y代表Luma亮度,U代表Chroma色度,V代表Contrast对比度。常见的YUV采样比例如下:

  • 4:4:4 表示完全采样
  • 4:2:2 表示水平2:1采样,垂直完全采样
  • 4:2:0 表示水平2:1采样,垂直2:1采样
  • 4:1:1 表示水平4:1采样,垂直完全采样

常见的YUV格式有:YUV420p、YUV420sp、NV21等。由于U和V分量都是Y分量的1/4,而RGB888的所有分量占比都是1。进一步可得,YUV整体占比是3/2,RGB整体占比是6/2,YUV所占存储空间比RGB少了3/2。因此,默认采用YUV作为视频压缩存储格式。

1、YUV420p

YUV420p属于平面存储,YUV分量占比为4:1:1,即每4个Y共享一组UV。先是Y分量,然后是U分量,最后是V分量。排列如下图所示:

2、YUV420sp

YUV420sp属于交错存储,YUV分量占比为4:1:1,即每4个Y共享一组UV。先是Y分量,然后是UV分量交错存储。排列如下图所示:

3、NV21

NV21属于交错存储,YUV分量占比为4:1:1,即每4个Y共享一组UV。Android手机摄像头预览数据默认是NV21格式。和YUV420sp的区别是,NV21是VUVU这样排列,如下图所示:

三、RGB与YUV转换

关于YUV与RGB的转换公式,可参考ITU标准:https://www.itu.int/rec/R-REC-BT.601。也可以参考维基百科:https://zh.wikipedia.org/wiki/YUV。咱们来看下转换公式:

以rgb转yuv为例,示例代码如下:

void rgb_to_yuv(int8_t *yuv, int *rgb, int width, int height) {int rgbIndex = 0;int yIndex = 0;int uIndex = width * height;int vIndex = width * height * 5 / 4;int R, G, B;float Y, U, V;// 遍历图像,获取所有像素点 for (int i = 0; i < height; i++) {for (int j = 0; j < width; j++) {// 从像素点获取R、G、B分量R = (rgb[rgbIndex] & 0xFF0000) >> 16;G = (rgb[rgbIndex] & 0xFF00) >> 8;B = (rgb[rgbIndex] & 0xFF);// 使用公式把RGB转成YUVY = 0.299 * R + 0.587 * G + 0.114 * B;U = -0.147 * R - 0.289 * G + 0.436 * B;V = 0.615 * R - 0.515 * G - 0.100 * B;// YUV分量赋值给yuv数组yuv[yIndex++] = (int8_t)Y;if (i % 2 == 0 && j % 2 == 0) {yuv[uIndex++] = (int8_t) U;yuv[vIndex++] = (int8_t) V;}rgbIndex++;}}
}

四、NV21转换为YUV420p

由于NV21是交错存储,4个Y共享一组UV,而且是VUVU这样排列。所以,我们需要把偶数的V分量、奇数的U分量读出来,然后赋值给YUV420p。代码如下:

static void nv21_to_yuv420p(int8_t *dst, int8_t *src, int len) {memcpy(dst, src, len); // yfor (int i = 0; i < len / 4; ++i) {*(dst + len + i) = *(src + len + i * 2 + 1);  // u*(dst + len * 5 / 4 + i) = *(src + len + i * 2); // v}
}

五、YUV旋转

YUV的存储是有旋转角度的存在。在手机拍摄时,按照逆时针来看,横屏向左是0度,竖屏向下是90度,横屏向右是180度,竖屏向上是270度。既然有旋转角度,我们就需要对YUV进行旋转处理,代码如下:

static void yuv420p_rotate90(int8_t *dst, const int8_t *src, int width, int height) {int n = 0;int wh = width * height;int half_width = width / 2;int half_height = height / 2;// yfor (int j = 0; j < width; j++) {for (int i = height - 1; i >= 0; i--) {dst[n++] = src[width * i + j];}}// ufor (int i = 0; i < half_width; i++) {for (int j = 1; j <= half_height; j++) {dst[n++] = src[wh + ((half_height - j) * half_width + i)];}}// vfor (int i = 0; i < half_width; i++) {for (int j = 1; j <= half_height; j++) {dst[n++] = src[wh + wh / 4 + ((half_height - j) * half_width + i)];}}
}static void yuv420p_rotate180(int8_t *dst, const int8_t *src, int width, int height) {int n = 0;int half_width = width / 2;int half_height = height / 2;// yfor (int j = height - 1; j >= 0; j--) {for (int i = width; i > 0; i--) {dst[n++] = src[width * j + i - 1];}}// uint offset = width * height;for (int j = half_height - 1; j >= 0; j--) {for (int i = half_width; i > 0; i--) {dst[n++] = src[offset + half_width * j + i - 1];}}// voffset += half_width * half_height;for (int j = half_height - 1; j >= 0; j--) {for (int i = half_width; i > 0; i--) {dst[n++] = src[offset + half_width * j + i - 1];}}
}static void yuv420p_rotate270(int8_t *dst, const int8_t *src, int width, int height) {for (int j = 0; j < width; j++) {for (int i = 1; i <= height; i++) {*dst++ = *(src + i * width - j);}}auto *src_u = const_cast<int8_t *>(src + width * height);for (int j = 0; j < width / 2; j++) {for (int i = 1; i <= height / 2; i++) {*dst++ = *(src_u + i * width / 2 - j);}}auto *src_v = const_cast<int8_t *>(src + width * height * 5 / 4);for (int j = 0; j < width / 2; j++) {for (int i = 1; i <= height / 2; i++) {*dst++ = *(src_v + i * width / 2 - j);}}
}static void yuv420p_rotate(int8_t *dst, int8_t *src, int width, int height, int degree) {switch(degree) {case 0:memcpy(dst, src, width * height * 3 / 2);break;case 90:yuv420p_rotate90(dst, src, width, height);break;case 180:yuv420p_rotate180(dst, src, width, height);break;case 270:yuv420p_rotate270(dst, src, width, height);break;default:break;}
}

走进音视频的世界——RGB与YUV格式相关推荐

  1. 走进音视频的世界——Matroska封装格式的介绍(二)

    Matroska封装格式非常灵活.兼容性好,既适用于本地文件存储又可以进行实时流传输.本篇文章主要探讨Matroska的编解码器映射,如何封装视频流.音频流.字幕流.如果要Matroska的介绍.功能 ...

  2. 走进音视频的世界——音频封装格式

    音频封装格式一般由:多媒体信息+音频流+封面流+歌词流组成.有些音乐会包含封面和歌词,则对应有封面流.歌词流.多媒体信息包括:标题.艺术家.专辑.作曲.音乐风格.日期.码率.时长.声道布局.采样率.音 ...

  3. 走进音视频的世界——音视频解码

    音视频文件是经过编码.封装而成的.那么反过来,要播放音视频文件,首先得解封装.解码.上一篇博客讨论到音视频编码:走进音视频的世界--音视频编码,我们来个上下呼应,本文与大家探讨一下音视频解码.本质上, ...

  4. 走进音视频的世界——剖析exo播放器架构

    ExoPlayer是Google开源的一款播放器,基于Android平台的可扩展多媒体播放器,支持HLS流.Smooth Streaming流.Dash流,支持扩展FFmpeg.Vpx.Av1.Fla ...

  5. 走进音视频的世界——音视频编码

    音视频流是通过特定编码器压缩,由一系列的压缩图像/语音帧组成.当然可能存在多种语言多音轨,每个音轨之间的音频流相互独立.还可能存在内置字幕,常见的字幕格式有sub.smi.ssa.srt等.但是,本篇 ...

  6. 走进音视频的世界——音视频的基本概念

    音视频通用的基本概念有码率.时长,而不同音视频有不同的封装格式.编码协议.其中视频关键参数有分辨率.帧率.画质.旋转角度.像素格式,而音频关键参数有采样率.声道数.声道布局.音质.采样数.采样位数.帧 ...

  7. 走进音视频的世界——视频封装格式

    音视频的时长怎么获取,音视频的封面怎么获取,音视频的格式怎么获取呢?这些信息都以特定格式存储在文件开头或者结尾,称为多媒体信息或者多媒体元数据.通用的封装格式由:文件标识头+多媒体信息+音视频(字幕) ...

  8. 走进音视频的世界——杜比视界Dolby Vision与HDR

    Dolby Vision(杜比视界)是杜比实验室推出的影像画质技术,具有更宽的色域和高动态范围HDR,亮度.色度和对比度更加逼真,从而使得整体图像更加生动.图像的明亮部分可以变得更亮,因此图像似乎具有 ...

  9. 走进音视频的世界——Matroska封装格式的介绍(一)

    Matroska是一个开放标准项目,基于EBML(Extensible Binary Meta Language 可扩展的二进制元语言),旨在成为多媒体格式容器的标准.EBML与XML结构有点类似,R ...

最新文章

  1. render函数和redirect函数的区别+反向解析
  2. 小评 XenServer 6.0功能
  3. [Poi2010]Antisymmetry
  4. 揭秘:美国国防部用什么样的操作系统
  5. Building COM Objects in C#
  6. linux云自动化运维基础知识23(DDNS服务配置)
  7. 大文本存mysql怎么建索引_如何正确合理的建立MYSQL数据库索引
  8. 64ubuntu编译32位程序
  9. C/C++ 面试题记录
  10. R 读取excel的方法
  11. 用php 用拼出一个菱形_这可是我没来过的杭州呀!远在开封的他,用一种特殊的方式,拼出一个彩色杭州...
  12. 使用SOCKET TCP
  13. 无法通过ip地址连接其它电脑的数据库,但是又可以ping通,错的不是配置,而是差了一个步骤
  14. TypeScript 素描 - 类
  15. oracle odi 目标数据存储: 临时目标数据存储未与连接关联,ODI知识模块--IKM Oracle Incremental Update...
  16. Android基础入门教程——9.1 使用SoundPool播放音效(Duang~)
  17. 电路布线问题的动态规划实现(java)
  18. 解决阿里云oss 图片跨域问题
  19. goodFeaturesToTrack——Shi-Tomasi角点检测
  20. 陈艾盐:春燕百集访谈节目第二十三集

热门文章

  1. 【新版12306抢票】python自动识别图片并登录12306,实现全自动抢票源代码-分享
  2. LWN:DVB与头文件和用户空间的regression!
  3. npm 和 npx 有什么区别?
  4. HttpPostedFile类 HttpPostedFile转字节bate
  5. 【2022研电赛】兆易创新杯全国二等奖:自动驾驶汽车路面目标智能检测系统
  6. 大学英语(第六册)复习(原文及全文翻译)——Unit 10 - Debating The Unknowable(探索未知世界)
  7. 3dMax 树的立体贴图
  8. python函数后面的-表示什么
  9. lcl手术和飞秒区别_主流近视手术大解密!一文读懂全飞秒和ICL的区别
  10. the default discovery settings are unsuitable for production use; at least one of [discovery.seed_h