實驗目的:
1.了解bmp檔案的基本組成;
2.實作bmp序列向yuv檔案的轉換(基本要求為24bit的bmp檔案,進階要求為支援小于24bit的bmp檔案),并用YUV Viewer播放驗證。
BMP檔案格式:
典型的 BMP 圖像檔案由四部分組成:
(1)位圖頭檔案資料結構,它包含 BMP 圖像檔案的類型、顯示内容等資訊;
位圖檔案頭:
typedef struct tagBITMAPFILEHEADER {WORD bfType;
LONG biXPelsPerMeter;
LONG biYPelsPerMeter;
DWORD biClrImportant; /說明對圖像顯示有重要影響的顔色索引的數目,如果是 0,表示都重要。/
} BITMAPINFOHEADER;
(3)調色闆,這個部分是可選的,有些位圖需要調色闆,有些位圖,比如真彩色圖(24位的 BMP)就不需要調色闆;
本實驗的BMP檔案為24位的真彩色圖,不需要調色闆。
(4)位圖資料,這部分的内容根據 BMP 位圖使用的位數不同而不同,在 24 位圖中直接使用 RGB,而其他的小于 24 位的使用調色闆中顔色索引值。
R、G、B所在位置,直接把此段資料當做普通RGB檔案進行處理即可。
使用windows.h中的BITMAPFILEHEADER 和BITMAPINFOHEADER 即可直接讀出位圖檔案頭和位圖資訊頭,并使用其.biWidth和.biHeight直接讀出圖像的寬和高,并對剩餘資料進行普通的RGBtoYUV轉換,分别對多個BMP檔案進行以上操作并将得到的資料放入同一YUV檔案中即可獲得YUV視訊,通過循環寫入的次數控制每幅圖的幀長。
main函數
#include<windows.h>
#include<stdlib.h>
#include<stdio.h>
#include<malloc.h>
#include"bmp2yuv.h"
int main(char argc,char *argv[])
{
int height,width;
BITMAPFILEHEADER File_header;
BITMAPINFOHEADER Info_header;
char* bmpfile_name[10];
char* yuvfile_name=NULL;
FILE* bmp;
FILE* yuv;
unsigned char *rgb_buffer,*y_buffer,*u_buffer,*v_buffer;
bool flip=true;
yuvfile_name=argv[11];
yuv=fopen(yuvfile_name,"wb");
if(yuv==NULL)
{
printf("cannot find yuv file!\n");
exit(1);
}
else
{
printf("The input yuv file is %s\n",yuvfile_name);
}
/*一共10張bmp圖檔*/
/*argv[1]到argv[10]用來表示這10張bmp圖檔*/
for(int num=0;num<10;num++)
{
bmpfile_name[num]=argv[num+1];
}
height=atoi(argv[12]);
width=atoi(argv[13]);
for(int i=0;i<10;i++)
{
bmp=fopen(bmpfile_name[i],"rb");
if(bmp==NULL)
{
printf("cannot find bmp file!\n");
exit(1);
}
else
{
printf("The input bmp file is %s\n",bmpfile_name[i]);
}
if((fread(&File_header,sizeof(BITMAPFILEHEADER),1,bmp))!=1)
{
printf("Read File header error!");
exit(0);
}
/*判斷是否為bmp檔案*/
if(File_header.bfType!=0x4D42)
{
printf("Not bmp file!");
exit(0);
}
else
{
printf("This is bmp file!\n");
}
if((fread(&Info_header,sizeof(BITMAPINFOHEADER),1,bmp))!=1)
{
printf("Read Info header error!");
exit(0);
}
/*判斷行數是否為4的倍數*/
if(Info_header.biWidth%4==0)
{
width=Info_header.biWidth;
}
else
{
width=(Info_header.biWidth*Info_header.biBitCount+31)/32*(32/8);
//每行的像素數*每個像素的位數=每行像素的總位數,必須為32bit的整數倍(4位元組)
//做法:(x+31)/32*(32/8)
}
/*判斷列數是否為偶數*/
if(Info_header.biHeight%2==0)
{
height=Info_header.biHeight;
}
else
{
height=Info_header.biHeight+1;
}
int size=width*height;
rgb_buffer=(unsigned char*)malloc(size*3);
y_buffer=(unsigned char*)malloc(size);
u_buffer=(unsigned char*)malloc(size/4);
v_buffer=(unsigned char*)malloc(size/4);
/*從bmp得到rgb資料*/
read_bmp_2_get_rgb(bmp,rgb_buffer,File_header,Info_header);
/*rgb轉yuv*/
if(RGB2YUV (width, height, rgb_buffer, y_buffer, u_buffer, v_buffer, flip))
{
printf("rgb2yuv failed!\n");
}
else
{
printf("rgb2yuv successfully!\n");
}
int j=0;
for(j=0;j<size;j++)
{
if(y_buffer[j]<16) y_buffer[j]=16;
if(y_buffer[j]>235) y_buffer[j]=235;
}
for(j=0;j<size/4;j++)
{
if(u_buffer[j]<16) y_buffer[j]=16;
if(u_buffer[j]>240) y_buffer[j]=240;
}
for(j=0;j<size/4;j++)
{
if(v_buffer[j]<16) v_buffer[j]=16;
if(v_buffer[j]>240) v_buffer[j]=240;
}
/*每張bmp圖檔播放30幀*/
for(int k=0;k<30;k++)
{
fwrite(y_buffer,1,size,yuv);
fwrite(u_buffer,1,size/4,yuv);
fwrite(v_buffer,1,size/4,yuv);
}
free(rgb_buffer);
free(y_buffer);
free(u_buffer);
free(v_buffer);
}
fclose(yuv);
return 0;
}
頭檔案bmp2yuv.h
```cpp
#include"stdio.h"
#include"windows.h"
void read_bmp_2_get_rgb(FILE* bmp,unsigned char* rgb_out,BITMAPFILEHEADER &file_header,BITMAPINFOHEADER &info_header);
int RGB2YUV (int x_dim, int y_dim, void *bmp, void *y_out, void *u_out, void *v_out, int flip);
void InitLookupTable();
read_bmp_2_get_yuv函數
在#include"stdlib.h"
#include"stdio.h"
#include"windows.h"
#include"malloc.h"
void read_bmp_2_get_rgb(FILE* bmp,unsigned char* rgb_out,BITMAPFILEHEADER &file_header,BITMAPINFOHEADER &info_header)
{
int width,height,w,h;
unsigned char *inverse_buffer,*buffer;
int x=0,y=0;
//行數為4的倍數
if(info_header.biWidth%4==0)
{
w=info_header.biWidth;
}
else
{
w=(info_header.biWidth*info_header.biBitCount+31)/32*(32/8);
}
//列數為偶數
if(info_header.biHeight%2==0)
{
h=info_header.biHeight;
}
else
{
h=info_header.biHeight+1;
}
width=w/8*info_header.biBitCount;
height=h;
inverse_buffer=(unsigned char*)malloc(height*width);
buffer=(unsigned char*)malloc(height*width);
//将bmp指針定位到有效資料的起始處
fseek(bmp,file_header.bfOffBits,0);
//int fseek(FILE *stream, long offset, int fromwhere);
//函數設定檔案指針stream的位置。
//如果執行成功,stream将指向以fromwhere為基準,偏移offset(指針偏移量)個位元組的位置,函數傳回0。
fread(inverse_buffer,height*width,1,bmp);
for(y=0;y<height;y++)
{
for(x=0;x<width;x++)
{
buffer[y*width+x]=inverse_buffer[width*(height-y-1)+x];
}
}
if(info_header.biBitCount==24)
{
memcpy(rgb_out,buffer,height*width);
free(inverse_buffer);
free(buffer);
}
}
這裡插入代碼片
rgb2yuv函數
在#include"stdlib.h"
#include"stdio.h"
#include"windows.h"
#include"malloc.h"
void read_bmp_2_get_rgb(FILE* bmp,unsigned char* rgb_out,BITMAPFILEHEADER &file_header,BITMAPINFOHEADER &info_header)
{
int width,height,w,h;
unsigned char *inverse_buffer,*buffer;
int x=0,y=0;
//行數為4的倍數
if(info_header.biWidth%4==0)
{
w=info_header.biWidth;
}
else
{
w=(info_header.biWidth*info_header.biBitCount+31)/32*(32/8);
}
//列數為偶數
if(info_header.biHeight%2==0)
{
h=info_header.biHeight;
}
else
{
h=info_header.biHeight+1;
}
width=w/8*info_header.biBitCount;
height=h;
inverse_buffer=(unsigned char*)malloc(height*width);
buffer=(unsigned char*)malloc(height*width);
//将bmp指針定位到有效資料的起始處
fseek(bmp,file_header.bfOffBits,0);
//int fseek(FILE *stream, long offset, int fromwhere);
//函數設定檔案指針stream的位置。
//如果執行成功,stream将指向以fromwhere為基準,偏移offset(指針偏移量)個位元組的位置,函數傳回0。
fread(inverse_buffer,height*width,1,bmp);
for(y=0;y<height;y++)
{
for(x=0;x<width;x++)
{
buffer[y*width+x]=inverse_buffer[width*(height-y-1)+x];
}
}
if(info_header.biBitCount==24)
{
memcpy(rgb_out,buffer,height*width);
free(inverse_buffer);
free(buffer);
}
}
這裡插入代碼片
實驗結果
輸出檔案
用yuv viewer打開: