一.实验原理
1.BMP文件
BMP(全称Bitmap)是Windows操作系统中的标准图像文件格式, BMP文件的图像深度可选lbit、4bit、8bit、16bit及24bit。BMP文件存储数据时,图像的扫描方式是按从左到右、从下到上的顺序。由于BMP文件格式是Windows环境中交换与图有关的数据的一种标准,因此在Windows环境中运行的图形图像软件都支持BMP图像格式。
2.BMP文件的构成
典型的BMP图像文件由四部分组成:
(1)位图头文件数据结构,它包含BMP图像文件的类型、显示内容等信息;
(2)位图信息数据结构,它包含有BMP图像的宽、高、压缩方法,以及定义颜色等信息;
(3)调色板,这个部分是可选的,有些位图需要调色板,有些位图,比如真彩色图(24位的BMP)就不需要调色板;
(4)位图数据,这部分的内容根据BMP位图使用的位数不同而不同,在24位图中直接使用RGB,而其他的小于24位的使用调色板中颜色索引值。
3.二进制编辑器打开BMP文件
前两个字节42 4D表示图像类型为BMP ,文件头FILEHEADER为14字节依次包含文件大小,偏移量等信息,信息头INFOHEADER为40字节,依次包含宽度,高度,深度,图像大小等信息。之后便根据深度的不同,存储的数据发生不同。
(1)24bit
由于24bitBMP图像是真彩色,故无调色板。INFOHEADER后直接开始存实际的位图数据。
(2)8bit
由于8bitBMP图像非真彩色,故需要调色板。
调色板所占空间字节数=sizeof(RGBQUAD)∗2^BitCount (如8bitBMP调色板所占空间字节数=4*2^8=2^10=1024)
所以INFOHEADER后的1024个字节属于改8bit BMP文件的调色板部分。
二.BMP2YUV文件转换流程分析
1.初始化(打开两个文件、定义变量和缓冲区等)
2.BMP文件,抽取或生成RGB数据写入缓冲区
3.调用RGB2YUV的函数实现RGB到YUV数据的转换
4.写YUV文件
5.程序收尾工作(关闭文件,释放缓冲区)
三.代码分析
1.关键代码分析:
(1)像素为16位的时候,位与移位取像素数据转换为8bit/彩色分量,写RGB缓冲区,用图解的方法更加直观。
代码实现:
if (info_h.biBitCount == 16)
{
for (Loop = 0; Loop < height * width ; Loop+=2)//loop每次循环中应该要逐次增加2个字节(16位)。
{
*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;
}
}
(2) 图像的扫描方式是按从左到右、从下到上的顺序。所以用flip来控制上下翻转,flip=1时正序,为0时上下翻转。
代码实现:
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++;
}
}
}
(3)biSizeImage指的是实际图像数据的大小,以字节为单位。规定每一行扫描的字节数必须是4字节的整数倍,与DWORD对齐。因此在计算中宽必须是4的整数倍,如果不是整数倍,则取大于宽的离4的整数倍最近的数值,这样每行的像素可以整数次读取完成。
代码实现:
if (((info_h.biWidth*info_h.biBitCount)%4) == 0)
width = info_h.biWidth/ 8 * info_h.biBitCount;
else
width = (info_h.biWidth*info_h.biBitCount + 31) / 32 * 4;
2.其他代码(具体分析已以注释方式呈现)
(1)main.cpp
#include <stdio.h>
#include <windows.h>
#include "bmp2yuv.h"
void main(int argc, char *argv[])
{
FILE *bmpFile = NULL, *yuvFile = NULL;
BITMAPFILEHEADER File_header;
BITMAPINFOHEADER Info_header;
char* bmpFileName=NULL;
char* yuvFileName=NULL;
unsigned char * rgbBuf = NULL;
unsigned char * yBuff = NULL;
unsigned char * uBuff = NULL;
unsigned char * vBuff = NULL;
int flip = 0;//flip=1时正序,为0时上下翻转
int frame_count = 250;//每图50帧,1bit,4bit,8bit,16bit,24bit各一张,共250帧
int frame_width, frame_height;
//打开yuv文件
yuvFileName=argv[6];
if ((yuvFile = fopen(yuvFileName, "wb")) == NULL)
{
printf("yuv file failed!");
exit(0);
}
else
{
printf("The output yuv file is %s\n",yuvFileName);
}
for (int n = 1; n < 6; n++)
{
// 循环打开bmp文件
if ((bmpFile = fopen(argv[n], "rb")) == NULL)
{
printf("bmp file open failed!");
exit(0);
}
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);
}
//判断是否为bmp文件
if (File_header.bfType != 0x4D42)
{
printf("Not bmp file!");
exit(0);
}
//读信息头信息
if (fread(&Info_header, sizeof(BITMAPINFOHEADER), 1, bmpFile) != 1)
{
printf("read info header error!");
exit(0);
}
frame_width = Info_header.biWidth;
frame_height=Info_header.biHeight;
printf("This is a %d bits image!\n", Info_header.biBitCount);
printf("\nbmp size: \t%d X %d\n", Info_header.biWidth, Info_header.biHeight);
//开辟缓冲区
rgbBuf = (unsigned char *)malloc(frame_height*frame_width * 3);
memset(rgbBuf, 0, frame_height*frame_width * 3);//初始化函数,用于清0
yBuff = (unsigned char *)malloc(frame_height*frame_width);
uBuff = (unsigned char *)malloc((frame_height*frame_width) / 4);
vBuff = (unsigned char *)malloc((frame_height*frame_width) / 4);
ReadRGB(File_header, Info_header, bmpFile, rgbBuf);
if (RGB2YUV(frame_width, frame_height, rgbBuf, yBuff, uBuff, vBuff, flip))
{
printf("rgb2yuv error");
exit(1);
}
for (int i = 0; i < frame_width*frame_height; i++)
{
if (yBuff[i] < 16) yBuff[i] = 16;
if (yBuff[i] > 235) yBuff[i] = 235;
}
for (int i = 0; i < frame_width*frame_height / 4; i++)
{
if (uBuff[i] < 16) uBuff[i] = 16;
if (uBuff[i] > 240) uBuff[i] = 240;
if (vBuff[i] < 16) vBuff[i] = 16;
if (vBuff[i] > 240) vBuff[i] = 240;
}
for (int f = 0; f < 50; f++)
{
fwrite(yBuff, 1, frame_width * frame_height, yuvFile);
fwrite(uBuff, 1, (frame_width * frame_height) / 4, yuvFile);
fwrite(vBuff, 1, (frame_width * frame_height) / 4, yuvFile);
}
}
free(rgbBuf);
free(yBuff);
free(uBuff);
free(vBuff);
fclose(bmpFile);
fclose(yuvFile);
getchar();
}
(2)bmp2yuv.cpp
#include <stdio.h>
#include <windows.h>
#include <math.h>
float RGBYUV02990[256], RGBYUV05870[256], RGBYUV01140[256];
float RGBYUV01684[256], RGBYUV03316[256];
float RGBYUV04187[256], RGBYUV00813[256];
bool MakePalette(FILE * pFile, BITMAPFILEHEADER &file_h, BITMAPINFOHEADER & info_h, RGBQUAD *pRGB_out)
{
/*如果图像开始位置与信息头结束的位置中间,还有2的info_h.biBitCount次方(颜色数)个RGBUAQ空间,则存在调色板*/
if ((file_h.bfOffBits - sizeof(BITMAPFILEHEADER) - info_h.biSize) == sizeof(RGBQUAD)*pow(2, (float)info_h.biBitCount))
{
fseek(pFile, sizeof(BITMAPFILEHEADER) + info_h.biSize, 0);
fread(pRGB_out, sizeof(RGBQUAD), (unsigned int)pow(2, (float)info_h.biBitCount), pFile);
return true;
}
else
return false;
}
//bmp转rgb
void ReadRGB( BITMAPFILEHEADER & file_h, BITMAPINFOHEADER & info_h, FILE * bmpFile,unsigned char * rgbDataOut)
{
unsigned long Loop, i, j;
unsigned char mask=0;
unsigned char *Data;
unsigned long width, height;
/*计算实际的宽高*/
//由于需与DWORD对齐,所以每行需满足为4字节的整数倍,若不是则需补成其整数倍
if (((info_h.biWidth*info_h.biBitCount)%4) == 0)
width = info_h.biWidth/ 8 * info_h.biBitCount;
else
width = (info_h.biWidth*info_h.biBitCount + 31) / 32 * 4;
//判断高是否为偶数,若不是,补齐
if ((info_h.biHeight % 2) == 0)
height = info_h.biHeight;
else
height = info_h.biHeight + 1;
Data = (unsigned char *)malloc(height*width);
//写入数据到Data中
fseek(bmpFile, file_h.bfOffBits, 0);
if (fread(Data, height*width, 1, bmpFile) != 1)
{
printf("read file error!");
exit(0);
}
RGBQUAD *pRGB = (RGBQUAD *)malloc(sizeof(RGBQUAD)*(unsigned int)pow(2,(float)info_h.biBitCount));//把unsigned char 改为unsigned int,否则溢出
if (!MakePalette(bmpFile, file_h, info_h, pRGB))
printf("No palette!");
/*判断深度,根据不同深度做出不同操作*/
//深度为24bit时,不需要调用调色板
if (info_h.biBitCount == 24)
{
memcpy(rgbDataOut, Data, height*width);//将Data中的数据直接拷到rgbDataOut
free(Data);
return;
}
//深度为16bit时,位与移位取像素数据转换为8bit/彩色分量写RGB缓冲区
if (info_h.biBitCount == 16)
{
for (Loop = 0; Loop < height * width ; Loop+=2)//loop每次循环中应该要逐次增加2个字节(16位)。
{
*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;
}
}
//深度小于等于8时,构造调色板
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++;
}
}
free(Data);
free(pRGB);
}
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;
void InitLookupTable();
if (init_done == 0)
{
InitLookupTable();
init_done = 1;
}
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;
*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++;
}
}
// 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()
{
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;
}
}
四.实验结果
1. 1bit
2. 4bit
3. 8bit
4. 16bit
5. 24bit