需求:需要将BMP中的数据转换成YUV420之后,再输出到数组中去;
一、BMP图像数据格式详解
数据段名称 | 大小(byte) | 开始地址 | 结束地址 |
位图文件头(bitmap-file header)
|
14 | 0000h | 000Dh |
位图信息头(bitmap-information header)
|
40 | 000Eh | 0035h |
调色板(color table)
|
由biBitCount决定 | 0036h | 未知 |
图片点阵数据(bitmap data)
|
由图片大小和颜色定 | 未知 | 未知 |
typedef struct tagBITMAPFILEHEADER
{
UINT16 bfType; //2Bytes,必须为"BM",即0x424D 才是Windows位图文件
DWORD bfSize; //4Bytes,整个BMP文件的大小
UINT16 bfReserved1; //2Bytes,保留,为0
UINT16 bfReserved2; //2Bytes,保留,为0
DWORD bfOffBits; //4Bytes,文件起始位置到图像像素数据的字节偏移量
} BITMAPFILEHEADER;
变量名 | 地址偏移 | 大小 | 作用说明 |
bfType | 0000h | 2Bytes |
文件标识符,必须为"BM",即0x424D 才是Windows位图文件
‘BM’:Windows 3.1x, 95, NT,… ‘BA’:OS/2 Bitmap Array ‘CI’:OS/2 Color Icon
‘CP’:OS/2 Color Pointer ‘IC’:OS/2 Icon
‘PT’:OS/2 Pointer
因为OS/2系统并没有被普及开,所以在编程时,你只需判断第一个标识“BM”就行
|
bfSize | 0002h | 4Bytes | 整个BMP文件的大小(以位B为单位) |
bfReserved1 | 0006h | 2Bytes | 保留,必须设置为0 |
bfReserved2 | 0008h | 2Bytes | 保留,必须设置为0 |
bfOffBits | 000Ah | 4Bytes | 说明从文件头0000h开始到图像像素数据的字节偏移量(以字节Bytes为单位),以为位图的调色板长度根据位图格式不同而变化,可以用这个偏移量快速从文件中读取图像数据 |
typedef struct _tagBMP_INFOHEADER
{
DWORD biSize; //4Bytes,INFOHEADER结构体大小,存在其他版本I NFOHEADER,用作区分
LONG biWidth; //4Bytes,图像宽度(以像素为单位)
LONG biHeight; //4Bytes,图像高度,+:图像存储顺序为Bottom2Top,-:Top2Bottom
WORD biPlanes; //2Bytes,图像数据平面,BMP存储RGB数据,因此总为1
WORD biBitCount; //2Bytes,图像像素位数
DWORD biCompression; //4Bytes,0:不压缩,1:RLE8,2:RLE4
DWORD biSizeImage; //4Bytes,4字节对齐的图像数据大小
LONG biXPelsPerMeter; //4 Bytes,用象素/米表示的水平分辨率
LONG biYPelsPerMeter; //4 Bytes,用象素/米表示的垂直分辨率
DWORD biClrUsed; //4 Bytes,实际使用的调色板索引数,0:使用所有的调色板索引
DWORD biClrImportant; //4 Bytes,重要的调色板索引数,0:所有的调色板索引都重要
}BMP_INFOHEADER;
变量名
|
地址偏移
|
大小
|
作用说明
|
biSize
|
000Eh
|
4Bytes
|
BNP信息头即BMP_INFOHEADER结构体所需要的字节数(以字节为单位)
|
biWidth
|
0012h
|
4Bytes
|
说明图像的宽度(以像素为单位)
|
biHeight
|
0016h
|
4Bytes
|
说明图像的高度(以像素为单位)。这个值还有一个用处,指明图像是正向的位图还是倒向的位图,该值是正数说明图像是倒向的即图像存储是由下到上;该值是负数说明图像是倒向的即图像存储是由上到下。大多数BMP位图是倒向的位图,所以此值是正值。
|
biPlanes
|
001Ah
|
2Bytes
|
为目标设备说明位面数,其值总设置为1
|
biBitCount
|
001Ch
|
2Bytes
|
说明一个像素点占几位(以比特位/像素位单位),其值可为1,4,8,16,24或32
|
biCompression
|
001Eh
|
4Bytes
|
说明图像数据的压缩类型,取值范围为:
0 BI_RGB 不压缩(最常用)
1 BI_RLE8 8比特游程编码(BLE),只用于8位位图
2 BI_RLE4 4比特游程编码(BLE),只用于4位位图
3 BI_BITFIELDS比特域(BLE),只用于16/32位位图
4
|
biSizeImage
|
0022h
|
4Bytes
|
说明图像的大小,以字节为单位。当用BI_RGB格式时,总设置为0
|
biXPelsPerMeter
|
0026h
|
4Bytes
|
说明水平分辨率,用像素/米表示,有符号整数
|
biYPelsPerMeter
|
002Ah
|
4Bytes
|
说明垂直分辨率,用像素/米表示,有符号整数
|
biClrUsed | 002Eh | 4Bytes | 说明位图实际使用的调色板索引数,0:使用所有的调色板索引 |
biClrImportant | 0032h | 4Bytes | 说明对图像显示有重要影响的颜色索引的数目,如果是0,表示都重要。 |
typedef struct _tagRGBQUAD
{
BYTE rgbBlue; //指定蓝色强度
BYTE rgbGreen; //指定绿色强度
BYTE rgbRed; //指定红色强度
BYTE rgbReserved; //保留,设置为0
} RGBQUAD;
V = 0.5 *R - 0.419*G - 0.081*B + 128;
三,BMP转YUV420
程序参考:
http://blog.csdn.net/vacu_um/article/details/68572557?reload
这部分代码中,大量为直接转载,博主为:vacu_um
2.1 读取bmp位图
static void ReadRGB(FILE *pFile, BITMAPFILEHEADER *file_h,
BITMAPINFOHEADER *info_h, unsigned char *rgbDataOut)
{
unsigned long Loop, iLoop, jLoop, width, height, w, h;
unsigned char mask, *Index_Data, *Data;
int shiftCnt;
unsigned char index ;
//保证是图像大小是4字节的整数倍,具体理由见下面的注释
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;
//若是24位,则bmp中有效数据大小是长*宽*3字节
//若是16位,则bmp中有效数据大小是长*宽*2字节
//若是8位,则bmp中有效数据大小是长*宽字节
//若是4位,则bmp中有效数据大小是长*宽/2字节
//若是2位,则bmp中有效数据大小是长*宽/4字节
//若是1位,则bmp中有效数据大小是长*宽/8字节(这大概是为什么bmp图像的长必须是4的倍数,宽必须是2的倍数的原因吧。。。)
width = (w/ 8)* info_h->biBitCount;
height = h;
//倒序前数据缓存区
Index_Data = (unsigned char*)malloc(height*width);//buffer大小应该与bmp中有效数据大小相同
//倒序后数据缓存区,用于存放bmp中的有效数
Data = (unsigned char*)malloc(height*width);//buffer大小应该与bmp中有效数据大小相同
//文件指针定位到有效数据起始处,读取有效数据
fseek(pFile,file_h->bfOffBits,0);
printf("file_h->bfOffBits = %d \n",file_h->bfOffBits);
if(fread(Index_Data, height*width, 1, pFile) > height*width)
{
printf("readfile error!");
exit(0);
}
//倒序存放
for (iLoop= 0; iLoop < height; iLoop++)
{
for (jLoop= 0; jLoop < width; jLoop++)
{
Data[iLoop*width + jLoop] =Index_Data[(height - iLoop - 1)*width
+ jLoop];
}
}
//24位:直接把数据复制给输出缓存区
if (info_h->biBitCount== 24)
{
memcpy(rgbDataOut,Index_Data, height*width);
free(Index_Data);
free(Data);
return;
}
//非24位:解码生成rgb,需要调色板信息
//生成调色板数组,数组的下标,对应bmp文件中有效数据,通过下标对应查找,便可得到该数据对应的颜色
//debug by LiuDong:(unsignedlong long)pow(),pow前的强制类型转换不能转换成unsignedint、unsignedchar等
//因为pow((float)2, info_h.biBitCount)最大值是2^24,unsigned char最大能表示255,unsigned int最大能表示2^32
RGBQUAD *pRGB = (RGBQUAD *)malloc(sizeof(RGBQUAD)*(unsigned int)pow((float)2,info_h->biBitCount));
/*一个单元代表一种颜色
调色板实际上是一个数组,它所包含的元素与位图所具有的颜色数相同,决定于biClrUsed和biBitCount字段。
数组中每个元素的类型是一个RGBQUAD结构。真彩色无调色板部分。
biBitCount;位数/像素,1、2、4、8、24.假如是16位,R占5位,G占5位,B占5位,空出一位,排列组合一共有2^15种颜色,其他位数同理。*/
//读取位图调色板数据
if(!MakePalette(pFile, file_h,info_h,pRGB))
printf("Nopalette!");
//16位:移位操作,从2字节中取出RGB信息,存到3字节中
if (info_h->biBitCount== 16)
{
for(Loop = 0; Loop < height * width; Loop += 2)
{
*rgbDataOut= (Data[Loop] & 0x1F) << 3;//B:用0001 1111取出低字节的右五位,再放到目标字节的高5位(通过右移3位),得到五位的B
*(rgbDataOut+ 1) = ((Data[Loop] & 0xE0) >> 2) + ((Data[Loop + 1] & 0x03)<< 6);//G:11100000取出低字节的左三位,00000011取出高字节的右两位,合并后,放到再放到目标字节的高5位,得到五位的G
*(rgbDataOut+ 2) = (Data[Loop + 1] & 0x7C) << 1; //R:0111 1100取出高字节的中间五位,再放到目标字节的高5位,得到5位的R
rgbDataOut+= 3;
}//RGB都各自位于字节的高5位
}
//1~8位:移位操作,从有限固定位中取出RGB信息,存到3字节中
//循环次数:有效数据字节数
for (Loop =0; LoopbiBitCount)
{
case 1://1000 0000,1位,黑白双色图
mask = 0x80;
break;
case 2://1100 0000,2位,4色图
mask = 0xC0;
break;
case 4://1111 0000,4位,16色图
mask = 0xF0;
break;
case 8://1000 0000,8位,256色图
mask = 0xFF;
}
shiftCnt = 1;//控制mask的移位,决定取字节中哪些数据
while(mask)//循环一次就是一个字节的解析过程
{
index = mask == 0xFF ? Data[Loop] : ((Data[Loop] & mask) >> (8 -shiftCnt * info_h->biBitCount));
//Loop代表第几个带转换的原始有效数据
//pRGB代表调色板
//8位:mask=1111 1111,index=data[Loop],即index为bmp中原始有效数据,直接对应调色板数组下标,得到相应颜色
//查找调色板,取出对应BGR存入目标buffer:rgbDataOut
*rgbDataOut= pRGB[index].rgbBlue;//B
*(rgbDataOut+ 1) = pRGB[index].rgbGreen;//G
*(rgbDataOut+ 2) = pRGB[index].rgbRed;//R
if(info_h->biBitCount== 8)
mask = 0;//如果是8位bmp,一次性取完一个字节的颜色数据,直接跳出循环即可
else
mask >>= info_h->biBitCount;//若是1位bmp,则一字节取8次数据;若是2位bmp,则一字节取4次数据;若是4位bmp,则一字节取2次数据
//debugby LiuDong
if(Loop == width*height - 1)
{
rgbDataOut= rgbDataOut + 3 - width*height*3;
break;
}
rgbDataOut+= 3;
shiftCnt++;
}
}
if(Index_Data)
free(Index_Data);
if (Data)
free(Data);
if (pRGB)
free(pRGB);
}
2.2 RGB转YUV换函数
/ RGB数据转化为YUV数据
// Ø 计算公式:Y=0.30R+0.59G+0.11B , U=0.493(B-Y) , V=0.877(R-Y)
// 需要注意采用4:2:0格式,u的数据是y的数据的1/4,v的数据是y的数据的1/4,有一个下采样的过程
static int RGB2YUV(unsigned long w,unsigned long h,unsigned char* rgbData,
unsigned char*y,unsigned char*u,unsigned char*v)
{
unsigned char*ytemp = NULL;
unsigned char*utemp = NULL;
unsigned char*vtemp = NULL;
unsigned long i, nr, ng, nb, nSize;
unsigned long j;
utemp = (unsigned char*)malloc(w*h);
vtemp = (unsigned char*)malloc(w*h);
printf(" RGB2YUV start \n");
//对每个像素进行 rgb -> yuv的转换
for (i = 0,nSize = 0; nSize235)
y[i]= 235;
}
for (i = 0;i240)
u[i]= 240;
if(v[i]>240)
v[i]= 240;
}
*/
if (utemp)
free(utemp);
if(vtemp)
free(vtemp);
}
四、程序流程
4.1 程序流程代码:
int main(int argc, char *argv[])
{
FILE *bmpFile = NULL;
FILE *yuvFile = NULL;
FILE *fp_dat = NULL;
char *file_namesp = NULL;
char filename_bmp[50]={0};
char filename_yuv[50]={0};
char filename_dat[50]={0};
unsigned char buff_head[14] = {0};
int i;
BITMAPFILEHEADER File_header;
BITMAPINFOHEADER Info_header;
int len = 0;
unsigned long width, height;
if (argc != 2)
{
printf("usage: %s \r\n", argv[0]);
return -1;
}
strcpy(filename_bmp,argv[1]);
file_namesp = strstr(filename_bmp,".bmp");
if(NULL == file_namesp)
{
printf("Please Use bmp \n");
return -1;
}
else
{
len = (file_namesp-filename_bmp);
memcpy(filename_yuv,filename_bmp,len);
memcpy(filename_dat,filename_bmp,len);
memcpy(&filename_yuv[len],".yuv",4);
memcpy(&filename_dat[len],".dat",4);
}
printf("%s %s\n",filename_yuv,filename_dat);
bmpFile= fopen(filename_bmp, "rb");
if (bmpFile == NULL)
{
printf("open file(%s) failed\r\n", filename_bmp);
return -1;
}
fseek(bmpFile, 0, SEEK_SET);
//读取位图文件头
if(fread(buff_head, 14, 1, bmpFile) > 14)
{
printf("read file header error!\n");
return -1;
}
// 由于结构体4字节对齐问题,不能直接用结构体地址
File_header.bfType = buff_head[0]|(buff_head[1]<<8);
File_header.bfSize = buff_head[2]|(buff_head[3]<<8)|(buff_head[4]<<16)|(buff_head[5]<<24);
File_header.bfOffBits = buff_head[10]|(buff_head[11]<<8)|(buff_head[12]<<16)|(buff_head[13]<<24);
//判断文件类型
if(File_header.bfType != 0x4D42)
{
printf("Notbmp file!\n");
return -1;
}
else
{
printf("this is a bmp file\n");
}
//读取位图信息头
if(fread(&Info_header, sizeof(BITMAPINFOHEADER), 1, bmpFile) > sizeof(BITMAPINFOHEADER))
{
printf("read info header error! \n");
return -1;
}
//为了保证图像大小是4的倍数,每一行的像素数*每像素的位数=一行总位数
//一行总位数必须是32位的整数倍。
//为什么呢?这样只有才能保证一行数据量是4字节的整数倍。
//怎么做呢?(x+31)除以32,再下取整,得到结果以后,再乘以32,
//就得到比原位数大且是32的倍数的数字,再除以8便得到字节数,肯定是4字节的整数倍。
if(Info_header.biWidth % 4 == 0)
width = Info_header.biWidth;
else
width =((Info_header.biWidth*Info_header.biBitCount + 31) / 32)*4;
//保证列数是偶数
if((Info_header.biHeight % 2) == 0)
height = Info_header.biHeight;
else
height = Info_header.biHeight + 1;
InitLookupTable();
//开辟缓冲区
rgbBuffer = (unsigned char*)malloc(height*width* 3);
yBuffer = (unsigned char*)malloc(height*width);
uBuffer = (unsigned char*)malloc(height*width/4);
vBuffer = (unsigned char*)malloc(height*width/4);
//从bmp文件中读取rgb信息
ReadRGB(bmpFile, &File_header,&Info_header, rgbBuffer);
RGB2YUV(width, height, rgbBuffer,yBuffer, uBuffer, vBuffer);
yuvFile = fopen(filename_yuv, "w");
if (yuvFile == NULL)
{
printf("open file(%s) failed\r\n", filename_yuv);
return -1;
}
//将转好的yuv写到文件中
if(WriteYUV(yBuffer, uBuffer, vBuffer, width*height, yuvFile))
printf("writeYUV file successful!\n");
else
printf("writeYUV file failed!\n");
// 写入到dat文件;
fp_dat = fopen(filename_dat, "w");
if (fp_dat == NULL)
{
printf("open file(%s) failed\r\n", filename_dat);
return -1;
}
buffer_dat = malloc((width*height*3/2)*8);
WriteYUVtoDAT(yBuffer, uBuffer, vBuffer, width*height, fp_dat) ;
fclose(fp_dat);
fclose(yuvFile);
fclose(bmpFile);
if(NULL != buffer_dat)
free(buffer_dat);
if(NULL != yBuffer)
free(yBuffer);
if(NULL != uBuffer)
free(uBuffer);
if(NULL != vBuffer)
free(vBuffer);
if(NULL != rgbBuffer)
free(rgbBuffer);
return 0;
}
4 .2 YUV文件存储函数
// .将YUV文件写入磁盘中
char WriteYUV(unsigned char*Y,unsigned char*U,unsigned char*V,unsigned long size,FILE *outFile)
{
if(fwrite(Y, 1, size,outFile)!= size)
return false;
if(fwrite(U, 1, size/ 4, outFile) != size/ 4)
return false;
if(fwrite(V, 1, size/ 4, outFile) != size/ 4)
return false;
return true;
}
4.3 YUV 转数组函数
// .将YUV数据写成数组
char WriteYUVtoDAT(unsigned char*Y,unsigned char*U,unsigned char*V,unsigned long size,FILE *datfile)
{
unsigned long len = size;
int i=0;
int file_len=0;
// Y 分量
for (i=0; i
代码已经上传至CSDN下载:
下载