读懂实验一思路与各个模块功能真的很重要!!
文章目录
前言
本次实验建立在实验一的基础上
一、实验目的
代码实现:将.bmp文件转化为.yuv文件,并将五张.yuv文件生成可以转场的.yuv视频
二、实验原理
1.BMP文件的结构
与RGB文件、YUV文件不同的是,BMP文件含有位图文件头BITMAPFILEHEADER、位图信息头BITMAPINFOHEADER、调色板Palette,本次实验需要现将BMP文件的RGB信息(位于实际的位图数据ImageData中)提取出来,再结合实验一的代码将RGB文件转换为YUV文件。
2.位图文件头内存储的元数据
3.位图信息头内存储的元数据
4.BMP文件转YUV文件实现思路:
- 程序初始化(打开两个文件、定义变量和缓冲区等)
- 读取BMP文件,抽取或生成RGB数据写入缓冲区(利用结构体)
(1)读位图文件头:判断文件是否可读入、判断文件是否为BMP文件
(2)读位图信息头(判断是否读出)
(3)判断像素的实际点阵数(获取图像的长和宽信息)
(4)开辟缓冲区,读数据,倒序存放
(5)根据每像素位数的不同,执行不同的操作:本程序转换的是24位BMP文件,直接取像素数据,写入RGB缓冲区 - 调用RGB2YUV的函数实现RGB到YUV数据的转换
- 写入YUV文件
- 关闭文件,释放缓冲区
三、代码实现
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信息,结合实验一的思路完成实验;第三,转场的实现函数有待深入学习。