格式转换——bmp 2 yuv
一、BMP文件的组成结构
BitMap(缩略为Bitmap)是Windows操作系统中广泛采用的标准图像文件格式。该技术主要分为两种类型:设备相关位图(DDB)和设备无关位图(DIB)。该种存储格式在大多数应用场景中未采用其他压缩方式。支持的图像深度包括1bit、4bit、8bit、16bit和24bit五种选择。该技术规定了从左到右、自下而上的扫描顺序。作为Windows系统中广泛使用的图形交换格式之一,在基于BitMap技术开发的应用程序通常均支持BMP文件格式。
典型的BMP图像文件组成 | 位图头文件数据结构,它包含BMP图像文件的类型、显示内容等信息 |
| --- |
|---|
| 调色板,这个部分是可选的; 有些位图需要调色板,有些位图,比如真彩色图(24位的BMP)就不需要调色板 |
| 位图数据,这部分的内容根据BMP位图使用的位数不同而不同,在24位图中直接使用RGB,而其他的小于24位的使用调色板中颜色索引值。 |
1. 位图文件头主要包括:

2.位图信息头主要包括:

- 调色板本质上是一个数组;它所包含的元素数量与位图的颜色数量一致;这两个参数决定了元素的数量:biClrUsed和biBitCount;每个元素都是RGBQUAD结构类型;真彩色的部分没有调色板支持。

4.紧随其后的就是图像数据的字节数组。对于那些使用调色板的位图来说,图像数据即为该像素颜色在调色板中的索引值(逻辑色彩)。而对于真彩色图像而言,则是其实际的RGB值。
该图像数据按照每条扫描行由连续编码像素信息构成的方式组织存储。其中每条扫描行所包含的信息量受色彩数量与每像素占用宽度因素的影响而不同。在实际应用中要求系统保证每条扫描行的数据长度必须是4个位节(DWORD)的整体倍数以便于后续处理操作进行有序执行。
扫描行数据是以自底向上的顺序存储。这表明,在阵列中第一个位置对应的字节代表了位于位图左下角的那个像素,在末尾位置对应的最后一个字节则对应着位于右上角的那个像素。
注:本次实验以24bit的真彩色图像为例,故不需要调色板。
二、字节序
不同类型的计算机系统采用了各自独特的数据存储方式,
同一个4-byte 32-bit integer在同一台计算机上仍会以一致的方式被表示出来。
数据的存储顺序可分为两大类:一种是"小端"(Little Endian),另一种是"大端"(Big Endian)。
其中,“小端”指的是低位的数据单元被存放在内存中较靠前的位置,“大端”则相反——高阶的数据单元被存放在内存中较靠前的位置。
多数现代处理器采用"小端"架构,
而像Motorola这样的设备则倾向于采用"大端"架构。
按照TCP/IP协议的设计规范,“大端”的概念已经被广泛应用于网络层以上的所有协议中,
因此人们通常将这种特定的大端编码称为网络码元(Byte Order, BOB)。
BMP文件在记录数据时遵循图像从左至右、自下而上的扫描规律;而RGB文件则采用自左至右、自上而下的记录方式。因此,在将BMP文件转换为RGB格式的过程中需要注意‘逆序处理’的问题。
//从24bit的bmp提取rgb数据时 倒序转正序写入缓存区
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width * 3; j++)
{
*(rgb + (height - 1 - i) * width * 3 + j) = *tmp_rgb;
tmp_rgb++;
}
}
三、实验核心代码
1. 头文件head.h定义
#pragma once
#include<windows.h>
#define uchar unsigned char
int rgb2yuv(int x_dim, int y_dim, void* bmp, void* y_out, void* u_out, void* v_out);
int bmp2rgb(int width, int height, FILE* bmpFile, unsigned char* rgb);
void InitLookupTable();
在<windows.h> 头文件中,定义BITMAPFILEHEADER 结构体。
2.main函数定义变量,指针和输出文件。
//定义变量
BITMAPFILEHEADER FILE_header;
BITMAPINFOHEADER INFO_header;
//缓冲buffer,用来存储数据,完成读写操作
FILE* bmpFile;
FILE* yuvFile;
uchar* rgbBuf;
uchar* yBuf;
uchar* uBuf;
uchar* vBuf;
//存储图片宽和高
int FWidth;
int FHeight;
//创建指向图片的指针
const int picNum = 7;
const char* bmpFileName[picNum] = { "djr0.bmp","djr1.bmp","djr2.bmp","djr3.bmp","djr4.bmp","djr5.bmp","djr6.bmp" };
const char* yuvFileName = "result.yuv";
//创建新的输出文件
fopen_s(&yuvFile, yuvFileName, "wb");
3.逐个对bmp图片进行转换处理
(1)打开后根据文件头读取判断
//打开每个bmp文件
fopen_s(&bmpFile, bmpFileName[i], "rb");
//读取bmp文件头
if (fread(&FILE_header, sizeof(BITMAPFILEHEADER), 1, bmpFile) != 1)
{
cout << "read file header error!" << endl;
exit(0);
}
else {
cout << "read file header success!" << endl;
}
//判断是否是bmp文件类型
if (FILE_header.bfType != 0x4D42) //此处是bmp的小端模式的数据
{
cout << "not the bmpFile!" << endl;
exit(0);
}
else {
cout << "this is a bmp file!" << endl;
}
(2)根据bmp信息头,直接调用结构体实例,读取rgb数据
//读取bmp信息头
if (fread(&INFO_header, sizeof(BITMAPINFOHEADER), 1, bmpFile) != 1)
{
cout << "read info header error!" << endl;
exit(0);
}
else {
cout << "read info header success!" << endl;
}
//通过结构体实例的成员函数,来调用参数
FWidth = INFO_header.biWidth;
FHeight = INFO_header.biHeight;
//给操作指针分配内存空间
rgbBuf = (uchar*)malloc(FWidth * FHeight * 3);
yBuf = (uchar*)malloc(FWidth * FHeight);
uBuf = (uchar*)malloc(FWidth * FHeight / 4);
vBuf = (uchar*)malloc(FWidth * FHeight / 4);
//读取bmp文件的rgb数据
bmp2rgb(FWidth, FHeight, bmpFile, rgbBuf);
//测试rgb数据是否传输成功
//fwrite(rgbBuf, 1, FHeight * FWidth * 3, yuvFile);

如果直接用YUVplayer打开传输的rgb文件,是无法正常显示图片。
(3)倒序读取后的rgb,转换成yuv数据。
int rgb2yuv(int width, int height, void* bmp, void* y_out, void* u_out, void* v_out)
{
//变量定义
static int init_Done = 0;
uchar* r, * g, * b;
uchar* y, * u, * v;
uchar* y_buf, * u_buf, * v_buf;
uchar* sub_u_buf, * sub_v_buf;
uchar* s_u, * s_v;
uchar* cu1, * cu2, * cv1, * cv2;
long size;
//定义查找表算
if (init_Done == 0)
{
InitLookupTable();
init_Done = 1;
}
//操作指针分配内存空间
size = width * height;
y_buf = (uchar*)y_out;
sub_u_buf = (uchar*)u_out;
sub_v_buf = (uchar*)v_out;
u_buf = (uchar*)malloc(size * sizeof(uchar));
v_buf = (uchar*)malloc(size * sizeof(uchar));
//rgb2yuv
b = (uchar*)bmp;
y = y_buf;
u = u_buf;
v = v_buf;
for (long i = 0; i < size; i++)
{
g = b + 1;
r = b + 2;
*y = (uchar)(RGBYUV02990[*r] + RGBYUV05870[*g] + RGBYUV01140[*b]);
*u = (uchar)(-RGBYUV01684[*r] - RGBYUV03316[*g] + (*b) / 2 + 128);
*v = (uchar)((*r) / 2 - RGBYUV04187[*g] - RGBYUV00813[*b] + 128);
//限定yuv的范围,防止溢出
if (*y < 16) *y = 16;
if (*y > 235) *y = 235;
if (*u < 16) *u = 16;
if (*u > 240) *u = 240;
if (*v < 16) *v = 16;
if (*v > 240) *v = 240;
b += 3;
y++;
u++;
v++;
}
//转换后的数组是4:4:4格式,采样成4:2:2格式
for (long j = 0; j < height / 2; j++)
{
s_u = sub_u_buf + j * width / 2;
s_v = sub_v_buf + j * width / 2;
//在没采样的数据中操作位置的指针
cu1 = u_buf + 2 * j * width;
cv1 = v_buf + 2 * j * width;
cu2 = u_buf + (2 * j + 1) * width;
cv2 = v_buf + (2 * j + 1) * width;
//使用均值进行赋值
for (long i = 0; i < width / 2; i++)
{
*s_u = (*cu1 + *(cu1 + 1) + *cu2 + *(cu2 + 1)) / 4;
*s_v = (*cv1 + *(cv1 + 1) + *cv2 + *(cv2 + 1)) / 4;
s_u++;
s_v++;
cu1 += 1;
cv1 += 2;
cu2 += 2;
cv2 += 2;
}
}
//释放临时变量
free(u_buf);
free(v_buf);
return 0;
}
其中定义了查找表,采用部分查找表法,可以提高运行效率
static float RGBYUV02990[256], RGBYUV05870[256], RGBYUV01140[256];
static float RGBYUV01684[256], RGBYUV03316[256];
static float RGBYUV04187[256], RGBYUV00813[256];
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;
}
4.设置每个画面出现的帧数,循环写入文件
//命令行中设置每幅图循环次数
int k;
cout << "Enter the number of picture cycles:" << endl;
cin >> k;
//每幅图循环k次
for (int i = 0; i < k; i++)
{
fwrite(yBuf, 1, FWidth * FHeight, yuvFile);
fwrite(uBuf, 1, (FWidth * FHeight) / 4, yuvFile);
fwrite(vBuf, 1, (FWidth * FHeight) / 4, yuvFile);
}
//cout << "write yuvFile success!" << endl;
5.释放空间,关闭文件
free(rgbBuf);
free(yBuf);
free(uBuf);
free(vBuf);
fclose(bmpFile);
四、实验结果

