【C++音视频开发】视频篇 | RGB与YUV

前言 

        本专栏将不间断更新有关C++音视频开发的内容,其中有初级章、中级章与高级章的内容,包括但不限于音视频基础、FFmpeg实战、QT、流媒体客户端、流媒体服务器、WebRTC实战、Android NDK等等。是博主花了将近5000元购买的课程中的知识点,其中也掺杂着一些我的个人理解,希望能帮助大家和我一起入门音视频开发

        ------本专栏拒不收费!


【特别说明:本文属于初级篇中的视频基础知识的子内容。】

初级篇目录http://t.csdn.cn/rB3TA
其余篇章更新中...


1.RGB

1.1 RGB的表示

 1.1.1  RGB24  

1.1.2 Packed和Planar:

1.1.3 bbp像素深度 

1.1.4 透明通道 

1.2 RGB的排列

2.YUV

2.1 YUV概念 

2.2 YUV采样表示法

2.3 YUV数据存储  

2.3.1——4:4:4格式 

2.3.2 ——4:2:2格式​编辑

2.3.3——4:2:0格式(应用最广泛的格式,播放器必须支持)

3、RGB与YUV的转换

3、1 YUV转RGB:

3、2 RGB24转YUV420P

【参考文献】


1.RGB


1.1 RGB的表示

        理解图像RGB的表示,只需要能看懂一下的两种表达方式即可。

 1.1.1  RGB24  

        RGB24是什么意思呢?其实是R分量G分量B分量都是8bit,加在一起是24bit,所以叫RGB24。

1.1.2 Packed和Planar:

        packed是图像存储策略的意思:

Packed:每个像素在内存中是连续存储的。如果每个像素有16bit,则每个像素在内存中是用两个连续的8位字节存储。如果每个像素有4bit,则每两个像素在内存中是用一个字节存储,每半个字节一个像素。如果一个像素有多个通道,则通道在内存中是相互交错的,如有三个通道的RGB:

 Planar:每个像素的组成,不是连续存储在一起的。而是分割在内存不同的位平面里。如果像素是单通道,也可以采用Planar,将单个像素的bit组成,分布在不同的平面。如果像素是多通道的,通常是将每个通道保存在不同的平面(平面可以理解为不同的内存块),如RGB,如果采用Planar:

Packed与Planar的优劣

Packed的优势当然是在于更节省内存空间。

Planar的优势:

  • 可以并行访问像素数据。在内存带宽不足的时候,Planar就明显优于Packed了。如YUV,如果采用Planar,相比于Packed,可以并行访问Y、U、V三个平面,那就相当于只花1/3的时间,就可以访问到一个YUV像素。
  • Planar在切换bit depth时,更加快:可以通过增加或丢弃平面,来快速扩增或缩减调色板。比如,4个平面变成5个平面时,一个像素的可选颜色,变成了25 = 32种。
  • 当代表像素的bit数,不是2的次幂的时候,Planar在空间和时间上的效率,都高于Packed。比如,在3-bit的RGB中(每3个bit表示一个像素,一个像素可选颜色为23 = 8种)。
    • 如果采用Planar,只需要3个平面。
    • 如果采用Packed,有两种实现方式:
      • 允许像素跨字节边界:提高了内存寻址和unpack像素的复杂度,导致耗时增加。
      • 采用padding:每个字节只存储两个像素,耗费6个bit,保留两个2bit不使用,导致空间消耗增大。RGB555就是这种做法。

1.1.3 bbp像素深度 

        bbp是像素深度的意思,即存储每个像素所用的位数。

        一个像素所能表达的不同颜色数取决于比特每像素(BPP)。这个最大数可以通过取二的色彩深度次幂来得到。例如,常见的取值有 :

         8 bpp [2^8=256;(256色)];

        16 bpp [2^16=65536; (65,536色,称为高彩色)];

        24 bpp [2^24=16777216;(16,777,216色,称为真彩色)];

        48 bpp [248=281474976710656;

1.1.4 透明通道 

         Alpha通道是计算机图形学中的术语,指的是特别的通道,意思是“非彩色”通道,主要是用来保存选区和编辑选区。对于有透明通道的RGB表示,需要在最前面或者最后面加上A符号,表示alpha通道。

        为什么用‘Alpha’代表透明度?

        Alpha 没有透明度的意思,不代表透明度。opacity 和 transparency 才和透明度有关,前者是不透明度,后者是透明度。比如 css 中的「opacity: 0.5」就是设定元素有 50% 的不透明度。后来 Alvy Ray Smith 提出每个像素再增加一个 Alpha 通道,取值为0到1,用来储存这个像素是否对图片有「贡献」,0代表透明、1代表不透明。也就是说,「Alpha 通道」储存一个值,其外在表现是「透明度」,Alpha 和透明度没啥关系。

为什么取名为 Alpha 通道,我觉得是因为这是除RGB以外「第一个通道」的意思,没有别的更深刻的含义。

「Alpha 通道」是图片内在的一个属性,用 css 或者其他外部方法设定透明度,并没有改变图片的 Alpha 通道的值。

阿尔法通道(α Channel或Alpha Channel)是指一张图片的透明和半透明度。例如:一个使用每个像素16比特存储的位图,对于图形中的每一个像素而言,可能以5个比特表示红色,5个比特表示绿色,5个比特表示蓝色,最后一个比特是阿尔法。在这种情况下,它要么表示透明要么不是,因为阿尔法比特只有0或1两种不同表示的可能性。又如一个使用32个比特存储的位图,每8个比特表示红绿蓝,和阿尔法通道。在这种情况下,就不光可以表示透明还是不透明,阿尔法通道还可以表示256级的半透明度,因为阿尔法通道有8个比特可以有256种不同的数据表示可能性。

RGBA是代表Red(红色) Green(绿色) Blue(蓝色)和 Alpha的色彩空间。虽然它有的时候被描述为一个颜色空间,但是它其实仅仅是RGB模型的附加了额外的信息。采用的颜色是RGB,可以属于任何一种RGB颜色空间,但是Catmull和Smith在1971至1972年间提出了这个不可或缺的alpha数值,使得alpha渲染和alpha合成变得可能。提出者以alpha来命名是源于经典的线性插值方程αA + (1-α)B所用的就是这个希腊字母。(线性插值是数学、计算机图形学等领域广泛使用的一种简单插值方法)

真正让图片变透明的不是Alpha 实际是Alpha所代表的数值和其他数值做了一次运算 。比如你有一张图片你想抠出图片中间的一部分 在PS里你会建立一个蒙板 然后在蒙板里把不需要的地方填充成黑色 需要的留成白色 这个时候实际上是是做了一次乘法。用黑色所代表的数值0去乘以你所填充的地方 那么这个地方就变透明了


1.2 RGB的排列

        我们前面已经讲过了RGB色彩表示,这里我们重点讲解RGB的排列。

        通常图像像素是按照RGB顺序进行排列的,但有些图像处理要转换成其他排列顺序,比如我们经常使用的OpenCv就经常转换为BGR的排列方式。

         为什么需要转换为BGR这样子的排列呢?这其实是算法上的问题:

         OpenCV1999年由Intel建立,当时主流的摄像头制造商和软件供应商提供的摄像头采集的图像的通道排列顺序为BGR,另外对于图片,位图BMP是最简单的,也是Windows显示图片的基本格式,其文件扩展名为*.BMP。在Windows下,任何格式的图片文件(包括视频播放)都要转化为位图才能显示出来,各种格式的图片文件也都是在位图格式的基础上采用不同的压缩算法生成的,值得注意的是位图BMP的格式就是BGR。正是基于BGR在当时被广泛使用,于是早期OpenCV开发者就选择BGR颜色格式,这也就成为了一种规范一直用到现在。


2.YUV


2.1 YUV概念  

        ▲ 与我们熟知的RGB类似,YUV也是一种颜色编码方式,但是它是指:亮度参量(Y:Luminance或Luma)和色度参量(UV:Chrominace或Chroma)分开进行表示的像素编码格式

        ▲ 将YUV分开的好处是什么呢?这样子做可以避免互相干扰,而且没有UV信息一样可以显示完整的图像,因而解决了彩色电视与黑白电视的兼容问题;还可以降低色彩的采样率而不会对图像质量有太多的影响,降低了视屏信号传输时对频宽(带宽)的要求。

  • 总结:为什么使用YUV? 显示器从黑白显示器演变过来的,为了和以前格式兼容以及YUV存储的数据比RGB要少很多

         

         ▲ YUV是一种比较笼统的说法,针对它的具体排列方式,可以分为很多种具体的格式:

                - 打包(packed)格式:将每个像素点Y、U、V分量交叉排列并以像素点为单元连续存放在同一数组中,通常几个相邻的像素点组成一个宏像素(macro-pixel)

                - 平面(planar)格式:使用三个数组分开连续的存放Y、U、V三个分量,即Y、U、V分别存放在各自的数组中。

2.2 YUV采样表示法

         ▲ YUV采用A:B:C表示法来描述Y,U,V采样频率比例,下图中黑点表示采样像素点Y分量,空心圆表示采样像素点的UV分量。主要分为YUV 4:4:4 、YUV 4:2:2、YUV 4:2:0,这几种常用的类型。

        ★ 4:4:4 表示色度频道没有下采样,即一个Y分量对应着一个U分量和一个V分量。

        ★ 4:2:2 表示2:1的水平下采样,没有垂直下采样,即每两个Y分量共用一个U分量和一个V分量。

        ★ 4:2:0 表示2:1的水平下采样,2:1的垂直下采样,即每四个Y分量公用一个U分量和一个V分量。(应用最广泛的格式,播放器必须支持
        重要:4:2:0并不意味着只有Y、Cb两个分量,而没有Cr分量。它实际指的是每行扫描线来说,只有一种色度分量,它以2:1的抽样率存储。相邻的扫描行存储不同的色度分量,也就是说,如果一行是4:2:0的话,下一行就是4:0:2,再下一行是4:2:0…以此类推

2.3 YUV数据存储

        ▲ 下面每一个分量数据存储在一

个char(或byte)中为例描述YUV的数据存储方式。

                (1)- 4:4:4 格式

                (2)- 4:2:2 格式

                (3)- 4:2:0 格式


2.3.1——4:4:4格式 


2.3.2 ——4:2:2格式


2.3.3——4:2:0格式(应用最广泛的格式,播放器必须支持

3、RGB与YUV的转换

        RGB与YUV关系:RGB用于屏幕图像的展示,YUV用于采集与编码。

3、1 YUV转RGB:

公式:

\begin{array}{l}{R=Y+1.403 \times(V-128)} \\ {G=Y-0.343 \times(U-128)-0.714 \times(V-128)} \\ {B=Y+1.770 \times(U-128)}\end{array}

3、2 RGB24转YUV420P

公式:

\begin{aligned} Y &=0.299 R+0.587 G+0.114 B \\ C_{r}=V &=0.500 R-0.419 G-0.081 B+128 \\ C_{b}=U &=-0.169 R-0.331 G+0.500 B+128 \end{aligned}


代码: 

#include <iostream>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
using namespace cv;
 
cv::Mat RGB2YUV(cv::Mat src, bool accelerate = false){
 
	CV_Assert(src.channels() == 3);
	cv::Mat dst(src.size(), CV_32FC3);  //这里最好要用浮点型,避免丧失精度
	cv::Vec3b rgb;
	int r = src.rows;
	int c = src.cols;
 
	for (int i = 0; i < r; ++i){
		for (int j = 0; j < c; ++j){
			rgb = src.at<cv::Vec3b>(i, j);
			int B = rgb[0]; int G = rgb[1]; int R = rgb[2];
			if (accelerate == false){
				dst.at<Vec3f>(i, j)[0] = R*0.299 + G*0.587 + B*0.114; //Y
				dst.at<Vec3f>(i, j)[1] = -0.169*R - 0.331*G + 0.500*B + 128; //U
				dst.at<Vec3f>(i, j)[2] = 0.500*R - 0.419*G - 0.081*B + 128;  //V
			}
			else{
				dst.at<Vec3f>(i, j)[0] = ((R << 6) + (R << 3) + (R << 2) + R + (G << 7) + (G << 4) + (G << 2) + (G << 1) + (B << 4) + (B << 3) + (B << 2) + B) >> 8; //Y
				dst.at<Vec3f>(i, j)[1] = (-((R << 5) + (R << 3) + (R << 1)+ R) - ((G << 6) + (G << 4) + (G << 2)+G) + (B << 7) + 32768) >> 8; //U
				dst.at<Vec3f>(i, j)[2] = ((R << 7) - ((G << 6) + (G << 5) + (G << 3) + (G << 3) + G) - ((B << 4) + (B << 2) + B) + 32768 )>> 8; //V
			}
		}
	}
	dst.convertTo(dst, CV_8UC3);
	return dst;
}
 
cv::Mat YUV2RGB(cv::Mat src, bool accelerate = false){
	CV_Assert(src.channels() == 3);
	cv::Mat dst(src.size(), CV_32FC3); //这里一定要用浮点型,避免丧失精度
	cv::Vec3b yuv;
	int r = src.rows;
	int c = src.cols;
	for (int i = 0; i < r; ++i){
		for (int j = 0; j < c; ++j){
			yuv = src.at<cv::Vec3b>(i, j);
			int Y = yuv[0]; int U = yuv[1]; int V = yuv[2];
			U = U - 128;
			V = V - 128;
			if (accelerate == false){
				dst.at<Vec3f>(i, j)[0] = Y + 1.770*U;//B
				dst.at<Vec3f>(i, j)[1] = Y - 0.343*U - 0.714*V;//G
				dst.at<Vec3f>(i, j)[2] = Y + 1.403*V;//R
			}
			else{
				dst.at<Vec3f>(i, j)[0] = Y + U + ((U * 198) >> 8);
				dst.at<Vec3f>(i, j)[1] = Y -((U * 88) >> 8) - ((V * 183)>>8);
				dst.at<Vec3f>(i, j)[2] = Y + V + ( (V * 103) >> 8);
			}
		}
	}
	dst.convertTo(dst, CV_8UC3);
	return dst;
}
 
 
int main(){
	cv::Mat src = cv::imread("I:\\Learning-and-Practice\\2019Change\\Image process algorithm\\Img\\4.JPG");
 
	if (src.empty()){
		return -1;
	}
	cv::Mat dst, dst1, dst2;
 
	opencv自带/
	double t2 = (double)cv::getTickCount(); //测时间
 
	cv::cvtColor(src, dst1, CV_RGB2YUV); //RGB2YUV
 
	t2 = (double)cv::getTickCount() - t2;
	double time2 = (t2 *1000.) / ((double)cv::getTickFrequency());
	std::cout << "Opencv_RGB2YUV=" << time2 << " ms. " << std::endl << std::endl;
 
	//RGB2YUV//
	double t1 = (double)cv::getTickCount(); //测时间
 
	dst = RGB2YUV(src,true); //RGB2YUV
	dst2 = YUV2RGB(dst,true); //YUV2BGR
 
	t1 = (double)cv::getTickCount() - t1;
	double time1 = (t1 *1000.) / ((double)cv::getTickFrequency());
	std::cout << "My_RGB2YUV=" << time1 << " ms. " << std::endl << std::endl;
 
 
	cv::namedWindow("src", CV_WINDOW_NORMAL);
	imshow("src", src);
	cv::namedWindow("My_RGB2YUV", CV_WINDOW_NORMAL);
	imshow("My_RGB2YUV", dst);
	cv::namedWindow("My_YUV2RGB", CV_WINDOW_NORMAL);
	imshow("My_YUV2RGB", dst2);
	cv::namedWindow("Opencv_RGB2YUV", CV_WINDOW_NORMAL);
	imshow("Opencv_RGB2YUV", dst1);
	cv::waitKey(0);
	return 0;
 
}

【参考文献】

【1】雷霄骅的博客_CSDN博客-FFMPEG,FFmpeg,视频质量评价领域博主

【2】http://t.csdn.cn/ONKF1

猜你喜欢

转载自blog.csdn.net/qq_51831335/article/details/127458157