YUV和RGB都是一种颜色空间编码,要在这两种颜色空间之间进行转换,当然得知道它们的内存布局情况。下面先简单说下这两种颜色空间的编码情况。
RGB颜色空间应该是我们比较熟悉的了,分别是Red,Green,Blue三基色,每个分量占用一个字节,取值为0-255,三个0为黑,三个255为白,每三个字节为一个像素,当然在我们很多时候的处理中,可能会加入alpha通道,表示透明度,组合起来就是rgba四个通道。
YUV有两大类,平面格式和打包格式,具体这两种格式有什么区别呢,只有知道了具体的存储格式,我们才能对各个像素进行处理。平面格式就是把YUV的三个分量Y,U,V分开存放,例如放在三个数组中;而打包格式是把YUV的三个分量都放在一起,如按照一定的编码顺序存放在一个数组中。就这里我们说的YUYV格式来说,它就是一种打包格式,在内存布局中的具体方式是这样,YUYV YUYV……,每个分量占用一个字节,每两个字节也就是16位为一个像素,每两个像素一组就是一个巨像素,我们看到每两个像素也就是一个巨像素中有两个Y分量一个UV分量,这是因为YUYV是以4:2:2的格式打包的。其他yuv格式如下,注意:YUY2到Y211都是打包格式,而IF09到YVU9都是平面格式。关于yuv的详细介绍请看这里。
上述的这些格式的处理方式都和YUYV的处理方式类似,只是它们的打包格式不同,从而YUV分量在内存中的具体编码方式也就不同,如果在看YUV的详细介绍时对所列举的各个采集方式不是很明白,可以自己动手试着画些草图或者是YUV的布局方式来帮助自己理解。它们不同的采集方式将会对应不同的处理方式,下面主要是对YUYV格式为例来做介绍:
我们先设想一张图片,它具有长和高两个变量,首先明确一点,在我们对图片进行处理转换时,这两个变量width和height是不会变得,也就是说一张RGB图像和一张YUV图像它们的长和高都是一样的,这一点对我们后续的处理很重要。同时它的长和高都是以像素点为基本单位,所以总的像素就是这张图的面积width*height,既然width和height都不变,那么一张图片的总像素点肯定就不会变了,所以图片的一行所占用的内存就为: width*(一个像素点占用的字节数), 对这些有个明确的概念后,我们处理起来就容易多了。一张RGB24图像不考虑alpha通道,那么它一个像素占用三个字节,考虑alpha通道RGB32,那么一个像素就占用四个字节。
对于YUV格式的图像来说,降低的只是它的色度采样率,也就是它的UV分量,而亮度分量Y是没有降低的,这里就不再做具体的说明。例如,4:4:4的采样方式是一个Y分量对应一个U和一个V分量,并没有降低;4:2:2是两个Y共用一个UV分量;4:2:0是四个Y共用一个UV分量。所以它的像素点数仍然没有变得。如,YUYV格式16位一个像素,一张图片占用的总内存为: width*height*2,其他格式内存占用情况可以看这里。
下面对YUYV码流进行提取单帧处理,现在我们应该很清楚的知道了,YUYV格式单帧图片所占用的内存应该就为width*height*2,yuv格式和rgb之间的转换都是有相应的公式的,网上一找很多的讲解,这里也不再介绍了。
下面是yuyv转rgb24的函数:
int convert_yuv_to_rgb_pixel(int y, int u, int v)
{
unsigned int pixel32 = 0;
unsigned char *pixel = (unsigned char *)&pixel32;
int r, g, b;
r = y + (1.370705 * (v-128));
g = y - (0.698001 * (v-128)) - (0.337633 * (u-128));
b = y + (1.732446 * (u-128));
if(r > 255) r = 255;
if(g > 255) g = 255;
if(b > 255) b = 255;
if(r < 0) r = 0;
if(g < 0) g = 0;
if(b < 0) b = 0;
pixel[0] = r * 220 / 256;
pixel[1] = g * 220 / 256;
pixel[2] = b * 220 / 256;
return pixel32;
}
int convert_yuv_to_rgb_buffer(unsigned char *yuv, unsigned char *rgb, unsigned int width, unsigned int height)
{
unsigned int in, out = 0;
unsigned int pixel_16;
unsigned char pixel_24[3];
unsigned int pixel32;
int y0, u, y1, v;
for(in = 0; in < width * height * 2; in += 4) {
pixel_16 =
yuv[in + 3] << 24 |
yuv[in + 2] << 16 |
yuv[in + 1] << 8 |
yuv[in + 0];
y0 = (pixel_16 & 0x000000ff);
u = (pixel_16 & 0x0000ff00) >> 8;
y1 = (pixel_16 & 0x00ff0000) >> 16;
v = (pixel_16 & 0xff000000) >> 24;
pixel32 = convert_yuv_to_rgb_pixel(y0, u, v);
pixel_24[0] = (pixel32 & 0x000000ff);
pixel_24[1] = (pixel32 & 0x0000ff00) >> 8;
pixel_24[2] = (pixel32 & 0x00ff0000) >> 16;
rgb[out++] = pixel_24[0];
rgb[out++] = pixel_24[1];
rgb[out++] = pixel_24[2];
pixel32 = convert_yuv_to_rgb_pixel(y1, u, v);
pixel_24[0] = (pixel32 & 0x000000ff);
pixel_24[1] = (pixel32 & 0x0000ff00) >> 8;
pixel_24[2] = (pixel32 & 0x00ff0000) >> 16;
rgb[out++] = pixel_24[0];
rgb[out++] = pixel_24[1];
rgb[out++] = pixel_24[2];
}
return 0;
}
//因为只是简单的测试用,所以代码写的有点随意
QFile file("G:/Documents/QtProject/yuyvSplit/flower_yuyv422_352X288.yuv");
if(!file.open(QIODevice::ReadOnly))
{
qDebug() << "open file failed.";
return;
}
int width = 352;
int height = 288;
int frames = 250; //yuyv码流总的帧数
int singleCounts = width * height * 2;
int yuyvCounts = singleCounts * 250;
int yuyvPitch = width * 2; //yuyv 缓存一行的大小
int rgb24Size = width * height * 3;
int rgb32Size = width * height * 4;
int rgb32Pitch = width * 4; //rgb32 缓存一行的大小
int rgb24Pitch = width * 3;
qDebug() << "yuyvCounts: " << yuyvCounts << "\nfileSize: " << file.size();
for(int i = 0; i < 1; ++i )
{
char *singleFrame = new char[singleCounts]();
int ret = file.read(singleFrame, singleCounts);
qDebug() << "read return size: " << ret << " signleCounts: " << singleCounts;
char *rgb24 = new char[rgb24Size]();
convert_yuv_to_rgb_buffer((unsigned char*)singleFrame, (unsigned char*)rgb24, width, height);
//不用QT的话,这里可以直接把图片rgb24buffer写入文件image.bmp中,
//注意保存格式,bmp是位图,而类似jpg,png之类的是经压缩过的。
QImage *img = new QImage((unsigned char*)rgb24, width, height, rgb24Pitch, QImage::Format_RGB888);
//这里把图像呈现出来观察
ui->image->setPixmap(QPixmap::fromImage(*img));
delete[] singleFrame;
delete[] rgb32;
}
file.close();