24位BMP文件转为YUV文件并实现其转场功能

读懂实验一思路与各个模块功能真的很重要!!


前言

本次实验建立在实验一的基础上


一、实验目的

代码实现:将.bmp文件转化为.yuv文件,并将五张.yuv文件生成可以转场的.yuv视频

二、实验原理

1.BMP文件的结构

在这里插入图片描述
与RGB文件、YUV文件不同的是,BMP文件含有位图文件头BITMAPFILEHEADER、位图信息头BITMAPINFOHEADER、调色板Palette,本次实验需要现将BMP文件的RGB信息(位于实际的位图数据ImageData中)提取出来,再结合实验一的代码将RGB文件转换为YUV文件。

2.位图文件头内存储的元数据

在这里插入图片描述

3.位图信息头内存储的元数据

在这里插入图片描述

4.BMP文件转YUV文件实现思路:

  1. 程序初始化(打开两个文件、定义变量和缓冲区等)
  2. 读取BMP文件,抽取或生成RGB数据写入缓冲区(利用结构体)
    (1)读位图文件头:判断文件是否可读入、判断文件是否为BMP文件
    (2)读位图信息头(判断是否读出)
    (3)判断像素的实际点阵数(获取图像的长和宽信息)
    (4)开辟缓冲区,读数据,倒序存放
    (5)根据每像素位数的不同,执行不同的操作:本程序转换的是24位BMP文件,直接取像素数据,写入RGB缓冲区
  3. 调用RGB2YUV的函数实现RGB到YUV数据的转换
  4. 写入YUV文件
  5. 关闭文件,释放缓冲区

三、代码实现

1.bmp2yuv.h

#pragma once
int BMP2YUV(int x_dim, int y_dim, void* bmp, void* y_out, void* u_out, void* v_out, int flip);
//自行构造的函数,实现将BMP文件(内的RGB信息)转化为YUV

void InitLookupTable();//部分查找表

2.bmp2yuv.cpp

#include "stdlib.h"
#include "bmp2yuv.h"

static float RGBYUV02990[256], RGBYUV05870[256], RGBYUV01140[256];
static float RGBYUV01684[256], RGBYUV03316[256];
static float RGBYUV04187[256], RGBYUV00813[256];
int RGB2YUV(int x_dim, int y_dim, void* bmp, void* y_out, void* u_out, void* v_out, int flip)
{
	static int init_done = 0;

	long i, j, size;
	unsigned char* r, * g, * b;
	unsigned char* y, * u, * v;
	unsigned char* pu1, * pu2, * pv1, * pv2, * psu, * psv;
	unsigned char* y_buffer, * u_buffer, * v_buffer;
	unsigned char* sub_u_buf, * sub_v_buf;

引入查找表:

	if (init_done == 0)
	{
		InitLookupTable();
		init_done = 1;
	}
	// check to see if x_dim and y_dim are divisible by 2
	if ((x_dim % 2) || (y_dim % 2)) return 1;
	size = x_dim * y_dim;

开拓缓冲区:

	// allocate memory
	y_buffer = (unsigned char*)y_out;
	sub_u_buf = (unsigned char*)u_out;
	sub_v_buf = (unsigned char*)v_out;
	u_buffer = (unsigned char*)malloc(size * sizeof(unsigned char));
	v_buffer = (unsigned char*)malloc(size * sizeof(unsigned char));
	if (!(u_buffer && v_buffer))
	{
		if (u_buffer) free(u_buffer);
		if (v_buffer) free(v_buffer);
		return 2;
	}

	b = (unsigned char*)bmp;
	y = y_buffer;
	u = u_buffer;
	v = v_buffer;

利用公式转化:看看是否需要倒置

	// convert RGB to YUV
	if (!flip) {
		for (j = 0; j < y_dim; j++)
		{
			y = y_buffer + (y_dim - j - 1) * x_dim;
			u = u_buffer + (y_dim - j - 1) * x_dim;
			v = v_buffer + (y_dim - j - 1) * x_dim;

			for (i = 0; i < x_dim; i++) {
				g = b + 1;
				r = b + 2;
				*y = (unsigned char)(RGBYUV02990[*r] + RGBYUV05870[*g] + RGBYUV01140[*b]);
				*u = (unsigned char)(-RGBYUV01684[*r] - RGBYUV03316[*g] + (*b) / 2 + 128);
				*v = (unsigned char)((*r) / 2 - RGBYUV04187[*g] - RGBYUV00813[*b] + 128);
				b += 3;
				y++;
				u++;
				v++;
			}
		}
	}
	else {
		for (i = 0; i < size; i++)
		{
			g = b + 1;
			r = b + 2;
			*y = (unsigned char)(RGBYUV02990[*r] + RGBYUV05870[*g] + RGBYUV01140[*b]);
			*u = (unsigned char)(-RGBYUV01684[*r] - RGBYUV03316[*g] + (*b) / 2 + 128);
			*v = (unsigned char)((*r) / 2 - RGBYUV04187[*g] - RGBYUV00813[*b] + 128);
			b += 3;
			y++;
			u++;
			v++;
		}
	}

下采样:四个一组

	// subsample UV
	for (j = 0; j < y_dim / 2; j++)
	{
		psu = sub_u_buf + j * x_dim / 2;
		psv = sub_v_buf + j * x_dim / 2;
		pu1 = u_buffer + 2 * j * x_dim;
		pu2 = u_buffer + (2 * j + 1) * x_dim;
		pv1 = v_buffer + 2 * j * x_dim;
		pv2 = v_buffer + (2 * j + 1) * x_dim;
		for (i = 0; i < x_dim / 2; i++)
		{
			*psu = (*pu1 + *(pu1 + 1) + *pu2 + *(pu2 + 1)) / 4;
			*psv = (*pv1 + *(pv1 + 1) + *pv2 + *(pv2 + 1)) / 4;
			psu++;
			psv++;
			pu1 += 2;
			pu2 += 2;
			pv1 += 2;
			pv2 += 2;
		}
	}

释放缓冲区:

	free(u_buffer);
	free(v_buffer);

	return 0;
}

定义查找表,每个BGBYUVxxxx[i]存放的是256个xxxx与i的乘积 用于RGB向YUV的转换(公式计算):

void InitLookupTable()
{
	int i;

	for (i = 0; i < 256; i++) RGBYUV02990[i] = (float)0.2990 * i;
	for (i = 0; i < 256; i++) RGBYUV05870[i] = (float)0.5870 * i;
	for (i = 0; i < 256; i++) RGBYUV01140[i] = (float)0.1140 * i;
	for (i = 0; i < 256; i++) RGBYUV01684[i] = (float)0.1684 * i;
	for (i = 0; i < 256; i++) RGBYUV03316[i] = (float)0.3316 * i;
	for (i = 0; i < 256; i++) RGBYUV04187[i] = (float)0.4187 * i;
	for (i = 0; i < 256; i++) RGBYUV00813[i] = (float)0.0813 * i;
}

3.main.cpp

#include<Windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include "bmp2yuv.h"
#pragma warning(disable:4996)
#define u_int8_t	unsigned __int8
#define u_int		unsigned __int32
#define u_int32_t	unsigned __int32
#define FALSE		false
#define TRUE		true

结构体声明:

BITMAPFILEHEADER File_header;
BITMAPINFOHEADER Info_header;

在这里插入图片描述
在这里插入图片描述
获取BMP文件的长宽信息:

int frameWidth = Info_header.biWidth;
int frameHeight = Info_header.biHeight;

初始化各参数与指针:

    u_int frameWidth = 352;			/* --width=<uint> */
	u_int frameHeight = 240;		/* --height=<uint> */
	bool flip = FALSE;				/* --flip ?????*/
	unsigned int i;

	/* internal variables */
	char* bmpFileName = NULL;
	char* yuvFileName = NULL;
	FILE* bmpFile = NULL;
	FILE* yuvFile = NULL;
	u_int8_t* bmpBuf = NULL;
	u_int8_t* yBuf = NULL;
	u_int8_t* uBuf = NULL;
	u_int8_t* vBuf = NULL;
	u_int32_t videoFramesWritten = 0;

	int transFrames = 30;//转场帧数
	int photoFrames = 30;//静止图片帧数
	bmpFileName = argv[1];
	yuvFileName = argv[2];

	frameWidth = atoi(argv[3]);
	frameHeight = atoi(argv[4]);

打开BMP文件:

bmpFile = fopen(bmpFileName, "rb");
	if (fread(&File_header, sizeof(BITMAPFILEHEADER), 1, bmpFile) != 1)
	{
		printf("read file header error!");
		exit(0);
	}
	if (File_header.bfType != 0x4D42)
	{
		printf("Not bmp file!");
		exit(0);
	}
	else
	{
		printf("this is a bmp file!");
	}

	if (fread(&Info_header, sizeof(BITMAPINFOHEADER), 1, bmpFile) != 1)
	{
		printf("read info header error!");
		exit(0);
	}
	if (bmpFile == NULL)
	{
		printf("cannot find bmp file\n");
		exit(1);
	}
	else
	{
		printf("The input bmp file is %s\n", bmpFileName);
	}

打开创建好的YUV文件:

yuvFile = fopen(yuvFileName, "wb");
	if (yuvFile == NULL)
	{
		printf("cannot find yuv file\n");
		exit(1);
	}
	else
	{
		printf("The output yuv file is %s\n", yuvFileName);
	}

为从BMP文件里读出的数据与写入YUV文件的数据创建缓冲区:

bmpBuf = (u_int8_t*)malloc(frameWidth * frameHeight * 3);

	yBuf = (u_int8_t*)malloc(frameWidth * frameHeight);
	uBuf = (u_int8_t*)malloc((frameWidth * frameHeight) / 4);
	vBuf = (u_int8_t*)malloc((frameWidth * frameHeight) / 4);
if (bmpBuf == NULL || yBuf == NULL || uBuf == NULL || vBuf == NULL)
	{
		printf("no enought memory\n");
		exit(1);
	}
	while (fread(bmpBuf, 1, frameWidth * frameHeight * 3, bmpFile))
	{
		if (BMP2YUV(frameWidth, frameHeight, bmpBuf, yBuf, uBuf, vBuf, flip))
		{
			printf("error");
			return 0;
		}

		for (i = 0; i < frameWidth * frameHeight; i++)
		{
			if (yBuf[i] < 16) yBuf[i] = 16;
			if (yBuf[i] > 235) yBuf[i] = 235;
		}

		for (i = 0; i < frameWidth * frameHeight / 4; i++)
		{
			if (uBuf[i] < 16) uBuf[i] = 16;
			if (uBuf[i] > 240) uBuf[i] = 240;

			if (vBuf[i] < 16) vBuf[i] = 16;
			if (vBuf[i] > 240) vBuf[i] = 240;
		}

		fwrite(yBuf, 1, frameWidth * frameHeight, yuvFile);
		fwrite(uBuf, 1, (frameWidth * frameHeight) / 4, yuvFile);
		fwrite(vBuf, 1, (frameWidth * frameHeight) / 4, yuvFile);

		printf("\r...%d", ++videoFramesWritten);
	}

	printf("\n%u %ux%u video frames written\n",
		videoFramesWritten, frameWidth, frameHeight);

(在main函数之前)用于转场

u_int8_t* getInsertFrames_mix(u_int8_t* buf1, u_int8_t* buf2, int frame,int currentFrame, u_int frameWidth,u_int frameHeight) {
	//获取两帧混合(插值)
	//frame:总转场帧数
	//currentFrame:当前帧
	u_int8_t * mix = (u_int8_t*)malloc(frameWidth * frameHeight);
	for (int j = 0; j < frameHeight * frameWidth; j++) {
		*(mix + j) = int((*(buf2 + j)* currentFrame + *(buf1 + j)*(frame- currentFrame)) / frame);
	}
	return mix;
}

(在main函数之前)用于转场

u_int8_t* getInsertFrames_sweap(u_int8_t* buf1, u_int8_t* buf2, int frame, int currentFrame, u_int frameWidth, u_int frameHeight) {
	//获取两帧混合(扫下来)
	//frame:总转场帧数
	//currentFrame:当前帧
	u_int8_t * mix = (u_int8_t*)malloc(frameWidth * frameHeight);
	for (int h = 0; h < frameHeight; h++) {
			for (int w = 0; w < frameWidth; w++) {
				if (currentFrame * frameHeight / frame > h) {
					*(mix + h * frameWidth + w) = *(buf2 + h * frameWidth + w);
				}
				else {
					*(mix + h * frameWidth + w) = *(buf1 + h * frameWidth + w);
				}
			}
		}
	return mix;
}

转场实现:

	//从第二张图片开始加入转场
if (pic > 2) {
		for (int frame = 0; frame < transFrames; frame++) {
			u_int8_t* y_mix = getInsertFrames_mix(y_temp, yBuf, transFrames, frame, frameWidth, frameHeight);
			u_int8_t* u_mix = getInsertFrames_mix(u_temp, uBuf, transFrames, frame, frameWidth / 2, frameHeight / 2);
			u_int8_t* v_mix = getInsertFrames_mix(v_temp, vBuf, transFrames, frame, frameWidth / 2, frameHeight / 2);
			fwrite(y_mix, 1, frameWidth * frameHeight, yuvFile);
			fwrite(u_mix, 1, (frameWidth * frameHeight) / 4, yuvFile);
			fwrite(v_mix, 1, (frameWidth * frameHeight) / 4, yuvFile);
			}
		}
		
		//写静态图片
		for (int frame = 0; frame < photoFrames; frame++) {
			fwrite(yBuf, 1, frameWidth * frameHeight, yuvFile);
			fwrite(uBuf, 1, (frameWidth * frameHeight) / 4, yuvFile);
			fwrite(vBuf, 1, (frameWidth * frameHeight) / 4, yuvFile);
		}
			
		printf("\n%u %ux%u video frames written\n",
			pic, frameWidth, frameHeight);
		fclose(yuvFile);

		
		//将当前图片存入temp中,留给下一张转场用
		y_temp = (u_int8_t*)malloc(frameWidth * frameHeight);
		u_temp = (u_int8_t*)malloc(frameWidth * frameHeight*0.5);
		v_temp = (u_int8_t*)malloc(frameWidth * frameHeight*0.5);
		for (int i = 0; i < frameWidth * frameHeight; i++)
		{
			*(y_temp + i) = *(yBuf + i);
		}
		for (int i = 0; i < frameWidth * frameHeight/4; i++)
		{
			*(u_temp + i) = *(uBuf + i);
			*(v_temp + i) = *(vBuf + i);
		}

释放缓冲区:

	/* cleanup */
	free(yBuf);
	free(uBuf);
	free(vBuf);
	free(bmpBuf);
	fclose(bmpFile);
	fclose(yuvFile);

四、实验素材与实验结果

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
结果:(gif超过5M,无法上传)
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

五、思考

倘若不是24位BMP文件,则需要利用到调色板,或者是将其他位的BMP文件转化为24位的BMP文件,有待补充。


总结

BMP文件转换为YUV文件有两点,第一,要读取HEADER中的源数据,得到长宽信息(利用结构体);第二,读取BMP文件中的RGB信息,结合实验一的思路完成实验;第三,转场的实现函数有待深入学习。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值