【数据压缩4】Lab2图像文件的读写和转换

本文档详细介绍了将BMP图像文件转换为YUV格式的过程,包括文件结构分析、读取与写入操作、RGB数据转换以及YUV编码。实验通过C++实现,涉及内存管理、文件读写、结构体使用和位操作,适用于图像处理和多媒体技术的学习。
摘要由CSDN通过智能技术生成

【数据压缩4】Lab2图像文件的读写和转换

一、实验名称

图像文件的读写和转换(设计性实验)

二、实验目的

1.理解图像文件的基本组成。

2.掌握结构体作为复杂数据对象的用法。进一步熟悉由问题到程序的解决方案,并掌握编程细节:如内存分配、倒序读写、字节序、文件读写过程等。

三、实验环境

安装 Visual Studio 软件或其他的个人计算机

四、实验步骤

基于C++实现BMP序列转YUV文件

  1. 在图像处理软件中自行生成多个BMP文件,至少含5个不同的场景画面,要求带logo。基本要求位深度为24bit的BMP,进阶要求为支持小于24bit的BMP。
  2. 编写将第一步所生成的多个BMP文件转化为YUV文件,要求可在命令行中设置每个画面出现的帧数。最后形成的YUV文件应至少包含200帧。重点掌握缓冲区分配、倒序读写、结构体的操作。
  3. 对整个程序进行调试,并将生成的YUV文件用播放软件观看,验证是否正确。

五、实验内容

1. BMP文件结构分析

(1)典型的 BMP 图像文件由四部分组成
组成说明
位图头文件数据结构BITMAPFILEHEADER包含 BMP 图像文件的类型、显示内容等信息
位图信息数据结构BITMAPINFOHEADER包含有 BMP 图像的宽、高、压缩方法,以及定义颜色等信息
调色板Palette这个部分是可选的,有些位图需要调色板,有些位图就不需要调色板,比如真彩色图(24位的 BMP)
实际的位图数据ImageData这部分的内容根据 BMP 位图使用的位数不同而不同,在 24 位图中直接使用 RGB,而其他的小于 24 位的使用调色板中颜色索引值

在这里插入图片描述

  1. 位图头文件结构体
typedef struct tagBITMAPFILEHEADER {
    WORD bfType; //说明文件的类型
    DWORD bfSize; // 说明文件的大小,用字节为单位 
    WORD bfReserved1; //保留,设置为0 
    WORD bfReserved2; // 保留,设置为0 
    DWORD bfOffBits; //说明从BITMAPFILEHEADER结构开始到实际的图像数据之间的字节偏移量 

} BITMAPFILEHEADER;
  1. 位图信息数据结构
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;
  1. 调色板
typedef struct tagRGBQUAD {
        BYTE    rgbBlue;	//指定蓝色分量
        BYTE    rgbGreen;	//指定绿色分量
        BYTE    rgbRed;		//指定红色分量
        BYTE    rgbReserved;//指定保留,指定为0
} RGBQUAD;
(2) 解决的问题
如何组织复杂的数据?用结构体组织

BITMAPFILEHEADER File_header;
RGBQUAD* pRGB;

如何用函数处理结构中的数据?结构成员的值个别地传给函数处理

frameWidth = Info_header.biWidth;

2. 程序设计

下面展示一次大循环对每张图片的处理过程,其余图片同理。

(1)程序初始化
定义变量和缓冲区
//定义变量
BITMAPFILEHEADER File_header;//位图头
BITMAPINFOHEADER Info_header;//位图信息

FILE* bmpFile = NULL;//输入bmp文件
FILE* yuvFile = NULL;//输出yuv文件

int frameWidth;//宽
int frameHeight;//高
const int imageNum = 5;//图片数量

//输入输出文件名
const char* bmpFileName[imageNum] = { "img001.bmp","img002.bmp","img003.bmp","img004.bmp","img005.bmp" };
const char* yuvFileName = "video.yuv";
//定义缓冲区
unsigned char* rgbBuf;
unsigned char* yBuf;
unsigned char* uBuf;
unsigned char* vBuf;
bool flip = TRUE;
指针数组作为main函数的形参

void main(int argc, char *argv[])

argv[0]=BMP2YUV.exe;
argc=n+1;
argv[1]=1.bmp;
argv[2]=2.bmp;
argv[n]=video.yuv;

//打开输出文件
yuvFile = fopen(argv[6], "wb");
//打开第1个输入文件
bmpFile = fopen(argv[1], "rb");
(2)读取BMP中的RGB数据

读取BMP文件,抽取或生成RGB数据写入缓冲区

(a)读bmp文件的位图头文件和位图信息,并进行异常判断
//1. 判断是否可读出位图头
if (fread(&File_header, sizeof(BITMAPFILEHEADER), 1, bmpFile) != 1){
    cout << "read file header error!" << endl;
    exit(0);
}
//2. 判断位图头是否是BMP文件
if (File_header.bfType != 0x4D42){
    cout << "Not bmp file!" << endl;
    exit(0);
}
else{
    cout << "this is a bmp file!" << endl;
}
//3. 判断位图信息是否可以读出
if (fread(&Info_header, sizeof(BITMAPINFOHEADER), 1, bmpFile) != 1){
    cout << "read info header error!" << endl;
    exit(0);
}
(b)读取像素的实际点阵数,分配缓存空间
注意字节序:

在实现 BMP 文件头信息的写入和读出时,需要注意整数保存时的字节序。在编程前先用二进制打开方式观察 BMP 文件各个部分的数据存储格式。如图所示,文件宽高就是小端方式存储,D0 02 00 00实际数值是 00 00 02 D0,十进制720。

//读取宽高像素数
frameWidth = Info_header.biWidth;
frameHeight = Info_header.biHeight;

//分配空间
rgbBuf = (unsigned char*)malloc(frameWidth * frameHeight * 3);
yBuf = (unsigned char*)malloc(frameWidth * frameHeight);
uBuf = (unsigned char*)malloc(frameWidth * frameHeight / 4);
vBuf = (unsigned char*)malloc(frameWidth * frameHeight / 4);
(c)读RGB数据,倒序转正序写入缓存区
注意位数:根据每像素位数的不同,执行不同的操作,如下代码实现了24位和小于8位bmp文件的操作。
位数操作
24/32bit直接取像素数据写RGB缓冲区
16bit位与移位取像素数据转换为8bit/彩色分量
8bit以下构造调色板,位与移位取像素数据

例如下图,18 00小端存储实际是00 18即24bit位图,无调色板直接读取选中区域
在这里插入图片描述

注意倒序:BMP文件的存储顺序是从左到右、从下到上的顺序

在这里插入图片描述

//读取BMP文件中的RGB数据
ReadRGB(bmpFile, File_header, Info_header, rgbBuf);
int ReadRGB(FILE* pFile, BITMAPFILEHEADER& file_h, BITMAPINFOHEADER& info_h, unsigned char* rgb)
{
	int width = info_h.biWidth * biBitCount / 8;
	int height = info_h.biHeight;
	unsigned char* bmp = (unsigned char*)malloc(sizeof(unsigned char) * width * height);
	unsigned char* yx_rgb = (unsigned char*)malloc(sizeof(unsigned char) * width * height);
	unsigned char mask;
	unsigned long Loop;
	
	fread(bmp, width * height, 1, pFile);
	//倒序转正序
	for (int i = 0; i < height; i++) {
		for (int j = 0; j < width; j++) {
			yx_rgb[i * width + j] = bmp[(height - i - 1) * width + j];
		}
	}

	if (biBitCount == 24)
	{
		memcpy(rgb, yx_rgb, height * width);
		return 0;
	}

	//小于等于8位
	else if (biBitCount <= 8) {

		//调色板
		RGBQUAD* pRGB = (RGBQUAD*)malloc(sizeof(RGBQUAD) * (unsigned int)pow((float)2, info_h.biBitCount));
		MakePalette(pFile, info_h, file_h, pRGB);

		for (Loop = 0; Loop < width * height; Loop++)
		{
			//根据位深设置掩膜
			switch (biBitCount){
			case 1:
				mask = 0x80;
				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 ? yx_rgb[Loop] : ((yx_rgb[Loop] & mask) >> (8 - shiftCnt * biBitCount));

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

				mask = 0;//8位一次循环就取完一个字节的颜色数据
				rgb += 3;
				shiftCnt++;
			}
		}
	}
	return 0;
}

(3)RGB到YUV数据的转换

格式转换原理
Y = 0.2990 R + 0.5870 G + 0.1140 B U = 128 − 0.1684 R − 0.3316 G + 0.5 B V = 128 + 0.5 R − 0.4187 G − 0.0813 B Y=0.2990R+0.5870G+0.1140B\\ U=128−0.1684R−0.3316G+0.5B\\ V=128+0.5R−0.4187G−0.0813B Y=0.2990R+0.5870G+0.1140BU=1280.1684R0.3316G+0.5BV=128+0.5R0.4187G0.0813B
调用RGB2YUV的函数实现RGB到YUV数据的转换

//RGB转YUV 4:2:0
RGB2YUV(frameWidth, frameHeight, rgbBuf, yBuf, uBuf, vBuf)
采用部分查找表法,提高运行效率
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;
    } 
}

RGB2YUV函数

void RGB2YUV(unsigned long w, unsigned long h, unsigned char * rgbBuf, unsigned char * y, unsigned char * u, unsigned char * v)
{
    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, r, g, b, nSize;
    //对每个像素进行 rgb -> yuv的转换
    for (i = 0, nSize = 0; nSize < w * h * 3; nSize += 3)
    {
        b = rgbBuf[nSize];
        g = rgbBuf[nSize + 1];
        r = rgbBuf[nSize + 2];
        y[i] = (unsigned char)(RGBYUV02990[r] + RGBYUV05870[g] + RGBYUV01140[b]);
        utemp[i] = (unsigned char)(-RGBYUV01684[r] - RGBYUV03316[g] + b / 2 + 128);
        vtemp[i] = (unsigned char)(r / 2 - RGBYUV04187[g] - RGBYUV00813[b] + 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);
}
(4)循环写YUV文件
//写入yuv文件,每张图片重复20帧,4:2:0
for (int i = 0; i < 20; i++){
    fwrite(yBuf, 1, frameWidth * frameHeight, yuvFile);
    fwrite(uBuf, 1, (frameWidth * frameHeight) / 4, yuvFile);
    fwrite(vBuf, 1, (frameWidth * frameHeight) / 4, yuvFile);
}
(5)关闭文件,释放缓冲区
//释放缓存空间
free(rgbBuf);
free(yBuf);
free(uBuf);
free(vBuf);
//关闭文件
fclose(bmpFile);
fclose(yuvFile);

六、实验结果

输入bmp文件
来自流媒体实验的test.mp4,使用ffmpeg截取bmp格式的文件,并加水印
在这里插入图片描述
输出yuv文件
24bit的10张bmp合成的yuv视频:

24bitBMP2YUV

8bit的10张bmp合成的yuv视频:

8bitBMP2YUV

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值