test_02:BMP to YUV

来源:互联网 发布:淘宝店铺退款率在哪看 编辑:程序博客网 时间:2024/06/05 10:11

一、实验原理

1. BMP文件的组成

  BMP文件由四部分组成:位图文件头数据、位图信息头数据、调色板和位图数据。下面结合具体图片说明。为了方便在查看数据项在图片里的具体内容,每行前面加了当前项在文件中的地址值。 
  (1)文件头 BITMAP_FILE_HEADER,包含如下内容

typedef struct tagBITMAPFILEHEADER {    //0x00~0x01,说明文件的类型    WORD bfType;     //0x02~0x05,说明文件的大小,用字节B为单位    DWORD bfSize;    //0x06~0x07,保留,设置为0    WORD bfReserved1;    //0x08~0x09,保留,设置为0    WORD bfReserved2;    //0x0a~0x0d,说明从BITMAP_FILE_HEADER结构开始到实际的图像数据之间的字节偏移量    DWORD bfOffBits;} BITMAPFILEHEADER;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

  (2)信息头BITMAP_INFO_HEADER,包含如下内容

typedef struct tagBITMAPINFOHEADER {    //0x0e~0x11,说明当前结构体所需字节数    DWORD biSize;    //0x12~0x15,以像素为单位说明图像的宽度    LONG biWidth;    //0x16~0x19,以像素为单位说明图像的高度    LONG biHeight;    //0x1a~0x1b,说明位面数,必须为1    WORD biPlanes;    //0x1c~0x1d,说明图像的位深度    WORD biBitCount;    //0x1e~0x21,说明图像是否压缩及压缩类型    DWORD biCompression;    //0x22~0x25,以字节为单位说明图像大小,必须是4的整数倍    DWORD biSizeImage;    //0x26~0x29,目标设备的水平分辨率,像素/米     LONG biXPelsPerMeter;    //0x2a~0x2d,目标设备的垂直分辨率,像素/米    LONG biYPelsPerMeter;    //0x2e~0x31,说明图像实际用到的颜色数,如果为0,则颜色数为2的biBitCount次方    DWORD biClrUsed;    //0x32~0x35,说明对图像显示有重要影响的颜色索引的数目,如果是0,表示都重要。    DWORD biClrImportant;} BITMAPINFOHEADER;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

  (3)调色板Palette。当图像位深度小于等于8时称为索引颜色,这时整个文件不保存每个像素的RGB值,而是把图像中的所有颜色编制成颜色表。在表示图片中每个像素的颜色信息时,使用颜色表的索引。由于位深度不超过8位,颜色表不超过256个数值。 
  调色板决定于位于0x2e~0x31的biClrUsed字段和位于0x1c~0x1d的biBitCount字段,每个元素的类型是一个 RGBQUAD 结构,顺序为B,G,R及保留字,每一个元素占用一个字节。

typedef struct tagRGBQUAD {    BYTE rgbBlue; /*指定蓝色分量*/    BYTE rgbGreen; /*指定绿色分量*/    BYTE rgbRed; /*指定红色分量*/    BYTE rgbReserved; /*保留,指定为0*/} RGBQUAD;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

  (4)位图数据ImageData。对于用到调色板的位图,图像数据就是该像素颜色在调色板中的索引值。对于真彩色图,图像数据就是实际的RGB值。 
  图像的每一扫描行由表示图像像素的连续的字节组成,每一行的字节数取决于图像的颜色数目和用像素表示的图像宽度。规定每一扫描行的字节数必须是 4 的整倍数,也就是DWORD 对齐的。扫描行是由底向上存储的,这就是说,阵列中的第一个字节表示位图左下角的像素,而最后一个字节表示位图右上角的像素。 
  表1是两幅具体图片及其文件内容,表格里只列出了文件头和信息头中不同部分的值。

 



这里写图片描述这里写图片描述


二进制方式打开这里写图片描述这里写图片描述
图像基本信息分辨率:1280×720
位深度:8位索引颜色
文件大小:901KB=922,680B分辨率:1280×1280
位深度:24位真彩色
文件大小:4.68MB=4,915,256B0x0002~0x000538 14 0e 0038 00 4b 000x000a~0x000d36 04 00 0036 00 00 000x0016~0x0019d0 02 00 0000 05 00 000x001c~0x001d08 0018 000x0022~0x002502 10 0e 0002 00 4b 00

表1 BMP文件的组成

  • 0x00~0x01,标记了此文件为BMP文件,内容是B和M两个字母的ASCII码。
  • 0x02~0x05,以字节为单位的文件大小。左图大小为922,680字节,写成十六进制就是E1438。在这里可以看出BMP文件的小端保存方式,先保存低位字节,再保存高位字节,保存顺序为38、14、0e。同理,右图为4,915,256(4B0038H)个字节,保存顺序为38、00、4b。
  • 0x0a~0x0d,实际数据的字节偏移量。BMP文件共有的前两部分文件头占了54(36H)个字节,左图包含了8位也就是256种索引颜色的调色板,调色板中每个颜色的RGBQUAD结构占用4字节,总共1024个字节的调色板信息。因此左图的偏移量为54+1024=1078(436H)个字节。
  • 0x16~0x19,左图高度为720(2D0H)个像素,右图高度为1280(500H)个像素。
  • 0x1c~0x1d,左图位深度为8(8H),右图位深度为24(18H)。
  • 0x22~0x25,BMP图像大小biSizeImage可由下式计算 
    biSizeImage=cx×biBitCount+3132×4×cy+2
    其中,cxcy表示水平和垂直方向的像素数。cx×biBitCount表示一行图像占了多少位。BMP规定这个图像大小必须是4字节的整数倍,也就是32位的整数倍,因此需要把 cx×biBitCount加31再除以32后下取整,就保证了计算结果是离这个数最近的而且是比它大的32的倍数,也就保证了是4字节的整数倍。乘以4和行数,得到4字节整数倍的图像大小。 
    另外,BMP文件的末尾两个字节是保留位,无论图像是什么这两个字节都为0,因此最后计算结果还要加上2字节。图像大小biSizeImage+字节偏移量bfOffBits=文件大小bfSize。左图的图像大小经过计算为921,602(E1002H)字节,右图图像大小为4,915,202(4B0002H)字节。
  • 0x26~0x29,水平分辨率。两幅图分辨率都是计算机上常见的72dpi,也就是72像素/英寸=28.346像素/厘米。以像素/米单位表示就是2834(B12H)。后面四个字节的垂直分辨率同理。

2.位深度的各种存储方式

  BMP文件的位深度有以下几种:1,2,4,8,16,24,32。其中1,2,4,8位对应索引颜色,图像包含调色板数据。16,24,32位直接保存像素的颜色值,这三种位深度的像素保存方式有多种,见图1。 

这里写图片描述
图1 Photoshop中BMP文件的位深度选项
  位深度选项中RGB代表红绿蓝三通道值,X代表保留位(空),A代表透明度信息(Alpha),数字代表位数。例如16位的A1 R5 G5 B5表示第一位为透明度信息,0代表像素不透明,1代表像素透明,2~6位为R通道信息,7~11位为G通道信息,12~16位为B通道信息。在32位的BMP文件中,透明度还可以用更精细的8位方式表示,实现像素的不同透明程度。本实验中对于非索引颜色的位图不考虑保留位和透明度信息,仅对16位R5 G6 B5方式保存的BMP文件和24位R8 G8 B8方式保存的BMP文件进行数据读写操作。

3.颜色转换公式

YUV=0.29900.16840.50000.58700.33160.41870.11400.50000.0813RGB+0128128
  转换公式与实验一相同,具体分析可见第一次的实验报告。

二、实验流程及代码分析

1. 主函数的流程

  BMP转YUV可由以下几步构成

读取参数,打开文件读取BMP文件头信息创建缓冲区调用函数进行转换清理内存

  下面是关键代码分析,代码说明在每段代码的下方。

//--------main_bmp2yuv.cpp--------//头文件,读取命令行参数,打开文件与之前类似,略过#include <windows.h>...//读取BMP文件头,信息头,读取错误时的处理代码略过BITMAPFILEHEADER file_header;BITMAPINFOHEADER info_header;if (fread(&file_header, sizeof(BITMAPFILEHEADER), 1, bmpFile) != 1)...if (file_header.bfType != 0x4D42){    printf("Not BMP file.\n");    exit(1);}if (fread(&info_header, sizeof(BITMAPINFOHEADER), 1, bmpFile) != 1)...//读取图像尺寸int width = info_header.biWidth;int height = info_header.biHeight;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

  BMP文件的结构组成包含在windows.h中,可以使用BITMAPFILEHEADER和BITMAPINFOHEADER两种数据类型方便地对文件头和信息头进行组织,这两种数据类型包含了第一部分中的全部属性。另外还有RGBQUAD类型,用来组织调色板。 
  首先读取文件头file_header,如果前两字节不是42H和4DH(B字母和M字母的ASCII码)说明不是BMP文件,退出。读完文件头之后文件指针就指向了信息头的起始,这时就可以进行读取信息头info_header的操作。读完两部分数据之后,使用信息头的biWidth和biHeight属性读取图像宽高信息,为下一步创建缓冲区做准备。

//开辟缓冲区,yuv缓冲区部分略过unsigned char* rgbBuf = (unsigned char*)malloc(width*height * 3);...//BMP与RGB的转换,得到RGB数据if (BMP2RGB(file_header, info_header, bmpFile, rgbBuf)){    printf("BMP2RGB error\n");    exit(1);}//RGB与YUV的转换,得到YUV数据int flip = 0if (RGB2YUV(width, height, rgbBuf, yBuf, uBuf, vBuf, flip)){    printf("RGB2YUV error\n");    exit(1);}//之后与RGB转YUV类似,例如对超出电平值的处理,关闭缓冲区等,略过...
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

  BMP文件先通过BMP2RGB函数转成RGB数据,再通过RGB2YUV函数转成YUV数据。读取到的图像数据是倒序存放的,flip=0保证了RGB2YUV可以正确地对其转换,具体会在后面的分析中说明。下面开始分析BMP2RGB函数的具体细节。

2. BMP2YUV函数的流程

判断实际字节数创建缓冲区,读取像素值根据位深度不同进行相应处理
//--------bmp2rgb.cpp--------//头文件,函数声明等略过...//判断像素的实际点阵数int w, h;w = (info_h.biWidth*info_h.biBitCount + 31) / 32 * 4;//w为实际一行的字节数h = info_h.biHeight;//h为列数//开辟实际字节数量的缓冲区,读数据,一次读取一个字节unsigned char* dataBuf = (unsigned char*)malloc(w*h);fseek(pFile, file_h.bfOffBits, 0);fread(dataBuf, 1, w*h, pFile);unsigned char* data = dataBuf;unsigned char* rgb = rgbBuf;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

  在BMP文件组成中说到,BMP文件规定一行的字节数必须是4字节的倍数,如果不是要补充相应的位数使之成为4的倍数。因此实际的字节数可能比像素数×位深度要多。使用上面说到的计算biSizeImage的公式可以得到实际每行有多少字节。由于整个文件最后两字节为保留字节,与获取像素值无关,因此这里w最后没有加2。 
  得到了实际存储的字节数后就可以开辟数据缓冲区了,使用文件头的字节偏移属性bfOffBits直接把文件指针定位到像素值数据的起始,开始读取数据,一次读取一个字节。 
  之后就是要根据量化位数的不同进行不同的操作,下面分别对其进行说明。 
  (1)8位及以下索引文件的读取 
  对于索引颜色,读取流程如下

创建调色板缓冲区,读调色板数据根据量化位数的不同计算蒙版值通过移位操作获取索引值
//调色板的字节数的初始值colorCntint colorCnt = pow(2, (float)info_h.biBitCount);//如果实际用到的颜色不为0,则以biClrUsed为准if (info_h.biClrUsed != 0)    colorCnt = info_h.biClrUsed;//分配调色板缓冲区RGBQUAD* pRGBBuf = (RGBQUAD*)malloc(sizeof(RGBQUAD)*colorCnt);if (pRGBBuf == NULL){    printf("Not enough memory\n");    return 1;}//调色板与文件起始处相距(文件头 + 信息头)个字节fseek(pFile, sizeof(BITMAPFILEHEADER) + info_h.biSize, 0);//读调色板数据fread(pRGBBuf, sizeof(RGBQUAD), colorCnt, pFile);RGBQUAD* pRGB = pRGBBuf;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

  索引颜色都有调色板,也就是索引值表。因此首先要把调色板保存下来。这里与课件中创建调色板的代码略有不同:

RGBQUAD *pRGB = (RGBQUAD *)malloc(sizeof(RGBQUAD)*(unsigned char)pow(2,info_h.biBitCount));...fread(pRGB_out,sizeof(RGBQUAD),(unsigned int)pow(2,info_h.biBitCount),pFile);
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

  课件中调色板的大小设为了2的biBitCount次方,一个n比特量化的BMP图像调色板的实际颜色数取决于biClrUsed的值。图2展示了这样一个例子。这是一幅30×30像素的图像,图像在右下角,4bit量化,但是整幅图里只有黑白灰三种颜色。 

这里写图片描述
图2 一张只有三种颜色的BMP图像
  信息头的几个关键字节用红色标出。0x0a~0x0d处的bfOffBits为42H,也就是从0x42开始为实际图像数据。0x1c~0x1d处的biBitCount表明图像是4位的,但是0x2e~0x31处的biClrUsed却表明图像只使用了三种颜色。调色板从0x36开始到0x41结束,共有12字节,储存了三种颜色。因此应以biClrUsed为准构建调色板。 
  从图2中也可以看出图像的存储方式是由底向上存储的。索引值表中第0个颜色是白色,第1个颜色是灰色,第2个颜色是黑色。0x42中前4位的值为2,说明存储的是黑色。图像左下角像素为黑色,左上角像素为白色,因此扫描行是由底向上存储的。 
  得到了调色板颜色数目colorCnt之后把文件指针移动到调色板的起始位置,也就是文件头加信息头个字节后,把调色板读取到缓冲区内。

for (j = 0; j < h; j++)//j控制行循环{    int pixCnt = 0;//pixCnt用于判断一行是否结束    for (i = 0; i < w; i++)//i控制列循环    {        unsigned char mask;                                 //根据不同的量化位数计算蒙版值        switch (info_h.biBitCount)        {        case 1:            mask = 0x80;//1000 0000            break;        case 2:            mask = 0xC0;//1100 0000            break;        case 4:            mask = 0xF0;//1111 0000            break;        case 8:            mask = 0xFF;//1111 1111            break;        }...
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

  由于读取文件的缓冲区data是一个字节一个字节读取的,但是只有8位量化的索引值才足够一个字节,1,2,4位的索引值都不足一个字节,也就是一个字节里包含了多个像素的信息。因此在读取像素索引值时需要一个“蒙版”遮住不需要的索引值,读完当前像素值把该蒙版向后移动相应的位数,显示出下一个像素值的信息。“遮住”的具体操作就是与运算,把不需要的位与0,需要的位与1,根据这个规则,计算出蒙版值。 
  对于1位的图像,一个字节里的第一位为第一个像素的值,因此蒙版值为1000 0000,也就是0x80。2,4,8位的图像同理。

int shiftCnt = 1;//shiftCnt控制蒙版的移位while (mask){    //通过移位操作获取当前像素的索引值    unsigned char index = mask == 0xFF ?        data[i + w*j] : ((data[i + w*j] & mask) >> (8 - shiftCnt * info_h.biBitCount));    *rgb = pRGB[index].rgbBlue;//B    *(rgb + 1) = pRGB[index].rgbGreen;//G    *(rgb + 2) = pRGB[index].rgbRed;//R    //如果是8位,开始读下一个字节,如果不是继续读当前字节    if (info_h.biBitCount == 8)        mask = 0;    else        mask >>= info_h.biBitCount;    rgb += 3;    shiftCnt++;    //如果到了行尾补充位则直接转到下一行    pixCnt++;    if (pixCnt == info_h.biWidth)    {        i = w - 1;        break;    }//释放缓冲区等操作略过...
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

  得到了蒙版,就可以读取索引值了。使用条件运算符判断当前图像是不是8位的图像,如果是,则直接逐字节读取索引值index,while循环结束,继续下一字节的读取。 
  如果不是8位的图像,需要将当前字节data[i + w*j]与蒙版mask做与运算。与运算结束后,有效位还在高位,比如2位的索引值,这时是××000000,与需要的索引值××(000000××)相比需要向右移动6位,也就是8-biBitCount。读完之后一个字节还有后面其它像素的索引值,因此需要把mask向右移动相应的位数biBitCount,继续while循环,这时读到的索引值为00××0000,需要向右移动4位得到正确的索引值,也就是8-2*biBitCount。因此可以归纳出每次移位的位数是8-shiftCnt*biBitCount。 
  读取像素信息时行列分别进行循环,是为了方便判断是否达到行结尾处的补充位。pixCnt保存了当前一行里已经读了多少个像素的值,如果达到一行的像素数biWidth,则跳过后面的补充位,进到下一行。仍以图2中30×30像素4位的BMP图像为例,根据biSizeImage的计算公式算出一行实际字节数为16字节,有效位数为30*4=120bit,需要补充8位。实际图像数据从0x42开始,经过120bit(15字节)后到0x50,接下来的8位0x51就是补充的数据位,读取图像索引值时要跳过这样的位,把行循环i置为行结尾w-1,这样就可以进行新一行的循环。 
  另外在图2中可以看出,补充位不一定都是0,比如0xc1,0x101,0x111处都是补充位,但是这些字节里的某些位不是0。 
  (2)16位R5 G6 B5格式图像的读取

for (j = 0; j < h; j++)//j控制行循环{    int pixCnt = 0;    for (i = 0; i < w; i+=2)//i控制列循环    {        *rgb = (data[i + w*j] & 0x1F) << 3;//B        *(rgb + 1) = ((data[i + w*j] & 0xE0) >> 3) + ((data[i + w*j + 1] & 0x07) << 5);//G低位+G高位        *(rgb + 2) = data[i + w*j + 1] & 0xF8; //R        rgb += 3;        //如果到了行尾补充位则直接转到下一行        pixCnt++;        if (pixCnt == info_h.biWidth)            i = w - 1;    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

  以这种方式保存的图像RGB也不是整字节的值,仍需要用蒙版进行与运算然后移位。16位2个字节中RGB的保存顺序如下

×××××R×××G1×××G2×××××BG
  第一个字节前5位保存红色R,后3位保存绿色G的高3位G1;第二个字节前3位保存绿色G的低3位G2,后5位保存蓝色B。存储时使用小端存储,就变成了这样
×××G2×××××B×××××R×××G1
  因此在读取实际的R,G,B值时,一次要读两个字节,并做以下操作

  • 将第一个字节data[i + w*j]与蒙版0x1F(0001 1111)与运算得到蓝色值,这时得到的值都在字节的低5位,范围是0~31,所以还要左移3位扩大到0~248,由于后3位都是0,因此B的值以8为单位递增。
  • 将第二个字节data[i + w*j + 1]与蒙版0x07(0000 0111)与运算得到绿色的高3位G1,左移5位搬移到整个字节的高位;第一个字节与蒙版0xE0(1110 0000)与运算得到绿色的低3位G2,右移3位接到G1的后面。绿色值为0~252,后2位都是0,以4为单位递增。
  • 将第二个字节与蒙版0xF8(1111 1000)与运算得到红色值,此时已经在高5位,范围是0~248,不需要移位,也以8为单位递增。

  (3)24位R8 G8 B8格式图像的读取

for (j = 0; j < h; j++)//j控制行循环{    int pixCnt = 0;    for (i = 0; i < w; i += 3)//i控制列循环    {        *rgb = data[i + w*j];//B        *(rgb + 1) = data[i + w*j + 1];//G        *(rgb + 2) = data[i + w*j + 2];//R        rgb += 3;        //如果到了行尾补充位则直接转到下一行        pixCnt++;        if (pixCnt == info_h.biWidth)            i = w - 1;    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

  这种方式下RGB各占1字节,因此每次只要顺序读取即可。小端方式保存,保存顺序是B,G,R。读完之后前进3字节读取下一颜色值。

三、实验结果与总结

  使用5张不同位深度的BMP图像进行测试,其中4位、8位、24位图各一张,16位图两张。分辨率均为800×540。 

这里写图片描述
图3 测试用图像序列
  写入YUV序列时,遍历图像中的每一像素值,相邻两图按照一张图的权重递增,另一张图权重递减的顺序相加,最终实现一个简单的渐变效果。部分代码如下

//--------main_yuvSequence.cpp--------//命令行参数:输入图像数,输入图像文件名列表,输出文件名,图像宽度,图像高度//包含头文件等略过const int FRAME_NUM = 200;//总帧数...int imgCount = atoi(argv[1]);//图像总数char** yuvFileName = (char**)malloc(imgCount);//图像名的数组FILE** yuvFile = (FILE**)malloc(imgCount);//图像文件指针的数组for (i = 0; i < imgCount; i++){    yuvFileName[i] = argv[i + 2];//依次获取各文件名    yuvFile[i] = fopen(yuvFileName[i], "rb");//依次打开各文件    if (yuvFile[i] == NULL)    ...}...int width = atoi(argv[imgCount + 3]);//图像宽度int height = atoi(argv[imgCount + 4]);//图像高度unsigned char* yBuf1 = (unsigned char*)malloc(width*height*1.5);//前一幅图像的缓冲区unsigned char* yBuf2 = (unsigned char*)malloc(width*height*1.5);//后一幅图像的缓冲区unsigned char* yBuf3 = (unsigned char*)malloc(width*height*1.5);//计算结果图像的缓冲区...
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

  输入图像为一个文件数组,数组大小由命令行参数控制,循环读入各个文件。实现此效果需要对YUV三个分量都进行处理,因此直接把YUV一起读入到缓冲区中,缓冲区大小为Y通道的1.5倍。

int delay = FRAME_NUM / (imgCount - 1);//过渡的持续时间fread(yBuf1, 1, width*height*1.5, yuvFile[0]);//读入第一张图像for (i = 0; i < (imgCount - 1); i++)//每张图像的循环{    fread(yBuf2, 1, width*height*1.5, yuvFile[i+1]);//读入后一幅图像    for (j = 0; j < delay; j++)//每一段过渡的循环    {        double alpha = (double)j / (double)delay;//计算权值系数        for (k = 0; k < width*height*1.5; k++)//每一帧的循环        {            yBuf3[k] = (1 - alpha)*(yBuf1[k]) + alpha*(yBuf2[k]);//相邻两幅图按照权值相加,得到目标像素值        }        fwrite(yBuf3, 1, width*height*1.5, yuvSeqFile);//将计算后的叠加图像写入到文件    }    memccpy(yBuf1, yBuf2, 1, width*height*1.5);//把后一幅图像的数据复制到前一幅图像的缓冲区中,为下一张图像做准备}//释放缓冲区等略过...
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

  实现方法很简单,但是效率比较低,对每张图的像素值要单独计算得出结果,一次写入一帧画面。由于读取时的顺序为YUV,输出时也是先计算完Y分量的值写入文件,再计算U分量的值写入文件,等等。实验结果如表2所示,其中截取了整个序列中的某些帧

这里写图片描述这里写图片描述这里写图片描述这里写图片描述这里写图片描述这里写图片描述这里写图片描述这里写图片描述这里写图片描述

表2 测试结果