一、BMP文件格式分析
bmp文件按如下顺序排列
位图文件头BITMAPFILEHEADER |
---|
位图信息头BITMAPINFOHEADER |
调色板Palette |
实际的位图数据ImageData |
其中文件头信息决定读取时判断是否为BMP文件
位图文件头BITMAPFILEHEADER
位图信息头BITMAPINFOHEADER
notice:在进行BMP文件的读写时,要注意其显示顺序和存储顺序在行方向上相反
调色板:
调色板实际上是一个数组,它所包含的元素与位图所具有的颜色数相同,决定于biClrUsed和biBItCount字段。数组中每个元素的类型是一个RGBQUAD结构。真彩色无调色板部分。
二、实验过程
1.变量定义以及main函数命令行参数处理
#include<windows.h>
#include<stdlib.h>
#include<stdio.h>
#include<malloc.h>
int main(char argc, char* argv[])
{
FILE* bmpFile = NULL;
FILE* yuvFile = NULL;
BITMAPFILEHEADER File_header;
BITMAPINFOHEADER Info_header;
int framew, frameh;
unsigned char* rgbData = NULL;
unsigned char* yBuff = NULL;
unsigned char* vBuff = NULL;
unsigned char* uBuff = NULL;
const char* bmpFileName[5];
const char* yuvFileName =NULL;
bool flip = true;
for (int num = 0; num < 5; num++)
{
bmpFileName[num] = argv[num + 1];
}
yuvFileName = argv[6];//输出为argv[6]
for (int i = 0; i < 5; i++)
{
bmpFile = fopen(bmpFileName[i], "rb"); //打开文件argv[1, 2, 3, 4, 5]
}
yuvFile = fopen(yuvFileName, "wb");
2.读取BMP
读取头文件:
if (fread(&File_header, sizeof(BITMAPFILEHEADER), 1, bmpFile) != 1) {
cout << "read file header error" << endl;
exit(0);
}
通过BMP文件头判断文件格式
if (File_header.bfType != 0x4D42) {
cout << "Not bmp file" << endl;
exit(0);
}
else
cout << "this is a bmp file" << endl;
读取BMP头信息:
if (fread(&Info_header, sizeof(BITMAPINFOHEADER), 1, bmpFile) != 1) {
cout << "read info header error" << endl;
exit(0);
}
//读取头信息中图像的宽高
framew = Info_header.biWidth;
frameh = Info_header.biHeight;
3.RGBtoYUV
(1)转换公式
根据上学期学过的电视原理知识我们知道如下公式:
在C++中使用按位与计算:
Y = (66 * R + 129 *G + 25 * B) >> 8 + 16;
U = (-38 * R - 74 *G + 112 * B) >> 8 + 128;
V = (112 * R - 94 *G - 18 * B) >> 8 + 128;
(2)下采样
下采样,就是RGB转换为YUV图像时,会产生同样多个数量的Y\U\V,若YUV想要4:2:0的采样制式,则需要进行下采样。
具体实现方法:U\V同理,以U为例。将相邻的四个U计算加权平均值,合成为一个。
(3)查找表
即将所有分量转换后对应的值存储在一个数组里,使用时可以直接取用,是一种以空间为代价换时间效率的策略。
part1:从BMP中读取RGB的信息
void ReadRGB(FILE* pFile, BITMAPFILEHEADER& file_h, BITMAPINFOHEADER& info_h, unsigned char* rgbDataOut){
unsigned long Loop, iLoop, jLoop, width, height, w, h;
unsigned char mask, * Index_Data, * Data;
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;
width = w / 8 * info_h.biBitCount;
height = h;
Index_Data = (unsigned char*)malloc(height * width);
Data = (unsigned char*)malloc(height * width);
fseek(pFile, file_h.bfOffBits, 0);
if (fread(Index_Data, height * width, 1, pFile) != 1) {
//printf("read file error!");
cout << "read file error" << endl;
exit(0);
}
for (iLoop = 0; iLoop < height; iLoop++) {
for (jLoop = 0; jLoop < width; jLoop++) {
Data[iLoop * width + jLoop] = Index_Data[(height - iLoop - 1) * width + jLoop];
}
}
if (info_h.biBitCount == 24){//24bit读取BMP文件 不需调色盘
memcpy(rgbDataOut, Data, height * width);
free(Index_Data);
free(Data);
return;
}
//判断深度选取不同调色板
RGBQUAD* pRGB = (RGBQUAD*)malloc(sizeof(RGBQUAD) * (unsigned int)pow(2, info_h.biBitCount));
if (!MakePalette(pFile, file_h, info_h, pRGB))
//printf("No palette!");
cout << "No palette!" << endl;
if (info_h.biBitCount == 16) {//16bitBMP文件的操作
if (info_h.biCompression == BI_RGB) {
for (Loop = 0; Loop < height * width; Loop += 2) {
*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;
}
}
free(Index_Data);
free(Data);
return;
}
for (Loop = 0; Loop < height * width; Loop++) {//1-8bit
switch (info_h.biBitCount) {
case 1:
mask = 0xC0;
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(Index_Data);
free(Data);
free(pRGB);
}
part2:调色板
bool MakePalette(FILE* pFile, BITMAPFILEHEADER& file_h, BITMAPINFOHEADER& info_h, RGBQUAD* pRGB_out) {
if ((file_h.bfOffBits - sizeof(BITMAPFILEHEADER) - info_h.biSize) == sizeof(RGBQUAD) * pow(2, info_h.biBitCount)) {
fseek(pFile, sizeof(BITMAPFILEHEADER) + info_h.biSize, 0);
fread(pRGB_out, sizeof(RGBQUAD), (unsigned int)pow(2, info_h.biBitCount), pFile);
return true;
}
else
return false;
}
part3:RGBtoYUV函数以及查找表
//部分查找表
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;
}
}
int RGB2YUV(unsigned long w, unsigned long h, unsigned char* rgbData, unsigned char* y, unsigned char* u, unsigned char* v,int flip) {
initLookupTable();//初始化查找表
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;
//对每个像素进行rgb2yuv转换
for (i = 0, nSize = 0; nSize < w * h * 3; nSize += 3) {
nb = rgbData[nSize];
ng = rgbData[nSize + 1];
nr = rgbData[nSize + 2];
y[i] = (unsigned char)(RGBYUV02990[nr] + RGBYUV05870[ng] + RGBYUV01140[nb]);
utemp[i] = (unsigned char)(-RGBYUV01684[nr] - RGBYUV03316[ng] + nb / 2 + 128);
vtemp[i] = (unsigned char)(nr / 2 - RGBYUV04187[ng] - RGBYUV00813[nb] + 128);
i++;
}
//对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++;
}
}
//对yuv信号进行限量化电平处理
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;
}
free(utemp);
free(vtemp);
return 0;
}
主函数中的调用:
rgbData = (unsigned char*)malloc(framew * frameh * 3);
yBuff = (unsigned char*)malloc(framew * frameh);
uBuff = (unsigned char*)malloc(framew * frameh / 4);
vBuff = (unsigned char*)malloc(framew * frameh / 4);
//调用ReadRGB函数,从BMP中读取RGB信息
ReadRGB(bmpFile, File_header, Info_header, rgbData);
//调用RGB2YUV文件
if (RGB2YUV(framew, frameh, rgbData, yBuff, uBuff, vBuff,flip)) {
//printf("error");
cout << "error" << endl;
return 0;
}
for (int i = 0; i < framew * frameh; i++) {
if (yBuff[i] < 16)
yBuff[i] = 16;
if (yBuff[i] > 235)
yBuff[i] = 235;
}
for (int i = 0; i < framew * frameh / 4; i++) {
if (uBuff[i] < 16)
uBuff[i] = 16;
if (vBuff[i] < 16)
vBuff[i] = 16;
if (uBuff[i] > 240)
uBuff[i] = 240;
if (vBuff[i] > 240)
vBuff[i] = 240;
}
for (int i = 0; i < 40; i++) {
fwrite(yBuff, 1, framew * frameh, yuvFile);
fwrite(uBuff, 1, (framew * frameh) / 4, yuvFile);
fwrite(vBuff, 1, (framew * frameh) / 4, yuvFile);
}
free(rgbData);
free(yBuff);
free(vBuff);
free(uBuff);
fclose(bmpFile);
}
三、实验结果
命令行参数设置:
使用YUV播放器播放: