BMP2YUV文件转化

BMP文件数据中的四层结构,分别是位图文件头、位图信息头、调色板、实际的位图数据。从位图信息头中提取图像宽高、实际的位图数据所在数据,从BMP中读取实际的位图数据,进而索引调色板数组得到每一数据的实际物理意义,即查找得到各位图数据代表的RGB再将RGB转化为YUV

Y=0.30R+0.59G+0.11B,
U=0.493(B-Y),
V=0.877(R-Y)

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

主函数功能:
1、 读取BMP文件
2、抽取BMP文件头和信息头信息,用结构体File_header盛放文件头数据,用结构体Info_header盛放信息头数据
3、调用ReadRGB 函数,从bmp文件中读取RGB信息
4、最后调用RGB2YUV函数,将rgb数据转为yuv
5、写YUV文件

#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 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__[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 char*rgbDataOut)
{
    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 00001位,黑白双色图
            mask = 0x80;
            break;
        case2://1100 00002位,4色图
            mask = 0xC0;
            break;
        case4://1111 00004位,16色图
            mask = 0xF0;
            break;
        case8://1000 00008位,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 char*y,unsigned char*u,unsigned char*v)
{
    int LookupTable();//初始化查找表
    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)
    {
        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);
}

 
bool MakePalette(FILE* pFile,const BITMAPFILEHEADER &file_h, const BITMAPINFOHEADER &info_h, RGBQUAD *pRGB_out)
{
    //先判断是否存在调色板:有效数据开始处离文件开头的距离-位图文件头大小-位图信息头大小=调色数据大小
    if ((file_h.bfOffBits- sizeof(BITMAPFILEHEADER)-info_h.biSize)== sizeof(RGBQUAD)*pow((float)2,info_h.biBitCount))
    {
        fseek(pFile,sizeof(BITMAPFILEHEADER)+info_h.biSize,0);
        //把调色数据放到pRGB_out中,完成make palette工作
        if(fread(pRGB_out, sizeof(RGBQUAD), (unsigned int)pow((float)2,info_h.biBitCount),pFile)!= (unsigned int)pow((float)2,info_h.biBitCount))
        {
            printf("Failto read RGBQUAD!\n");
        }
        return true;
    }
    else
        return false;
}

实验结果导出
在这里插入图片描述

在这里插入图片描述

实验总结:
bmp文件24bit有其在结构深度上的特殊性,能够将其进行更好的处理,但相应数据送到rgb时的字节顺序需要格外注意。
另外我自己的编程能力还要进一步提高哦,强制转换都忘记了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值