声明:以下内容均属于自己理解,不保证正确性
一、bmp图片的获取
从网上找到一张任意格式的图片,再用Windows系统自带的画图软件打开(在命令行输入mspaint即可快速打开画图),将图片保存成bmp24位图格式即可。注意在本程序中bmp图片的行数和列数都必须为偶数,否则可能会导致最终生成的Cb和Cr分量的个数不一样。
二、流程
代码过程大致分为以下几步:
1.以只读文件形式打开bmp图片,并获取bmp头到数据位置的偏移(单位为字节),以及该图片的宽和高(单位为像素)。
2.使用fseek函数将文件指针定位到bmp数据位置。(实际省略了这一步)。
3.根据图片的宽和高建立Y、Cb、Cr缓冲区指针,并为其分配内存。(本程序中用queue容器代替了指针)。
4.定义一个函数,一次可以从bmp文件中读取一个像素的数据,由于是24位的位图文件,所以也就是一次读取3个字节,按照先后顺序,这三个字节的内容分别代表B、G、R的分量值。
5.接下来依次读取bmp的每个像素,对每个像素进行RGB到YCbCr的转换,并按照4:2:0的格式采样,将采样后的数据保存在相应的缓冲区中。下面以一个两行四列的像素块举例,每个小方格代表一个像素如图:
由于是24位的位图,因此上图中的每个像素中都有R、G、B三个分量,经过变换后可以得到Y、Cb、Cr三个分量的值。由于要按照4:2:0进行采样,因此对第一行的第一个像素,既对Y分量采样,也对Cb分量采样;对第一行的第二个像素,只对Y分量采样;第一行的第三个像素,既对Y分量采样,也对Cb分量采样;第一行的第四个像素,只对Y分量采样。对于第二行,第一个像素和第三个像素仅对Y分量采样;第二个和第四个像素对Y分量和Cr分量采样。
因此可以构建两层for循环,伪代码如下:
for(int I = 1; I <= 行数; i++)
for(int j = 1; j<= 列数; j++)
{
if(I == 奇数 && j == 奇数)
奇数行奇数列就将Y和Cb存入缓冲区
if(I == 奇数 && j == 偶数)
奇数行偶数列将Y存入缓冲区
if(I == 偶数 && j == 奇数)
偶数行奇数列将Y存入缓冲区
if(I == 偶数 && j == 偶数)
偶数行偶数列就将Y和Cr存入缓冲区
}
6.关闭bmp文件指针,以只写方式打开一个yuv格式文件,将上面的Y、Cb、Cr缓冲区中的数据一次写入文件,结束。
三、结果
我自己的bmp图片如下,长为500像素,宽为322像素:
程序可以运行,也产生了一个可以观察的yuv格式的文件,但是效果不太令人满意,有点鬼畜。。。
与原始bmp图片的区别是:1.图片倒置
2.颜色也不一致
3.清晰度降低
目前还在思考为什么会出现这样的情况,而且在调试的时候,我还发现了YCbCr分量的像素值竟然出现了负数,理论上值应该在0-255之间才对吧。。。不知道错在哪里。以后发现了再改。
最后附上我自己的程序代码,哪位大佬有兴趣可以帮我看一下哪里出了问题,多谢!
#include<iostream>
#include<stdlib.h>
#include<queue>
#include<Windows.h>
typedef struct yuvPixel
{
char temp_Y;
char temp_Cb;
char temp_Cr;
}yuvPixel;
void read_from_bmpFile_in_pixel(FILE* fp, yuvPixel& temp_yuvPixel);
int main(void)
{
errno_t err;
FILE* bmpFile;
if (err = fopen_s(&bmpFile, "001.bmp", "rb"))
{
std::cout << "Open failed." << std::endl;
exit(-1);
}
//获取bmp图片的长和宽
int width = 0;
int height = 0;
BITMAPFILEHEADER bmpFileHeader;
BITMAPINFOHEADER bmpInfoHeader;
fread(&bmpFileHeader, sizeof(BITMAPFILEHEADER), 1, bmpFile);//这一句不能少,否则无法让文件指针偏移到bmp数据位置
fread(&bmpInfoHeader, sizeof(BITMAPINFOHEADER), 1, bmpFile);
width = bmpInfoHeader.biWidth;
height = bmpInfoHeader.biHeight;
//定义Y、Cb、Cr的队列缓冲区
std::queue<char> Y;
std::queue<char> Cb;
std::queue<char> Cr;
//定义一个临时像素
yuvPixel temp_yuvPixel;
//读取bmp中的像素值到YCbCr的缓冲区中
for(int i=1;i<=height;i++)//遍历每行
for (int j = 1; j <= width; j++)//遍历每列
{
read_from_bmpFile_in_pixel(bmpFile, temp_yuvPixel);
if ((i % 2 == 1) && (j % 2 == 1))
{
Y.push(temp_yuvPixel.temp_Y);
Cb.push(temp_yuvPixel.temp_Cb);
}
if ((i % 2 == 1) && (j % 2 == 0))
{
Y.push(temp_yuvPixel.temp_Y);
}
if ((i % 2 == 0) && (j % 2 == 1))
{
Y.push(temp_yuvPixel.temp_Y);
}
if ((i % 2 == 0) && (j % 2 == 0))
{
Y.push(temp_yuvPixel.temp_Y);
Cr.push(temp_yuvPixel.temp_Cr);
}
}
fclose(bmpFile);
bmpFile = NULL;
FILE* yuvFile;
if (err = fopen_s(&yuvFile, "001.yuv", "wb"))
{
std::cout << "Open failed." << std::endl;
exit(-1);
}
//依次将YCbCr分量的值写入到文件中
for (int i = 0; i < width*height; i++)
{
fputc(Y.front(),yuvFile);
Y.pop();
}
for (int i = 0; i < width*height / 4; i++)
{
fputc(Cb.front(), yuvFile);
Cb.pop();
}
for (int i = 0; i < width*height / 4; i++)
{
fputc(Cr.front(), yuvFile);
Cr.pop();
}
fclose(yuvFile);
yuvFile = NULL;
return 0;
}
void read_from_bmpFile_in_pixel(FILE* fp, yuvPixel& temp_yuvPixel)
{
//这个函数的功能是从bmp图片中读取一个像素,并解析出该像素中的Y、Cb、Cr的值
/*
这里的RGB空间向YCbCr颜色空间的转换公式,我用的是万帅、杨付正的《新一代高效视频编码H.265/HEVC:原理标准与实现》第28页
Y = 0.299*R + 0.587*G + 0.114* B;
Cb = -0.169*R - 0.331*G + 0.499*B + 128;
Cr = 0.499*R - 0.418*G - 0.0813*B + 128;
*/
char B = fgetc(fp);
char G = fgetc(fp);
char R = fgetc(fp);
float temp_Y = 0.299*R + 0.587*G + 0.114* B;
float temp_Cb = -0.169*R - 0.331*G + 0.499*B + 128;
float temp_Cr = 0.499*R - 0.418*G - 0.0813*B + 128;
temp_yuvPixel.temp_Y = temp_Y;
temp_yuvPixel.temp_Cb = temp_Cb;
temp_yuvPixel.temp_Cr = temp_Cr;
}