实验项目名称:图像文件的读写和转换
实验目的: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