其他分享
首页 > 其他分享> > BMP2YUV文件转化

BMP2YUV文件转化

作者:互联网

BMP2YUV文件转化

bmp文件
由四个部分组成:
1.位图文件头
2. 位图信息头
3. 调色板
4.实际的位图数据

24bit进行转化
(1)位图头文件数据结构,它包含 BMP图像文件的类型、显示内容等信息;
(2)位图信息数据结构,它包含有BMP图像的宽、高压缩方法,以及定义颜色等信息;
(3)调色板,这个部分是可选的,有些位图需要调色板,有些位图需要调色板,比如真彩(24bit的BMP)就不需要调色板;
(4)位图数据,这部分的内容根据位图使用的位数不同而不同,在24位图中直接使用RGB,而其他的小于24位的使用调色板中颜索引值。

主函数
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <math.h>
#include “3.h”
typedef struct tagBITMAPFILEHEADER {
WORD bfType;
/* 说明文件的类型 /
DWORD bfSize;
/
说明文件的大小,用字节为单位*/
/注意此处的字节序问题/
WORD bfReserved1;
/*保留,设置为0 */
WORD bfReserved2;
/*保留,设置为0 /
DWORD bfOffBits;
/
说明从BITMAPFILEHEADER结构
开始到实际的图像数据之间的字节偏移量 */
} BITMAPFILEHEADER;

typedef struct tagBITMAPINFOHEADER {
DWORD biSize;
/说明结构体所需字节数/
LONG biWidth;
/以像素为单位说明图像的宽度/
LONG biHeight;
/以像素为单位说明图像的高速/
WORD biPlanes;
/*说明位面数,必须为1 */
WORD biBitCount;
/*说明位数/像素,1、2、4、8、24 */
DWORD biCompression;
/*说明图像是否压缩及压缩类型BI_RGB,BI_RLE8,BI_RLE4,BI_BITFIELDS */
DWORD biSizeImage;
/以字节为单位说明图像大小,必须是4的整数倍/
LONG biXPelsPerMeter;
/*目标设备的水平分辨率,像素/米 */
LONG biYPelsPerMeter;
/*目标设备的垂直分辨率,像素/米 /
DWORD biClrUsed;
/
说明图像实际用到的颜色数,如果为0则颜色数为2的biBitCount次方 */
DWORD biClrImportant;
/说明对图像显示有重要影响的颜色索引的数目,如果是0,表示都重要。/
} BITMAPINFOHEADER;

typedef struct tagRGBQUAD {
BYTE rgbBlue; /指定蓝色分量/
BYTE rgbGreen; /指定绿色分量/
BYTE rgbRed; /指定红色分量/
BYTE rgbReserved; /保留,指定为0/
} RGBQUAD;

bool WriteYUV(unsigned charY,unsigned charU,unsigned charV,unsigned long size,FILEoutFile)
{
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;
FILEbmpFile;
FILE
yuvFile;

unsigned char*rgbBuffer, *yBuffer, *uBuffer, *vBuffer;
char name__[20];
//连接成字符串:potter.yuv,方便文件写入磁盘
strcpy_s(name__, argv[1]);
strcat_s(name__, 20, ".yuv");
fopen_s(&yuvFile,name__ , "wb");
if(!yuvFile)
{
    printf("yuvfile open error!\n");
    exit(0);
}
for (int i = 1; i <= atoi(argv[2]); ++i){
    char str[20];
    char name_[20];
    //将“potter”“序号”“.bmp”连接成文件名,再用fopen函数打开文件
    strcpy_s(name_, argv[1]);
    _itoa_s(i, str, 20, 10);
    strcat_s(name_, 20, str);
    strcat_s(name_, 20,".bmp");
    fopen_s(&bmpFile,name_ , "rb");
    if(!bmpFile)
    {
        printf("bmpfile open error!\n");
        exit(0);
    }

    //读取位图文件头
    if(fread(&File_header, sizeof(BITMAPFILEHEADER), 1, bmpFile) != 1)
    {
        printf("read file header error!\n");
        exit(0);
    }

    //判断文件类型
    if(File_header.bfType != 0x4D42)
    {
        printf("Not bmp file!\n");
        exit(0);
    }
    else
    {
        printf("this is a bmp file\n");
    }

    //读取位图信息头
    if(fread(&Info_header, sizeof(BITMAPINFOHEADER), 1, bmpFile) != 1)
    {
        printf("read info header error!\n");
        exit(0);
    }

    unsigned long width, height;
    if(Info_header.biWidth % 4 == 0)

        width = Info_header.biWidth;
    else
        //为了保证图像大小是4的倍数,每一行的像素数*每像素的位数=一行总位数
        //一行总位数必须是32位的整数倍。
        //为什么呢?这样只有才能保证一行数据量是4字节的整数倍。
        //怎么做呢?(x+31)除以32,再下取整,得到结果以后,再乘以32,就得到比原位数大且是32的倍数的数字,再除以8便得到字节数,肯定是4字节的整数倍。
        width =(Info_header.biWidth*Info_header.biBitCount + 31) / 32 * (32 / 8);

    //保证列数是偶数
    if((Info_header.biHeight % 2) == 0)
        height = Info_header.biHeight;
    else
        height = Info_header.biHeight + 1;

    //开辟缓冲区
    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];

    //从bmp文件中读取rgb信息
    ReadRGB(bmpFile, File_header,Info_header, rgbBuffer);

    //核心步骤,将rgb数据转为yuv
    RGB2YUV(width, height, rgbBuffer,yBuffer, uBuffer, vBuffer);

    //将转好的yuv写到文件中
    if(WriteYUV(yBuffer, uBuffer, vBuffer, width*height, yuvFile))
        printf("writeYUV file successful!\n");
    else
        printf("writeYUV file failed!\n");

    //free时一定要小心,指针不要指向不是堆的区域
    if(rgbBuffer)
        free(rgbBuffer);
    if(yBuffer)
        free(yBuffer);
    if(uBuffer)
        free(uBuffer);
    if(vBuffer)
        free(vBuffer);
    fclose(bmpFile);
}
fclose(yuvFile);//注意多帧播放时,yuvFile一定只关一次,并且在最后关,不要重复关闭,否则会出现文件覆盖
return 0;

}

void ReadRGB(FILE pFile,const BITMAPFILEHEADER &file_h, const BITMAPINFOHEADER &info_h, unsigned charrgbDataOut)
{
unsigned longLoop, iLoop, jLoop, width, height, w, h;
unsigned charmask, *Index_Data, *Data;

//保证是图像大小是4字节的整数倍,具体理由见下面的注释
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;

//若是24位,则bmp中有效数据大小是长*宽*3字节
//若是16位,则bmp中有效数据大小是长*宽*2字节
//若是8位,则bmp中有效数据大小是长*宽字节
//若是4位,则bmp中有效数据大小是长*宽/2字节
//若是2位,则bmp中有效数据大小是长*宽/4字节
//若是1位,则bmp中有效数据大小是长*宽/8字节(这大概是为什么bmp图像的长必须是4的倍数,宽必须是2的倍数的原因吧。。。)
width = w / 8 * info_h.biBitCount;
height = h;

//倒序前数据缓存区
Index_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(Index_Data, height*width, 1, pFile) != 1)
{
    printf("readfile error!");
    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];
}

//24位:直接把倒序后的缓存区数据复制给输出缓存区
if (info_h.biBitCount== 24)
{
    memcpy(rgbDataOut,Data, height*width);
    free(Index_Data);
    free(Data);
    return;
}

//非24位:解码生成rgb,需要调色板信息

//生成调色板数组,数组的下标,对应bmp文件中有效数据,通过下标对应查找,便可得到该数据对应的颜色
//debug by LiuDong:(unsignedlong long)pow(),pow前的强制类型转换不能转换成unsignedint、unsignedchar等
//因为pow((float)2, info_h.biBitCount)最大值是2^24,unsigned char最大能表示255,unsigned int最大能表示2^32
RGBQUAD*pRGB = (RGBQUAD *)malloc(sizeof(RGBQUAD)*(unsigned int)pow((float)2,info_h.biBitCount));
/*一个单元代表一种颜色
调色板实际上是一个数组,它所包含的元素与位图所具有的颜色数相同,决定于biClrUsed和biBitCount字段。
数组中每个元素的类型是一个RGBQUAD结构。真彩色无调色板部分。
biBitCount;位数/像素,1、2、4、8、24.假如是16位,R占5位,G占5位,B占5位,空出一位,排列组合一共有2^15种颜色,其他位数同理。*/

//读取位图调色板数据
if(!MakePalette(pFile, file_h,info_h,pRGB))
    printf("Nopalette!");

//16位:移位操作,从2字节中取出RGB信息,存到3字节中
if (info_h.biBitCount== 16)
{
    for(Loop = 0; Loop < height * width; Loop += 2)
    {
        *rgbDataOut= (Data[Loop] & 0x1F) << 3;//B:用0001 1111取出低字节的右五位,再放到目标字节的高5位(通过右移3位),得到五位的B
        *(rgbDataOut+ 1) = ((Data[Loop] & 0xE0) >> 2) + ((Data[Loop + 1] & 0x03)<< 6);//G:11100000取出低字节的左三位,00000011取出高字节的右两位,合并后,放到再放到目标字节的高5位,得到五位的G
        *(rgbDataOut+ 2) = (Data[Loop + 1] & 0x7C) << 1; //R:0111 1100取出高字节的中间五位,再放到目标字节的高5位,得到5位的R
        rgbDataOut+= 3;
    }//RGB都各自位于字节的高5位
}

//1~8位:移位操作,从有限固定位中取出RGB信息,存到3字节中
//循环次数:有效数据字节数
for (Loop =0; Loop<width*height; Loop++)
{
    //根据位深设置掩膜
    switch(info_h.biBitCount)
    {
    case1://1000 0000,1位,黑白双色图
        mask = 0x80;
        break;
    case2://1100 0000,2位,4色图
        mask = 0xC0;
        break;
    case4://1111 0000,4位,16色图
        mask = 0xF0;
        break;
    case8://1000 0000,8位,256色图
        mask = 0xFF;
    }

    int shiftCnt = 1;//控制mask的移位,决定取字节中哪些数据

    while(mask)//循环一次就是一个字节的解析过程
    {
        unsigned char index = mask == 0xFF ? Data[Loop] : ((Data[Loop] & mask) >> (8 -shiftCnt * info_h.biBitCount));
        //Loop代表第几个带转换的原始有效数据
        //pRGB代表调色板
        //8位:mask=1111 1111,index=data[Loop],即index为bmp中原始有效数据,直接对应调色板数组下标,得到相应颜色

        //查找调色板,取出对应BGR存入目标buffer:rgbDataOut
        *rgbDataOut= pRGB[index].rgbBlue;//B
        *(rgbDataOut+ 1) = pRGB[index].rgbGreen;//G
        *(rgbDataOut+ 2) = pRGB[index].rgbRed;//R

        if(info_h.biBitCount== 8)
            mask = 0;//如果是8位bmp,一次性取完一个字节的颜色数据,直接跳出循环即可
        else
            mask >>= info_h.biBitCount;//若是1位bmp,则一字节取8次数据;若是2位bmp,则一字节取4次数据;若是4位bmp,则一字节取2次数据
        //debugby LiuDong
        if(Loop == width*height - 1)
        {
            rgbDataOut= rgbDataOut + 3 - width*height*3;
            break;
        }
        rgbDataOut+= 3;
        shiftCnt++;
    }
}
if(Index_Data)
    free(Index_Data);
if (Data)
    free(Data);
if (pRGB)
    free(pRGB);

}

void RGB2YUV(unsigned longw,unsigned longh,unsigned char* rgbData, unsigned chary,unsigned charu,unsigned charv)
{
int LookupTable();//初始化查找表
unsigned char
ytemp = NULL;
unsigned charutemp = NULL;
unsigned char
vtemp = NULL;
utemp = (unsigned char*)malloc(wh);
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)
{
    nb = rgbData[nSize];
    ng = rgbData[nSize+ 1];
    nr = rgbData[nSize+ 2];
    y[i]= (unsigned char)(RGBYUV02990[nr]+ RGBYUV05870[ng] + RGBYUV01140[nb]);
    utemp[i] = (unsignedchar)(-RGBYUV01684[nr]- RGBYUV03316[ng] + nb / 2 + 128);
    vtemp[i] = (unsignedchar)(nr/ 2 - RGBYUV04187[ng] - RGBYUV00813[nb] + 128);
    i++;
}
//对u信号及v信号进行采样,因为是4:2:0格式,所以u的数据是y的数据的1/4,v的数据是y的数据的1/4
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++;
}
//对y、u、v 信号进行限电平处理
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);

}

标签:BMP2YUV,info,字节,文件,unsigned,调色板,转化,bmp,Data
来源: https://blog.csdn.net/weixin_52188651/article/details/118392718