BMP转YUV实验报告

1.BMP格式文件格式转换基本原理

BMP文件的内部储存格式如下图所示,包括文件头,信息头,调色板和位图数据。在VS下以二进制方式打开BMP文件时,我们可以清晰地看到这四个模块的内容。


1)文件头的内部存储数据如下图所示,其中,WORD--2字节,DWORD--4字节,在二进制文件中,可以根据字节数推算,从而找到表示相应含义的字节。


2)信息头的内部存储数据如下图所示,WORD--2字节,DWORD--4字节,LONG--2字节。


3)调色板——本质是一个数组,它包含的元素和位图具有的颜色数相同,由biClrUsed和biBitCount所决定。元素的类型都是RGBQUAD结构。但是本实验中,只有8bit, 4bit, 1bit位图有调色板,24bit和16bit的真彩色无调色板部分。这也就为转换时的处理带来了方便,尤其是24bit的真彩图可以在读入文件头和信息头之后直接将IMAGE DATA部分直接写入rgbBuf,24bit位图的图像数据部分存储和rgb格式相同。调色板包含的数据如下图所示。


2.实验流程分析

首先,打开并读入BMP文件、定义变量,开辟存储空间。BMP文件中首先被读入的应该是文件头和信息头,读入之后才能根据其中给出的关于位图的宽高、bit数等信息进行开辟空间等操作。在本实验中,定义两个指针:BITMAPFILEHEADER File_header; BITMAPINFOHEADER Info_header; 分别用来获取文件头和信息头的数据。注意,这两种类型需要加<windows.h>头文件。

其次,提取出RGB数据,写入缓冲区。不同bit的文件不同处理。24bit图可以直接取图片数据;16bit比较复杂,需要取像素数据转换成8bit彩色分量后再写入rgbBuf;8bit,4bit,1bit均需要构造调色板。需要注意的是图像数据需要倒置,具体代码见代码分析部分。

再次,调用上次实验写好的RGB转YUV的函数实现转换,并写入新的YUV文件。生成视频时可以循环写入,每张图片素材40帧。打开方式“wb+”可以下次继续写入。

最后,释放缓冲区,关闭文件。

3.关键代码分析

1,获取文件的宽高,开辟储存空间。

u_int width, height;
	if ((Info_header.biWidth % 4) == 0)
		width = Info_header.biWidth;
	else
		width = (Info_header.biWidth*Info_header.biBitCount + 31) / 32 * 4;
		//加31除32后取整数部分,就保证了计算结果是离这个数最近的而且是比它大的32的倍数,即为4字节的整数倍。
	    //乘4和行数,得到4字节整数倍的图像大小,以便开辟储存空间时除4时不出现问题
	if ((Info_header.biHeight % 2) == 0)
		height = Info_header.biHeight;
	else
		height = Info_header.biHeight + 1;

	
	/* get the output buffers for a frame */
	rgbBuf = (u_int8_t*)malloc(height*width * 3);
	yBuf = (u_int8_t*)malloc(height*width * 3);
	uBuf = (u_int8_t*)malloc(height*width / 4);
	vBuf = (u_int8_t*)malloc(height*width / 4);
2,读入BMP图像数据,在本实验中,将读取BMP中rgb数据的过程写在bmp2rgb.cpp文件中,下面给出bmp2rgb.cpp的代码。这个过程中,要注意的一点是,BMP的图像数据部分虽然和rgb形式相同,但是是倒序存储在文件中的,需要我们在提取时将数据倒序存储在rgbBuf中,详见代码注释部分。在完成后需要注意释放开辟的空间。

#include<windows.h>
#include "rgb2yuv.h"
#include<stdio.h>
#include<stdlib.h>
#include<malloc.h>
#include<math.h>

int BMP2RGB(FILE * pFile, BITMAPFILEHEADER & file_h, BITMAPINFOHEADER & info_h, unsigned char * rgbDataOut)
{
	unsigned long Loop,i,j,width,height,w,h;
	unsigned char mask,*Index_Data,* Data;

	if (((info_h.biWidth/8*info_h.biBitCount)%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;

	width = w/8*info_h.biBitCount;
	height = h;

	Index_Data = (unsigned char *)malloc(height*width);
	Data = (unsigned char *)malloc(height*width);

	fseek(pFile,file_h.bfOffBits,0);
	if(fread(Index_Data,height*width,1,pFile) != 1)
	{
		printf("read file error!\n\n");
		exit(0);
	}
	//倒序存储,考虑到BMP的图像存储顺序是倒序,尾行在第一行,在这里做倒转。
	//正序图像数据存入Data
	for ( i = 0;i < height;i++)
	{
		for (j = 0;j < width;j++)
		{
 			Data[i*width+j] = Index_Data[(height-i-1)*width+j];
		}
	}
	if(info_h.biBitCount==24)
	{
		memcpy(rgbDataOut,Data,height*width);
	}
	if(info_h.biBitCount==16)
	{
			for (Loop = 0;Loop < height * width;Loop +=2)
			{
				*rgbDataOut = (Data[Loop]&0x1F)<<3;
				*(rgbDataOut + 1) = ((Data[Loop]&0xE0)>>2) + ((Data[Loop+1]&0x03)<<6);
				*(rgbDataOut + 2) = (Data[Loop+1]&0x7C)<<1;

				rgbDataOut +=3;
			}
	}
	RGBQUAD *pRGB = (RGBQUAD *)malloc(sizeof(RGBQUAD)*(unsigned int)pow(float(2),info_h.biBitCount));
	int temp = sizeof(pRGB);
	if(!MakePalette(pFile,file_h,info_h,pRGB))
		printf("No palette!\n\n");
	if(info_h.biBitCount!=24&&info_h.biBitCount!=16)
	{
		for (Loop=0;Loop<height*width;Loop++)
		{
			switch(info_h.biBitCount)
			{
			case 1:
				mask = 0x80;
				break;
			case 2:
				mask = 0xC0;
				break;
			case 4:
				mask = 0xF0;
				break;
			case 8:
				mask = 0xFF;
			}
			int shiftCnt = 1;
			while (mask)
			{
				unsigned char index = mask == 0xFF ? Data[Loop] : ((Data[Loop] & mask)>>(8 - shiftCnt * info_h.biBitCount));
				* rgbDataOut = pRGB[index].rgbBlue;
				* (rgbDataOut+1) = pRGB[index].rgbGreen;
				* (rgbDataOut+2) = pRGB[index].rgbRed;
				if(info_h.biBitCount == 8)
					mask =0;
				else
					mask >>= info_h.biBitCount;
				rgbDataOut += 3;
				shiftCnt ++;
			}
		}
	}
	if(Index_Data) free(Index_Data);
	if(Data) free(Data);
	if(pRGB) free(pRGB);
	return(0);
}
4,实验结果及其分析

  

24bit 16bit 8bit

   

原图 4bit 1bit

转换结果和原图如上面的六幅图所示,实验中选取了四幅微信头像作为素材,其中这一幅天空颜色渐变,在不同的量化比特数下效果十分明显,故此处以此图为例。第一幅24bit的图可以实现2^24种颜色,颜色过渡自然,用原图和24bit转换后的图进行比较,可以发现几乎没有差别。从16bit和8bit可以看到天空的颜色出现明显分层。4bit时,图中只有16种颜色。1bit时,只有黑白两色。

生成视频时要尤其注意,图片的宽高应该一致,这在处理单张图片时不会出现问题,但是多幅图拼接成视频就会出现问题。由于选取的第五张图宽高不同于前四张头像,所以出现图像混乱,如下图所示。最终我们以四幅图,每幅图50帧的方式写满200帧的视频并正常播放。


5,调试过程问题总结

1)rgb2yuv.h中提示FILE未定义符号,提示BITMAPFILEHEADER和BITMAPINFOHEADER未定义。包括我在内,不少同学都遇到了头文件中报错的问题。后者会出现红色下划线,包含了<windows.h>头文件后报错下划线就会消失。但是FILE未定义时,并没有红色的下划线提示,报错时反复看也看不出问题。一般对类型报错都是由没有包含定义该类型的头文件导致。其实这样的问题很容易解决:右键FILE->转到定义。此时会弹出定义FILE的文件窗口,即“stdio.h”,将它include即可。

#ifndef RGB2YUV_H_
#define RGB2YUV_H_
#include<windows.h>
#include<stdio.h>

int BMP2RGB(FILE * pFile, BITMAPFILEHEADER & file_h, BITMAPINFOHEADER & info_h, unsigned char * rgbDataOut);
int RGB2YUV (int x_dim, int y_dim, void *bmp, void *y_out, void *u_out, void *v_out, int flip);
bool MakePalette(FILE * pFile, BITMAPFILEHEADER &file_h, BITMAPINFOHEADER & info_h, RGBQUAD *pRGB_out);
void InitLookupTable();


#endif

2)复制代码出现问题。报错:“ error C4335: 检测到 Mac 文件格式: 请将源文件转换为 DOS 格式或 UNIX 格式 ”此处再次强调编程习惯的重要性,实验时粘贴代码,尤其是从ppt或者网页上直接粘贴到VS上会导致出现如下错误。这个错误非常麻烦,一般是空格或者换行的格式有误,比如用了全角,但是往往我们是看不出空格和换行符的错误的。所以一旦出现问题,就把复制过的地方重新敲一遍,或者把换行重新敲一下。另外,复制代码还可能 随之出现不识别已经定义的数据类型符报错的问题,比如1)中提到的不识别BITMAPINFOHEADER,这时尝试保存好工程然后关闭所有文件重新打开一下。可能会弹出下面的对话框,选择是,标准化后错误可能就消失了。




参考:http://blog.csdn.net/leixiaohua1020/article/details/13506099 致敬师哥。


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一段简单的 Linux 下 BMP YUV420 的 C 语言代码: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #define BMP_HEADER_SIZE 54 void bmp_to_yuv420(char *bmp_file, char *yuv_file, int width, int height) { FILE *bmp_fp = fopen(bmp_file, "rb"); if (bmp_fp == NULL) { printf("Failed to open BMP file.\n"); return; } FILE *yuv_fp = fopen(yuv_file, "wb"); if (yuv_fp == NULL) { printf("Failed to create YUV file.\n"); fclose(bmp_fp); return; } // BMP header unsigned char bmp_header[BMP_HEADER_SIZE]; fread(bmp_header, sizeof(unsigned char), BMP_HEADER_SIZE, bmp_fp); // Image data int image_size = width * height * 3; unsigned char *image_data = (unsigned char *)malloc(image_size * sizeof(unsigned char)); fread(image_data, sizeof(unsigned char), image_size, bmp_fp); // YUV data int y_size = width * height; int u_size = y_size / 4; int v_size = y_size / 4; unsigned char *yuv_data = (unsigned char *)malloc((y_size + u_size + v_size) * sizeof(unsigned char)); // Convert BMP to YUV int i, j, k, l; for (i = 0; i < height; i++) { for (j = 0; j < width; j++) { int index = i * width + j; int y_index = i * width + j; int u_index = (i / 2) * (width / 2) + (j / 2) + y_size; int v_index = (i / 2) * (width / 2) + (j / 2) + y_size + u_size; unsigned char r = image_data[index * 3]; unsigned char g = image_data[index * 3 + 1]; unsigned char b = image_data[index * 3 + 2]; // Y component yuv_data[y_index] = (unsigned char)(0.299 * r + 0.587 * g + 0.114 * b); // U component if (i % 2 == 0 && j % 2 == 0) { int u_sum = 0; for (k = i; k < i + 2; k++) { for (l = j; l < j + 2; l++) { int u_index = (k / 2) * (width / 2) + (l / 2) + y_size; u_sum += image_data[(k * width + l) * 3 + 1]; } } yuv_data[u_index] = (unsigned char)(u_sum / 4); } // V component if (i % 2 == 0 && j % 2 == 0) { int v_sum = 0; for (k = i; k < i + 2; k++) { for (l = j; l < j + 2; l++) { int v_index = (k / 2) * (width / 2) + (l / 2) + y_size + u_size; v_sum += image_data[(k * width + l) * 3 + 2]; } } yuv_data[v_index] = (unsigned char)(v_sum / 4); } } } // Write YUV file fwrite(yuv_data, sizeof(unsigned char), y_size + u_size + v_size, yuv_fp); // Cleanup free(image_data); free(yuv_data); fclose(bmp_fp); fclose(yuv_fp); } int main() { bmp_to_yuv420("input.bmp", "output.yuv", 640, 480); return 0; } ``` 这段代码会将输入的 BMP 文件换为 YUV420 格式,并将结果保存到指定的 YUV 文件中。需要注意的是,这段代码仅适用于 24 位真彩色 BMP 图像。如果要处理其他类型的 BMP 图像,需要对代码进行适当的修改。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值