0
点赞
收藏
分享

微信扫一扫

实验 BMP2YUV

舟海君 2022-04-07 阅读 32
c++

实验项目名称:图像文件的读写和转换

实验目的:1.理解图像文件的基本组成。2.掌握结构体作为复杂数据对象的用法。进一步熟悉由问题到程序的解决方案,并掌握编程细节:如内存分配、倒序读写、字节序、文件读写过程等。

一、BMP文件的组成结构

BMP文件可以分为两类:设备相关位图(DDB)和设备无关位图(DIB)

BMP文件的图像深度可选1bit、4bit、8bit、16bit及24bit。BMP文件存储数据是,图像的扫描方式是从左到右、从下到上的顺序。

典型的BMP图像文件由四部分组成:

(1)位图头文件数据结构——包含BMP图像文件的类型、显示内容等信息

(2)位图信息数据结构——包含BMP图像的宽、高、压缩方法以及定义颜色等信息

(3)调色板——此为可选部分,有些位图需要调色板,有些位图不需要,如真彩色图(24bit的BMP)就不需要调色板

(4)位图数据——这部分的内容根据BMP位图使用位数的不同而不同,在24位图中直接使用RGB,其他小于24位的使用调色板中颜色索引值。

紧跟在调色板之后的是图像数据字节阵列。对于用到调色板的位图,图像数据就是该像素颜色在调色板中的索引值(逻辑色)。对于真彩色图,图像数据就是实际的R、G、B值。图像的每一扫描行由表示图像像素的连续的字节组成,每一行的字节数取决于图像的颜色数目和用像素表示的图像宽度。规定每一扫描行的字节数必须是4的整倍数,也就是DWORD对齐的。扫描行是由底向上存储的,这就是说,阵列中的第一个字节表示位图左下角的像素,而最后一个字节表示位图右上角的像素。

二、实验步骤

1.打开文件BMP文件,并读取文件头,信息头

2.判断调色板

3.读取BMP文件中的RGB数据

4.RGB转YUV

5.设置帧数,并输出YUV文件

主函数按上述流程编写,其中会额外用到读取RGB数据的函数(read_rgb),RGB转YUV的函数(RGB2YUV),调色板函数(make_plattet)。分别如下:

//读取BMP文件的RGB数据
void read_rgb(FILE* pfile, const BITMAPFILEHEADER& file_h, const BITMAPINFOHEADER& info_h, unsigned char* d_out)
{
    unsigned long loop, iloop, jloop, b_w, b_h, w, h;
    unsigned char mask, * index_data, * data;
    //mask = NULL;
    if ((info_h.biWidth % 4) == 0)
        w = info_h.biWidth;
    else
        w = (info_h.biWidth * info_h.biBitCount + 31) / 32 * 4;
    if ((info_h.biHeight % 2) == 0)
        h = info_h.biHeight;
    else
        h = info_h.biHeight + 1;

    b_w = w / 8 * info_h.biBitCount;
    b_h = h;

    index_data = (unsigned char*)malloc(b_w * b_h);
    data = (unsigned char*)malloc(b_w * b_h);

    fseek(pfile, file_h.bfOffBits, 0);
    if (fread(index_data, b_w * b_h, 1, pfile) != 1)
    {
        printf("read file error\n");
        exit(0);
    }

    for (iloop = 0; iloop < b_h; iloop++)
    {
        for (jloop = 0; jloop < b_w; jloop++)
        {
            data[iloop * b_w + jloop] = index_data[(b_h - iloop - 1) * b_w + jloop];
        }
    }
    if (info_h.biBitCount == 24)
    {
        memcpy(d_out, data, b_h * b_w);
        free(index_data);
        free(data);
        return;
    }
    //判断调色板
    RGBQUAD* pRGB = (RGBQUAD*)malloc(sizeof(RGBQUAD) * (unsigned int)pow((float)2, info_h.biBitCount));
    if (!make_palette(pfile, file_h, info_h, pRGB))
        printf("no palette\n");
    if (info_h.biBitCount == 16)
    {
        for (loop = 0; loop < b_w * b_h; loop += 2)
        {
            *d_out = (data[loop] & 0x1F) << 3;
            *(d_out + 1) = ((data[loop] & 0xE0) >> 2) + ((data[loop + 1] & 0x03) << 6);
            *(d_out + 2) = (data[loop + 1] & 0x7C) << 1;
            d_out += 3;
        }
    }

    for (loop = 0; loop < b_w * b_h; loop++)
    {
        switch (info_h.biBitCount)
        {
        case1:
            mask = 0x80;
            break;
        case2:
            mask = 0xC0;
            break;
        case4:
            mask = 0xF0;
            break;
        case8:
            mask = 0xFF;
        }
        int flag = 1;
        while (mask)
        {
            unsigned char index = mask == 0xFF ? data[loop] : ((data[loop] & mask) >> (8 - flag * info_h.biBitCount));
            *d_out = pRGB[index].rgbBlue;
            *(d_out + 1) = pRGB[index].rgbGreen;
            *(d_out + 2) = pRGB[index].rgbRed;
            if (info_h.biBitCount == 8)
                mask = 0;
            else
                mask >>= info_h.biBitCount;
            if (loop == b_w * b_h - 1)
            {
                d_out = d_out + 3 - b_w * b_h * 3;
                break;
            }
            d_out += 3;
            flag++;
        }
    }
    if (index_data)
        free(index_data);
    if (data)
        free(data);
    if (pRGB)
        free(pRGB);
}
//查找表与RGB2YUV
static float RGBYUV02990[256];
static float RGBYUV05870[256];
static float RGBYUV01140[256];
static float RGBYUV01684[256];
static float RGBYUV03316[256];
static float RGBYUV04187[256];
static float RGBYUV00813[256];
//查找表
void initLookupTable()
{
    for (int i = 0; i < 256; i++)
    {
        RGBYUV02990[i] = (float)0.2990 * i;
        RGBYUV05870[i] = (float)0.5870 * i;
        RGBYUV01140[i] = (float)0.1140 * i;
        RGBYUV01684[i] = (float)0.1684 * i;
        RGBYUV03316[i] = (float)0.3316 * i;
        RGBYUV04187[i] = (float)0.4187 * i;
        RGBYUV00813[i] = (float)0.0813 * i;
    }
}

void RGB2YUV(unsigned long w, unsigned long h, unsigned char* rgb_data, unsigned char* y, unsigned char* u, unsigned char* v)
{
    initLookupTable();
    unsigned char* y_temp = NULL;
    unsigned char* u_temp = NULL;
    unsigned char* v_temp = NULL;
    u_temp = (unsigned char*)malloc(w * h);
    v_temp = (unsigned char*)malloc(w * h);
    unsigned long i, nr, ng, nb, n_size;
    for (i = 0, n_size = 0; n_size < w * h * 3; n_size += 3)
    {
        nb = rgb_data[n_size];
        ng = rgb_data[n_size + 1];
        nr = rgb_data[n_size + 2];
        y[i] = (unsigned char)(RGBYUV02990[nr] + RGBYUV05870[ng] + RGBYUV01140[nb]);
        u_temp[i] = (unsigned char)(-RGBYUV01684[nr] - RGBYUV03316[ng] + nb / 2 + 128);
        u_temp[i] = (unsigned char)(nr/2 - RGBYUV04187[ng] -RGBYUV00813[nb] + 128);
        i++;
    }
    int k = 0;
    for (i = 0; i < h; i += 2)
    {
        for (unsigned long j = 0; j < w; j += 2)
        {
            u[k] = (u_temp[i * w + j] + u_temp[(i + 1) * w + j] + u_temp[i * w + j + 1] + u_temp[(i + 1) * w + j + 1]) / 4;
            v[k] = (v_temp[i * w + j] + v_temp[(i + 1) * w + j] + v_temp[i * w + j + 1] + v_temp[(i + 1) * w + j + 1]) / 4;
            k++;
        }
    }

    for (i = 0; i < w * h; i++)
    {
        if (y[i] < 16)
            y[i] = 16;
        if (y[i] > 235)
            y[i] = 235;
    }
    for (i = 0; i < w * h / 4; i++)
    {
        if (u[i] < 16)
            u[i] = 16;
        if (v[i] < 16)
            v[i] = 16;
        if (u[i] > 240)
            u[i] = 240;
        if (v[i] > 240)
            v[i] = 240;
    }
    if (u_temp)
        free(u_temp);
    if (v_temp)
        free(v_temp);
}
//调色板函数
bool make_palette(FILE* pfile, const BITMAPFILEHEADER& file_h, const BITMAPINFOHEADER& info_h, RGBQUAD* pRGB_out)
{
    if ((file_h.bfOffBits - sizeof(BITMAPFILEHEADER) - info_h.biSize) == sizeof(RGBQUAD) * pow((float)2, info_h.biBitCount))
    {
        fseek(pfile, sizeof(BITMAPFILEHEADER) + info_h.biSize, 0);
        if (fread(pRGB_out, sizeof(RGBQUAD), (unsigned int)pow((float)2, info_h.biBitCount), pfile) != (unsigned int)pow((float)2, info_h.biBitCount))
        {
            printf("fail to read RGBQUAD\n");
        }
        return true;
    }
    else
        return false;
}

在写主函数之前先进行命令行参数的调整,需要传入5张BMP格式的图片,此处的参数命名应与文件夹中的图片名称一致。

//主函数
int main(int argc, char** argv)
{
	//定义变量
	BITMAPFILEHEADER File_header;
	BITMAPINFOHEADER Info_header;
	FILE* bmpFile = NULL;
	FILE* yuvFile = NULL;
	int frameWidth;
	int frameHeight;
	const int imageNum = 5;
	const char* bmpFileName[imageNum] = { "1.bmp","2.bmp","3.bmp","4.bmp","5.bmp" };
	const char* yuvFileName = "out.yuv";
	unsigned char* rgbBuf;
	unsigned char* yBuf;
	unsigned char* uBuf;
	unsigned char* vBuf;
	bool flip = TRUE;

	//打开输出文件
	yuvFile = fopen(yuvFileName, "wb");

	//逐个图片转换
	for (int i = 0; i < imageNum; i++)
	{
		bmpFile = fopen(bmpFileName[i], "rb");
		//读文件头、信息头
		if (fread(&File_header, sizeof(BITMAPFILEHEADER), 1, bmpFile) != 1)
		{
			cout << "read file header error!" << endl;
			exit(0);
		}
		if (File_header.bfType != 0x4D42)
		{
			cout << "Not bmp file!" << endl;
			exit(0);
		}
		else
		{
			cout << "this is a bmp file!" << endl;
		}
		if (fread(&Info_header, sizeof(BITMAPINFOHEADER), 1, bmpFile) != 1)
		{
			cout << "read info header error!" << endl;
			exit(0);
		}

		//赋值
		frameWidth = Info_header.biWidth;
		frameHeight = Info_header.biHeight;

		//分配空间
		rgbBuf = (unsigned char*)malloc(frameWidth * frameHeight * 3);
		yBuf = (unsigned char*)malloc(frameWidth * frameHeight);
		uBuf = (unsigned char*)malloc(frameWidth * frameHeight / 4);
		vBuf = (unsigned char*)malloc(frameWidth * frameHeight / 4);

		//调色板判断
		RGBQUAD* pRGB = (RGBQUAD*)malloc(sizeof(RGBQUAD) * (unsigned int)pow(2, Info_header.biBitCount));
		if (!make_palette(bmpFile, File_header, Info_header, pRGB))
			cout << "No palettel" << endl;

		//读取BMP文件中的RGB数据
		read_rgb(frameWidth, frameHeight, bmpFile, rgbBuf)

		//RGB转YUV
		if (RGB2YUV(frameWidth, frameHeight, rgbBuf, yBuf, uBuf, vBuf, flip))
		{
			printf("error");
			return 0;
		}

		for (int i = 0; i < frameWidth * frameHeight; i++)
		{
			if (yBuf[i] < 16) yBuf[i] = 16;
			if (yBuf[i] > 235) yBuf[i] = 235;
		}

		for (int i = 0; i < frameWidth * frameHeight / 4; i++)
		{
			if (uBuf[i] < 16) uBuf[i] = 16;
			if (uBuf[i] > 240) uBuf[i] = 240;

			if (vBuf[i] < 16) vBuf[i] = 16;
			if (vBuf[i] > 240) vBuf[i] = 240;
		}

		//写入yuv文件
		for (int i = 0; i < 40; i++)
		{
			fwrite(yBuf, 1, frameWidth * frameHeight, yuvFile);
			fwrite(uBuf, 1, (frameWidth * frameHeight) / 4, yuvFile);
			fwrite(vBuf, 1, (frameWidth * frameHeight) / 4, yuvFile);
		}

		free(rgbBuf);
		free(yBuf);
		free(uBuf);
		free(vBuf);
		fclose(bmpFile);
	}
	fclose(yuvFile);

	return 0;
}

实验输出的yuv文件打开后效果如下:

yuv_out

三、实验反思

1.对于命令行参数的理解

int main(int argc,char **argv){}

argc记录下argv的数目,不需要输入,在项目设置中设置的参数都是argv,输入与该工程同文件下的图片名称,指针可以指到图片所在位置并在代码中运用。

2.关于从bmp文件中读取RGB数据(read_rgb)

bmp的位图数据是从左向右,从下向上存放的,index_data是直接从bmp文件中读取的与数据顺序相倒的数据,需要再进行一次转换得到data。需要留意的是,得到的data并不一定是RGB数据,因为bmp有2位、4位、8位、16位、24位不同的位深,仅有24位的bmp位图的有效数据就RGB。

3.关于RGB2YUV

转换公式如下:

Y=0.299R+0.587G+0.114B

U=0.493(B-Y)

V=0.877(R-Y)

这里采用了4:2:0的采样格式,U和V的数据都是Y数据的1/4。

主参考资料:BMP文件转YUV文件_C语言实现_vacu_um的博客-CSDN博客_bmp转yuv

举报

相关推荐

0 条评论