文件格式转换(BMP to YUV)
BMP图片获取及处理
选择好需要使用的图像,用系统(Windows)自带的画图软件打开所选择的文件,加上文字标识并另存为bmp文件。
-
所选择的图像大小:720 x 480
-
保存的格式为:24位bmp
BMP文件格式简介
BMP图像文件往往由四部分组成:
- 位图文件头:它包含BMP图像文件的类型、显示内容等信息;
typedef struct tagBITMAPFILEHEADER {
WORD bfType; /* 说明文件的类型 */
DWORD bfSize; /* 说明文件的大小,用字节为单位*/
WORD bfReserved1; /*保留,设置为0 */
WORD bfReserved2; /*保留,设置为0 */
DWORD bfOffBits; /* 说明从BITMAPFILEHEADER结构开始到实际的图像数据之间的字节偏移量 */
} BITMAPFILEHEADER;
- 位图信息头:它包含有BMP图像的宽、高、压缩方法,以及定义颜色等信息
typedef struct tagBITMAPINFOHEADER {
DWORD biSize; /*说明结构体所需字节数*/
DWORD biWidth; /*以像素为单位说明图像的宽度*/
DWORD biHeight; /*以像素为单位说明图像的高速*/
WORD biPlanes; /*说明位面数,必须为1 */
WORD biBitCount; /*说明位数/像素,1、2、4、8、24 */
DWORD biCompression; /*说明图像是否压缩及压缩类型BI_RGB,BI_RLE8,BI_RLE4,BI_BITFIELDS */
DWORD biSizeImage; /*以字节为单位说明图像大小,必须是4的整数倍*/
DWORD biXPelsPerMeter; /*目标设备的水平分辨率,像素/米 */
DWORD biYPelsPerMeter; /*目标设备的垂直分辨率,像素/米 */
DWORD biClrUsed; /* 说明图像实际用到的颜色数,如果为0则颜色数为2的biBitCount次方 */
DWORD biClrImportant; /*说明对图像显示有重要影响的颜色索引的数目,如果是0,表示都重要。*/
} BITMAPINFOHEADER;
- 调色板:这个部分是可选的,有些位图需要调色板,有些位图,比如真彩色图(24位的BMP)就不需要调色板;
typedef struct tagRGBQUAD {
BYTE rgbBlue; /*指定蓝色分量*/
BYTE rgbGreen; /*指定绿色分量*/
BYTE rgbRed; /*指定红色分量*/
BYTE rgbReserved; /*保留,指定为0*/
} RGBQUAD;
- 位图数据:这部分的内容根据BMP位图使用的位数不同而不同,在24位图中直接使用RGB,而其他的小于24位的使用调色板中颜色索引值
程序流程及主要代码
-
文件读入
//重建读入文件名,并用fopen函数打开文件 strcpy_s(name_in, argv[1]); _itoa_s(i, str, 20, 10); strcat_s(name_in, 20, str); strcat_s(name_in, 20, ".bmp"); fopen_s(&bmpFile, name_in, "rb");
-
获取位图文件头、位图信息头
//读取位图文件头 fread(&File_header, sizeof(BITMAPFILEHEADER), 1, bmpFile); //读取位图信息头 fread(&Info_header, sizeof(BITMAPINFOHEADER), 1, bmpFile);
-
从bmp文件中读出rgb信息
- bmp文件存储形式为从左往右、从下往上存储,这里进行正序读取,倒序输出;也可以直接进行倒序读取,正序输出
- rgb的存储顺序为b->g->r
void READrgb(FILE* pFile, const BITMAPFILEHEADER& file_h, const BITMAPINFOHEADER& info_h, unsigned char* rgbDataOut) { unsigned long width, height; unsigned char * raw_Data, * Data; //对于24位bmp文件,有效的数据大小为:长*宽*3字节(24bit) width = info_h.biWidth * 3; height = info_h.biHeight; //倒序前数据缓存区 raw_Data = (unsigned char*)malloc(height * width);//buffer大小应该与bmp中有效数据大小相同 //倒序后数据缓存区,用于存放bmp中的有效数 Data = (unsigned char*)malloc(height * width);//buffer大小应该与bmp中有效数据大小相同 //令文件指针指向实际有效数据处:需要使用到文件头中的有效数据偏移量 fseek(pFile, file_h.bfOffBits, 0); if (fread(raw_Data, height * width, 1, pFile) != 1) { printf("read file error!"); exit(0); } //倒序存放 for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { Data[i * width + j] = raw_Data[(height - i - 1) * width + j]; } } //将缓存区中倒序后的数据复制到输出缓存区(24bit位图可直接写) memcpy(rgbDataOut, Data, height * width); if (raw_Data) free(raw_Data); if (Data) free(Data); }
-
将rgb信息转换成yuv信息
-
RGB转换成YUV公式:
Y = 0.30 R + 0.59 G + 0.11 B Y=0.30R+0.59G+0.11B Y=0.30R+0.59G+0.11BU = 0.493 ( B - Y ) U=0.493(B-Y) U=0.493(B-Y)
V = 0.877 ( R - Y ) V=0.877(R-Y) V=0.877(R-Y)
-
4:2:0 yuv格式采样公式:
四 个 Y 公 用 的 U = 四 个 Y 对 应 的 U 之 和 / 4 四个Y公用的 U = 四个Y对应的U之和/4 四个Y公用的U=四个Y对应的U之和/4四 个 Y 公 用 的 V = 四 个 Y 对 应 的 V 之 和 / 4 四个Y公用的 V = 四个Y对应的V之和/4 四个Y公用的V=四个Y对应的V之和/4
-
在转换后yuv会出现非正常取值(噪点),需要对此进行处理
void 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; utemp = (unsigned char*)malloc(w * h); vtemp = (unsigned char*)malloc(w * h); unsigned long i, nr, ng, nb, nSize; //进行 rgb到yuv的转换 for (i = 0, nSize = 0; nSize < w * h * 3; nSize += 3) { //分别提取每个像素的r、g、b值 nb = rgbData[nSize]; ng = rgbData[nSize + 1]; nr = rgbData[nSize + 2]; //根据转换公式进行yuv转换 y[i] = (unsigned char)(0.2990 * nr + 0.5870 * ng + 0.1140 * nb); utemp[i] = (unsigned char)(-0.168 * nr - 0.332 * ng + 0.5 * nb + 128); vtemp[i] = (unsigned char)(0.5 * nr - 0.419 * ng - 0.081 * nb + 128); i++; } //根据4:2:0格式对u、v进行采样 int k = 0; for (i = 0; i < h; i += 2) { for (unsigned long j = 0; j < w; j += 2) { u[k] = (utemp[i * w + j] + utemp[(i + 1) * w + j] + utemp[i * w + j + 1] + utemp[(i + 1) * w + j + 1]) / 4; v[k] = (vtemp[i * w + j] + vtemp[(i + 1) * w + j] + vtemp[i * w + j + 1] + vtemp[(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 < h * w / 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 (utemp) free(utemp); if (vtemp) free(vtemp); }
-
-
文件写入
//构造写出文件名并创建文件 strcpy_s(name_out, argv[1]); strcat_s(name_out, 20, ".yuv"); fopen_s(&yuvFile, name_out, "wb"); //根据命令行参数设定的帧数写入文件 for (int m = 0; m < atoi(argv[2+i]); m++) { //将转好的yuv写到文件中 flag = WriteYUV(yBuffer, uBuffer, vBuffer, width * height, yuvFile); }
bool 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; }
主程序完整代码
int main(int argc, char** argv)
{
BITMAPFILEHEADER File_header;
BITMAPINFOHEADER Info_header;
FILE* bmpFile;
FILE* yuvFile;
unsigned char* rgbBuffer, * yBuffer, * uBuffer, * vBuffer;
char name_out[20];
//构造写出文件名并创建文件
strcpy_s(name_out, argv[1]);
strcat_s(name_out, 20, ".yuv");
fopen_s(&yuvFile, name_out, "wb");
if (!yuvFile)
{
printf("yuvfile open error!\n");
exit(0);
}
//根据命令行参数打开文件并进行转换操作
for (int i = 1; i <= atoi(argv[2]); i++) { //atoi():将字符串转换成整数
char str[20];
char name_in[20];
//重建读入文件名,并用fopen函数打开文件
strcpy_s(name_in, argv[1]);
_itoa_s(i, str, 20, 10);
strcat_s(name_in, 20, str);
strcat_s(name_in, 20, ".bmp");
fopen_s(&bmpFile, name_in, "rb");
//保证bmp文件能够打开
if (bmpFile)
{
//读取位图文件头
fread(&File_header, sizeof(BITMAPFILEHEADER), 1, bmpFile);
//读取位图信息头
fread(&Info_header, sizeof(BITMAPINFOHEADER), 1, bmpFile);
//判断文件类型
if (File_header.bfType != 0x4D42)
{
printf("Not bmp file!\n");
//exit(0);
break;
}
unsigned long width, height;
width = Info_header.biWidth;
height = Info_header.biHeight;
//开辟缓冲区
rgbBuffer = new unsigned char[height * width * 3];
yBuffer = new unsigned char[height * width];
uBuffer = new unsigned char[height * width / 4];
vBuffer = new unsigned char[height * width / 4];
//读取rgb信息
READrgb(bmpFile, File_header, Info_header, rgbBuffer);
//将rgb数据转为yuv
rgb2yuv(width, height, rgbBuffer, yBuffer, uBuffer, vBuffer);
bool flag;
//根据命令行参数设定的帧数写入文件
for (int m = 0; m < atoi(argv[2+i]); m++) {
//将转好的yuv写到文件中
flag = WriteYUV(yBuffer, uBuffer, vBuffer, width * height, yuvFile);
}
//判断文件成功写入
if (flag)
{
printf("YUVfile written!\n");
}
else
{
break;
}
if (rgbBuffer)
free(rgbBuffer);
if (yBuffer)
free(yBuffer);
if (uBuffer)
free(uBuffer);
if (vBuffer)
free(vBuffer);
fclose(bmpFile);
}
else {
printf("bmpfile open error!\n");
break;
}
}
fclose(yuvFile);
return 0;
}
命令行参数及bmp存储文件名说明
argv[1]
:bmp文件名前缀
argv[2]
:bmp文件数
argv[3]~argv[n]
:每帧图像重复帧数
实验结果
- bmp文件准备:统一命名为
文件名前缀n.bmp
(这里直接用的另一门课上生成的图进行操作了),存储在工程同一目录下
r
- 生成的yuv文件
其他注意事项
在构造文件头、信息头结构体时成员变量的长度并不相同,会有以下问题:
sizeof(BITMAPFILEHEADER)
、sizeof(BITMAPINFOHEADER)
得到的结果与定义的结构体总长不同- 字节对齐问题,在使用
fread()
函数进行读取时出现有字节被跳过的现象而导致实验失败
这部分内容具体讲解参考:https://www.cnblogs.com/wuyudong/p/memory-alignment.html