走进音视频的世界——RGB与YUV格式
在图像的世界里,一般使用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格式相关推荐
- 走进音视频的世界——Matroska封装格式的介绍(二)
Matroska封装格式非常灵活.兼容性好,既适用于本地文件存储又可以进行实时流传输.本篇文章主要探讨Matroska的编解码器映射,如何封装视频流.音频流.字幕流.如果要Matroska的介绍.功能 ...
- 走进音视频的世界——音频封装格式
音频封装格式一般由:多媒体信息+音频流+封面流+歌词流组成.有些音乐会包含封面和歌词,则对应有封面流.歌词流.多媒体信息包括:标题.艺术家.专辑.作曲.音乐风格.日期.码率.时长.声道布局.采样率.音 ...
- 走进音视频的世界——音视频解码
音视频文件是经过编码.封装而成的.那么反过来,要播放音视频文件,首先得解封装.解码.上一篇博客讨论到音视频编码:走进音视频的世界--音视频编码,我们来个上下呼应,本文与大家探讨一下音视频解码.本质上, ...
- 走进音视频的世界——剖析exo播放器架构
ExoPlayer是Google开源的一款播放器,基于Android平台的可扩展多媒体播放器,支持HLS流.Smooth Streaming流.Dash流,支持扩展FFmpeg.Vpx.Av1.Fla ...
- 走进音视频的世界——音视频编码
音视频流是通过特定编码器压缩,由一系列的压缩图像/语音帧组成.当然可能存在多种语言多音轨,每个音轨之间的音频流相互独立.还可能存在内置字幕,常见的字幕格式有sub.smi.ssa.srt等.但是,本篇 ...
- 走进音视频的世界——音视频的基本概念
音视频通用的基本概念有码率.时长,而不同音视频有不同的封装格式.编码协议.其中视频关键参数有分辨率.帧率.画质.旋转角度.像素格式,而音频关键参数有采样率.声道数.声道布局.音质.采样数.采样位数.帧 ...
- 走进音视频的世界——视频封装格式
音视频的时长怎么获取,音视频的封面怎么获取,音视频的格式怎么获取呢?这些信息都以特定格式存储在文件开头或者结尾,称为多媒体信息或者多媒体元数据.通用的封装格式由:文件标识头+多媒体信息+音视频(字幕) ...
- 走进音视频的世界——杜比视界Dolby Vision与HDR
Dolby Vision(杜比视界)是杜比实验室推出的影像画质技术,具有更宽的色域和高动态范围HDR,亮度.色度和对比度更加逼真,从而使得整体图像更加生动.图像的明亮部分可以变得更亮,因此图像似乎具有 ...
- 走进音视频的世界——Matroska封装格式的介绍(一)
Matroska是一个开放标准项目,基于EBML(Extensible Binary Meta Language 可扩展的二进制元语言),旨在成为多媒体格式容器的标准.EBML与XML结构有点类似,R ...
最新文章
- render函数和redirect函数的区别+反向解析
- 小评 XenServer 6.0功能
- [Poi2010]Antisymmetry
- 揭秘:美国国防部用什么样的操作系统
- Building COM Objects in C#
- linux云自动化运维基础知识23(DDNS服务配置)
- 大文本存mysql怎么建索引_如何正确合理的建立MYSQL数据库索引
- 64ubuntu编译32位程序
- C/C++ 面试题记录
- R 读取excel的方法
- 用php 用拼出一个菱形_这可是我没来过的杭州呀!远在开封的他,用一种特殊的方式,拼出一个彩色杭州...
- 使用SOCKET TCP
- 无法通过ip地址连接其它电脑的数据库,但是又可以ping通,错的不是配置,而是差了一个步骤
- TypeScript 素描 - 类
- oracle odi 目标数据存储: 临时目标数据存储未与连接关联,ODI知识模块--IKM Oracle Incremental Update...
- Android基础入门教程——9.1 使用SoundPool播放音效(Duang~)
- 电路布线问题的动态规划实现(java)
- 解决阿里云oss 图片跨域问题
- goodFeaturesToTrack——Shi-Tomasi角点检测
- 陈艾盐:春燕百集访谈节目第二十三集
热门文章
- 【新版12306抢票】python自动识别图片并登录12306,实现全自动抢票源代码-分享
- LWN:DVB与头文件和用户空间的regression!
- npm 和 npx 有什么区别?
- HttpPostedFile类 HttpPostedFile转字节bate
- 【2022研电赛】兆易创新杯全国二等奖:自动驾驶汽车路面目标智能检测系统
- 大学英语(第六册)复习(原文及全文翻译)——Unit 10 - Debating The Unknowable(探索未知世界)
- 3dMax 树的立体贴图
- python函数后面的-表示什么
- lcl手术和飞秒区别_主流近视手术大解密!一文读懂全飞秒和ICL的区别
- the default discovery settings are unsuitable for production use; at least one of [discovery.seed_h