BMP2YUV文件转换实验报告
1.基本原理
(1)BMP文件组成结构
BMP(Bitmap) 是一种标准图像文件格式,分为设备相关位图(DDB)和设备无关位图(DIB)。文件深度可选1bit、4bit、8bit、16bit及24bit。BMP文件在储存数据时,图像的扫描方式是按照从左到右、从下到上的顺序。
典型的BMP文件有位图头文件数据结构、位图信息数据结构、调色板、位图数据四部分组成,具体分析如下:
typedef struct tagBITMAPFILEHEADER {
WORD bfType; /* 说明文件的类型; */
DWORD bfSize; /* 说明文件的大小,用字节为单位 */
/*注意此处的字节序问题,bmp以小尾字节序存储*/
WORD bfReserved1; /* 保留,设置为0 */
WORD bfReserved2; /* 保留,设置为0 */
DWORD bfOffBits; /* 说明从BITMAPFILEHEADER结构开始到实际的图像数据之间的字节 偏移量 */
} BITMAPFILEHEADER;
typedef struct tagBITMAPINFOHEADER {
DWORD biSize; /* 说明结构体所需字节数 */
LONG biWidth; /* 以像素为单位说明图像的宽度 */
LONG biHeight; /* 以像素为单位说明图像的高速 */
WORD biPlanes; /* 说明位面数,必须为1 */
WORD biBitCount; /* 说明位数/像素,1、2、4、8、24 */
DWORD biCompression; /* 说明图像是否压缩及压缩类型 BI_RGB,BI_RLE8,BI_RLE4,BI_BITFIELDS */
DWORD biSizeImage; /* 以字节为单位说明图像大小 ,必须是4 的整数倍*/
LONG biXPelsPerMeter; /* 目标设备的水平分辨率,像素/米 */
LONG biYPelsPerMeter; /*目标设备的垂直分辨率,像素/米 */
DWORD biClrUsed; /* 说明图像实际用到的颜色数,如果为0
则颜色数为2的biBitCount次方 */
DWORD biClrImportant; /*说明对图像显示有重要影响的颜色
索引的数目,如果是0,表示都重要。*/
} BITMAPINFOHEADER;
typedef struct tagRGBQUAD {
BYTE rgbBlue; /*指定蓝色分量*/
BYTE rgbGreen; /*指定绿色分量*/
BYTE rgbRed; /*指定红色分量*/
BYTE rgbReserved; /*保留,指定为0*/
} RGBQUAD;
紧接着是信息头:
第三部分调色板,只有在1位图(单色),4位图(16色),8位图(256色)的情况下,此时会有颜色表。调色板实际上是一个数组,它所包含的元素与位图所具有的颜色数相同,决定于biClrUsed和biBitCount字段。数组中每个元素的类型是一个RGBQUAD结构。RGBQUAD结构体由4个字节型数据组成,所以一个RGBQUAD结构体只占用4字节空间,从左到右每个字节依次表示(蓝色,绿色,红色,未使用)。
以16色为例,这16个RGBQUAD结构体依次为:
到此为止,正好达到从BITMAPFILEHEADER结构开始到实际的图像数据之间的字节偏移量14+40+64=118字节。第四部分就是实际的图像数据。如果是真彩色图,图像数据就是实际的RGB值。图像的每一行由表示像素的连续字节组成,每一行的字节数取决于图像的颜色数目和用像素表示的图像宽度。每一行的字节数必须DWORD对齐,必须是4的整数倍。
(2)字节序
(3)彩色空间转换(RGB2YUV)
RGB转YUV的公式可推出YUV转RGB的公式如下,R = Y + 1.4075(V - 128)
G = Y - 0.3455(U - 128) - 0.7169(V - 128)
B = Y + 1.779(U - 128)
2.实验流程分析
(d)写200帧的YUV文件;
(e)程序收尾工作(关闭文件,释放缓冲区);
3.关键代码及分析
//判断像素的实际点阵数
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;//width为实际一行的字节数
height = h;//height为列数
规定每一行的字节数必须DWORD对齐。首先判断像素的点阵数。一个字节是8位,每个像素所需字节数为info_h.biBitCount/8,图像每行的字节数 =W*info_h.biBitCount/8。如果每一行的字节数不是4的整数倍,需要在每行数据补零字节数为4-(W*info_h.biBitCount/8)
Mod 4,补充后以像素为单位的宽度W = (info_h.biWidth*info_h.biBitCount+31)/32*4;case 16://位与移位取像素数据转换为8bit的彩色分量
if(info_h.biCompression == BI_RGB)
{
for (Loop = 0;Loop < height * width;Loop +=2)
{
*rgbDataOut = (Data[Loop]&0x1F)<<3;//0001 1111
*(rgbDataOut + 1) = ((Data[Loop]&0xE0)>>2) + ((Data[Loop+1]&0x03)<<6); //1110 0000 //0000 0011
*(rgbDataOut + 2) = (Data[Loop+1]&0x7C)<<1;//0111 1100
rgbDataOut +=3;
}
}
高位----------->低位 | |
Data[loop] |
Data[loop+1] |
hgfe dcba |
ponm lkji |
&0001 1111左移三位 edcb a000 |
|
*rgbDataOut = edcb a000 | |
&1110 0000右移两位 00hg F000 |
&0000 0011 左移6位 ji00 0000 |
*(rgbDataOut + 1)= jihg f000 | |
|
&0111 1100左移1位 0onm lk00 |
*(rgbDataOut + 2) = onml k000 |
switch(info_h.biBitCount)
{
case 1:
mask = 0x80;//1000 0000当biBitCount=1时,8个像素占1个字节;
break;
case 2:
mask = 0xC0;//1100 0000当biBitCount=2时,4个像素占1个字节;
break;
case 4:
mask = 0xF0;// 1111 0000当biBitCount=4时,2个像素占1个字节;
break;
case 8:
mask = 0xFF;//1111 1111 当biBitCount=8时,1个像素占1个字节;
}
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;
//8bit图像直接读取下一字节
if(info_h.biBitCount == 8)
mask =0;
else
mask >>= info_h.biBitCount;
rgbDataOut+=3;
shiftCnt ++;
}
由于biBitCount的不同,一个字节所能表示的像素数也不同,所以需要掩码mask来掩蔽暂时不需要的bit位。8bit每次恰好读取一个字节,故不需要进行掩码移位。小于8bit的情况,以biBitCount=4bit为例,2个像素占1个字节,shiftCnt=1------>Data[Loop]低四位右移4位即第一个像素的颜色索引值--------->mask右移4位变成0F(0000 1111)---------->shiftCnt
= 2 --->Data[Loop]高四位不移位(右移0位)即为第二个像素的颜色索引值。2bit和1bit的原理与4bit相似。/*--------------main.cpp-------------*/
#include <stdio.h>
#include<windows.h>
#include "bmp2yuv.h"
#define u_int8_t unsigned __int8
#define u_int unsigned __int32
#define u_int32_t unsigned __int32
BITMAPFILEHEADER File_header;
BITMAPINFOHEADER Info_header;
int main(int argc, char** argv)
{ //变量初始化
u_int frameWidth = 352; /* --width=<uint> */
u_int frameHeight = 240; /* --height=<uint> */
bool flip = TRUE; /* --flip */
u_int size,i,k,j,t=0;
char *a=NULL;
char* bmpFileName = NULL;
char* yuvFileName = NULL;
FILE* bmpFile = NULL;
FILE* yuvFile = NULL;
u_int8_t* rgbBuf = NULL;
u_int8_t* yBuf = NULL;
u_int8_t* uBuf = NULL;
u_int8_t* vBuf = NULL;
u_int32_t videoFramesWritten = 0;
//命令行参数
k =atoi(argv[6]);//图像重复帧数
yuvFileName = argv[7];
/* open the YUV file */
yuvFile = fopen(yuvFileName, "wb");
if (yuvFile == NULL)
{
printf("cannot find yuv file\n");
exit(1);
}
else
{
printf("The output yuv file is %s\n", yuvFileName);
}
for(j = 1 ;j<6;j++)
{
bmpFileName = argv[j];
/* open the bmp file */
bmpFile = fopen(bmpFileName, "rb");
if (bmpFile == NULL)
{
printf("cannot find bmp file\n");
exit(1);
}
else
{
printf("The input bmp file is %s\n", bmpFileName);
}
//读取位图文件头
if(fread(&File_header,sizeof(BITMAPFILEHEADER),1,bmpFile) != 1)
{
printf("read file header error!");
exit(0);
}
if (File_header.bfType != 0x4D42)
{
printf("Not bmp file!");
exit(0);
}
else
{
a =(char *)&File_header.bfType;
printf("this is a %s\n",a);
//printf("this is a %x\n",file_h.bfType);
}
//读取信息头
if(fread(&Info_header,sizeof(BITMAPINFOHEADER),1,bmpFile) != 1)
{
printf("read info header error!\n\n");
return false;
}
// end read header
frameWidth = Info_header.biWidth;
frameHeight = Info_header.biHeight;
size = frameWidth * frameHeight;
//开辟缓冲区
rgbBuf = (u_int8_t*)malloc(size * 3);
yBuf = (u_int8_t*)malloc(size);
uBuf = (u_int8_t*)malloc(size / 4);
vBuf = (u_int8_t*)malloc(size / 4);
//从BMP文件中读取RGB
BMPreadRGB(bmpFile,File_header,Info_header,rgbBuf);
//RGB转YUV
if(RGB2YUV(frameWidth, frameHeight, rgbBuf, yBuf, uBuf, vBuf, flip))
{
printf("error");
return 0;
}
//write yuv file
for(i = 0 ;i < k; i++)
{
fwrite(yBuf, 1, frameWidth * frameHeight, yuvFile);
fwrite(uBuf, 1, (frameWidth * frameHeight) / 4, yuvFile);
fwrite(vBuf, 1, (frameWidth * frameHeight) / 4, yuvFile);
printf("\r...%d", ++videoFramesWritten);
}
printf("\n%u %ux%u video frames written\n", videoFramesWritten, frameWidth, frameHeight);
/* cleanup */
free(rgbBuf);
free(yBuf);
free(uBuf);
free(vBuf);
fclose(bmpFile);
}
fclose(yuvFile);
system("pause");
return(0);
}
主函数依次进行了输入输出文件的打开,bmp文件头读取,如果bftype不等于4d42则不是bmp文件,如果是bmp文件,接着读取信息头,获取图像的宽和高,为开辟缓冲区做准备,先调用BMPreadRGB函数写RGB缓冲区,再调用RGB2YUV函数获取YUV数据块,最后写200帧yuv文件。特别注意,最后必须在200帧图像数据写完之后才能关闭yuvfile,否则每一循环yuvfile中的数据都会被更新一次,得到的只有40帧图像。关于命令行参数的设置详细可见彩色空间转换实验报告。void BMPreadRGB(FILE * pFile, BITMAPFILEHEADER &file_h, BITMAPINFOHEADER &info_h,unsigned char * rgbDataOut )
{
u_int32_t w, h,width,height;
u_int Loop,i,j;
unsigned char mask,*dataBuf,*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;//width为实际一行的字节数
height = h;//height为列数
//开辟实际字节数量的缓冲区,读数据,一次读取一个字节
dataBuf = (unsigned char*)malloc(width*height);
Data = (unsigned char*)malloc(width*height);
fseek(pFile, file_h.bfOffBits, 0);
if(fread(dataBuf, 1, width*height, pFile) == 0)
{
printf("read file error!\n\n");
exit(0);
}
//倒序存放
for ( i = 0;i < height;i ++)
for ( j = 0;j < width;j++)
{
Data[i*width+j] = dataBuf[(height-i-1)*width+j];
}
//根据不同像素位数执行不同操作
switch(info_h.biBitCount)
{
case 24:
memcpy(rgbDataOut,Data,height*width);//真彩色图直接取像素数据写rgb缓冲区
if(dataBuf)
free(dataBuf);
if(Data)
free(Data);
return ;
case 16://位与移位取像素数据转换为8bit的彩色分量
if(info_h.biCompression == BI_RGB)
{
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;
}
}
if(dataBuf)
free(dataBuf);
if(Data)
free(Data);
return ;
default://对于小于等于8bit的bmp文件,首先需要位与移位取像素数据,查调色板,写RGB缓冲区
RGBQUAD *pRGB = (RGBQUAD *)malloc(sizeof(RGBQUAD)*(unsigned int)pow(float(2),info_h.biBitCount));
if(!MakePalette(pFile,file_h,info_h,pRGB))
printf("No palette!");
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;
//8bit图像直接读取下一字节
if(info_h.biBitCount == 8)
mask =0;
else
mask >>= info_h.biBitCount;
rgbDataOut+=3;
shiftCnt ++;
}
}
if(dataBuf)
free(dataBuf);
if(Data)
free(Data);
if(pRGB)
free(pRGB);
return ;
}
}
bool MakePalette(FILE * pFile,BITMAPFILEHEADER &file_h,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);
fread(pRGB_out,sizeof(RGBQUAD),(unsigned int)pow(float(2),info_h.biBitCount),pFile);
return true;
}
else
return false;
}
RGB2YUV函数float RGBYUV02990[256],RGBYUV05870[256],RGBYUV01140[256];
float RGBYUV01684[256],RGBYUV03316[256];
float RGBYUV04187[256],RGBYUV00813[256];
void initLookupTable();
int RGB2YUV (int x_dim, int y_dim, void *bmp, void *y_out, void *u_out, void *v_out, int flip)
{
static int init_done = 0;
long i, j, size;
unsigned char *r, *g, *b;
unsigned char *y, *u, *v;
unsigned char *pu1, *pu2, *pv1, *pv2, *psu, *psv;
unsigned char *y_buffer, *u_buffer, *v_buffer;
unsigned char *sub_u_buf, *sub_v_buf;
float tmpy,tmpu,tmpv;
if (init_done == 0)
{
InitLookupTable();
init_done = 1;
}
// check to see if x_dim and y_dim are divisible by 2
if ((x_dim % 2) || (y_dim % 2)) return 1;
size = x_dim * y_dim;
// allocate memory
y_buffer = (unsigned char *)y_out;
sub_u_buf = (unsigned char *)u_out;
sub_v_buf = (unsigned char *)v_out;
u_buffer = (unsigned char *)malloc(size * sizeof(unsigned char));
v_buffer = (unsigned char *)malloc(size * sizeof(unsigned char));
if (!(u_buffer && v_buffer))
{
if (u_buffer) free(u_buffer);
if (v_buffer) free(v_buffer);
return 2;
}
b = (unsigned char *)bmp;
y = y_buffer;
u = u_buffer;
v = v_buffer;
// convert RGB to YUV
if (!flip) {
for (j = 0; j < y_dim; j ++)
{
y = y_buffer + (y_dim - j - 1) * x_dim;
u = u_buffer + (y_dim - j - 1) * x_dim;
v = v_buffer + (y_dim - j - 1) * x_dim;
for (i = 0; i < x_dim; i ++)
{
g = b + 1;
r = b + 2;
*y = (unsigned char)( RGBYUV02990[*r] + RGBYUV05870[*g] + RGBYUV01140[*b]);
*u = (unsigned char)(- RGBYUV01684[*r] - RGBYUV03316[*g] + (*b)/2 + 128);
*v = (unsigned char)( (*r)/2 - RGBYUV04187[*g] - RGBYUV00813[*b] + 128);
b += 3;
y ++;
u ++;
v ++;
}
}
} else {
for (i = 0; i < size; i++)
{
g = b + 1;
r = b + 2;
tmpy = RGBYUV02990[*r] + RGBYUV05870[*g] + RGBYUV01140[*b];
if(tmpy>235)
{
tmpy=235;
}
if(tmpy<16)
{
tmpy=16;
}
*y = (unsigned char)tmpy;
tmpu = - RGBYUV01684[*r] - RGBYUV03316[*g] + (*b)/2 + 128;
if(tmpu>240)
{
tmpu=240;
}
if(tmpu<16)
{
tmpu=16;
}
*u = (unsigned char)tmpu;
tmpv = (*r)/2 - RGBYUV04187[*g] - RGBYUV00813[*b] + 128;
if(tmpv>240)
{
tmpv=240;
}
if(tmpv<16)
{
tmpv=16;
}
*v = (unsigned char)tmpv;
b += 3;
y ++;
u ++;
v ++;
}
}
// subsample UV
for (j = 0; j < y_dim/2; j ++)
{
psu = sub_u_buf + j * x_dim / 2;
psv = sub_v_buf + j * x_dim / 2;
pu1 = u_buffer + 2 * j * x_dim;
pu2 = u_buffer + (2 * j + 1) * x_dim;
pv1 = v_buffer + 2 * j * x_dim;
pv2 = v_buffer + (2 * j + 1) * x_dim;
for (i = 0; i < x_dim/2; i ++)
{
*psu = (*pu1 + *(pu1+1) + *pu2 + *(pu2+1)) / 4;
*psv = (*pv1 + *(pv1+1) + *pv2 + *(pv2+1)) / 4;
psu ++;
psv ++;
pu1 += 2;
pu2 += 2;
pv1 += 2;
pv2 += 2;
}
}
free(u_buffer);
free(v_buffer);
return 0;
}
void InitLookupTable()
{
int i;
for (i = 0; i < 256; i++) RGBYUV02990[i] = (float)0.2990 * i;
for (i = 0; i < 256; i++) RGBYUV05870[i] = (float)0.5870 * i;
for (i = 0; i < 256; i++) RGBYUV01140[i] = (float)0.1140 * i;
for (i = 0; i < 256; i++) RGBYUV01684[i] = (float)0.1684 * i;
for (i = 0; i < 256; i++) RGBYUV03316[i] = (float)0.3316 * i;
for (i = 0; i < 256; i++) RGBYUV04187[i] = (float)0.4187 * i;
for (i = 0; i < 256; i++) RGBYUV00813[i] = (float)0.0813 * i;
}