(一个失败的程序)C++实现将一张彩色bmp格式图片转换成yuv4:2:0格式

声明:以下内容均属于自己理解,不保证正确性

一、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;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值