BMPtoYUV

一、BMP文件格式分析

bmp文件按如下顺序排列

位图文件头BITMAPFILEHEADER
位图信息头BITMAPINFOHEADER
调色板Palette
实际的位图数据ImageData

其中文件头信息决定读取时判断是否为BMP文件

位图文件头BITMAPFILEHEADER

在这里插入图片描述

 位图信息头BITMAPINFOHEADER

在这里插入图片描述

 notice:在进行BMP文件的读写时,要注意其显示顺序和存储顺序在行方向上相反

调色板:

调色板实际上是一个数组,它所包含的元素与位图所具有的颜色数相同,决定于biClrUsed和biBItCount字段。数组中每个元素的类型是一个RGBQUAD结构。真彩色无调色板部分。

二、实验过程

1.变量定义以及main函数命令行参数处理

#include<windows.h>
#include<stdlib.h>
#include<stdio.h>
#include<malloc.h>


int main(char argc, char* argv[])
{
	FILE* bmpFile = NULL;
	FILE* yuvFile = NULL;
	BITMAPFILEHEADER File_header;
	BITMAPINFOHEADER Info_header;
	int framew, frameh;
	unsigned char* rgbData = NULL;
	unsigned char* yBuff = NULL;
	unsigned char* vBuff = NULL;
	unsigned char* uBuff = NULL;
	const char* bmpFileName[5];
	const char* yuvFileName =NULL;
	bool flip = true;

for (int num = 0; num < 5; num++)
	{
		bmpFileName[num] = argv[num + 1];
	}
	yuvFileName = argv[6];//输出为argv[6]

	for (int i = 0; i < 5; i++)
	{
		bmpFile = fopen(bmpFileName[i], "rb"); //打开文件argv[1, 2, 3, 4, 5]
	}
	yuvFile = fopen(yuvFileName, "wb");


2.读取BMP

读取头文件:

if (fread(&File_header, sizeof(BITMAPFILEHEADER), 1, bmpFile) != 1) {
			cout << "read file header error" << endl;
			exit(0);
		}

通过BMP文件头判断文件格式

if (File_header.bfType != 0x4D42) {
			cout << "Not bmp file" << endl;
			exit(0);
		}
		else
			cout << "this is a bmp file" << endl;

 读取BMP头信息:

if (fread(&Info_header, sizeof(BITMAPINFOHEADER), 1, bmpFile) != 1) {
			cout << "read info header error" << endl;
			exit(0);
		}
//读取头信息中图像的宽高
framew = Info_header.biWidth;
frameh = Info_header.biHeight;

3.RGBtoYUV

(1)转换公式

根据上学期学过的电视原理知识我们知道如下公式:

 在C++中使用按位与计算:

Y = (66 * R + 129 *G + 25 * B) >> 8 + 16;
U = (-38 * R - 74 *G + 112 * B) >> 8 + 128;
V = (112 * R - 94 *G - 18 * B) >> 8 + 128;
(2)下采样

下采样,就是RGB转换为YUV图像时,会产生同样多个数量的Y\U\V,若YUV想要4:2:0的采样制式,则需要进行下采样。

具体实现方法:U\V同理,以U为例。将相邻的四个U计算加权平均值,合成为一个。

(3)查找表

即将所有分量转换后对应的值存储在一个数组里,使用时可以直接取用,是一种以空间为代价换时间效率的策略。

part1:从BMP中读取RGB的信息

void ReadRGB(FILE* pFile, BITMAPFILEHEADER& file_h, BITMAPINFOHEADER& info_h, unsigned char* rgbDataOut){
	unsigned long Loop, iLoop, jLoop, width, height, w, h;
	unsigned char mask, * Index_Data, * Data;
 
	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;
 
	width = w / 8 * info_h.biBitCount;
	height = h;
 
	Index_Data = (unsigned char*)malloc(height * width);
	Data = (unsigned char*)malloc(height * width);
 
	fseek(pFile, file_h.bfOffBits, 0);
	if (fread(Index_Data, height * width, 1, pFile) != 1) {
		//printf("read file error!");
		cout << "read file error" << endl;
		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];
		}
	}
 
	if (info_h.biBitCount == 24){//24bit读取BMP文件 不需调色盘
		memcpy(rgbDataOut, Data, height * width);
		free(Index_Data);
		free(Data);
		return;
	}
	//判断深度选取不同调色板
	RGBQUAD* pRGB = (RGBQUAD*)malloc(sizeof(RGBQUAD) * (unsigned int)pow(2, info_h.biBitCount));
	if (!MakePalette(pFile, file_h, info_h, pRGB))
		//printf("No palette!");
		cout << "No palette!" << endl;
 
	if (info_h.biBitCount == 16) {//16bitBMP文件的操作
		if (info_h.biCompression == BI_RGB) {
			for (Loop = 0; Loop < height * width; Loop += 2) {
				*rgbDataOut = (Data[Loop] & 0x1F) << 3;
				*(rgbDataOut + 1) = ((Data[Loop] & 0xE0) >> 2) + ((Data[Loop + 1] & 0x03) << 6);
				*(rgbDataOut + 2) = (Data[Loop + 1] & 0x7C) << 1;
				rgbDataOut += 3;
			}
		}
		free(Index_Data);
		free(Data);
		return;
	}
	
	for (Loop = 0; Loop < height * width; Loop++) {//1-8bit
		switch (info_h.biBitCount) {
		case 1:
			mask = 0xC0;
			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 ? Data[Loop] : ((Data[Loop] & mask) >> (8 - shiftCnt * info_h.biBitCount));
			*rgbDataOut = pRGB[index].rgbBlue;
			*(rgbDataOut + 1) = pRGB[index].rgbGreen;
			*(rgbDataOut + 2) = pRGB[index].rgbRed;
			if (info_h.biBitCount == 8)
				mask = 0;
			else
				mask >>= info_h.biBitCount;
			rgbDataOut += 3;
			shiftCnt++;
		}
	}
	free(Index_Data);
	free(Data);
	free(pRGB);
}

part2:调色板 

bool MakePalette(FILE* pFile, BITMAPFILEHEADER& file_h, BITMAPINFOHEADER& info_h, RGBQUAD* pRGB_out) {
	if ((file_h.bfOffBits - sizeof(BITMAPFILEHEADER) - info_h.biSize) == sizeof(RGBQUAD) * pow(2, info_h.biBitCount)) {
		fseek(pFile, sizeof(BITMAPFILEHEADER) + info_h.biSize, 0);
		fread(pRGB_out, sizeof(RGBQUAD), (unsigned int)pow(2, info_h.biBitCount), pFile);
		return true;
	}
	else
		return false;
}

part3:RGBtoYUV函数以及查找表

//部分查找表
static float RGBYUV02990[256];
static float RGBYUV05870[256];
static float RGBYUV01140[256];
static float RGBYUV01684[256];
static float RGBYUV03316[256];
static float RGBYUV04187[256];
static float RGBYUV00813[256];
 
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;
	}
}
int RGB2YUV(unsigned long w, unsigned long h, unsigned char* rgbData, unsigned char* y, unsigned char* u, unsigned char* v,int flip) {
	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, nr, ng, nb, nSize;
	//对每个像素进行rgb2yuv转换
	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] = (unsigned char)(-RGBYUV01684[nr] - RGBYUV03316[ng] + nb / 2 + 128);
		vtemp[i] = (unsigned char)(nr / 2 - RGBYUV04187[ng] - RGBYUV00813[nb] + 128);
		i++;
	}
	//对u信号及v信号进行采样
	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++;
		}
	}
	//对yuv信号进行限量化电平处理
	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 < w * h/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;
	}
	free(utemp);
	free(vtemp);
	return 0;
}

主函数中的调用:

rgbData = (unsigned char*)malloc(framew * frameh * 3);
yBuff = (unsigned char*)malloc(framew * frameh);
uBuff = (unsigned char*)malloc(framew * frameh / 4);
vBuff = (unsigned char*)malloc(framew * frameh / 4);

//调用ReadRGB函数,从BMP中读取RGB信息
		ReadRGB(bmpFile, File_header, Info_header, rgbData);
		//调用RGB2YUV文件
		if (RGB2YUV(framew, frameh, rgbData, yBuff, uBuff, vBuff,flip)) {
			//printf("error");
			cout << "error" << endl;
			return 0;
		}
		for (int i = 0; i < framew * frameh; i++) {
			if (yBuff[i] < 16)
				yBuff[i] = 16;
			if (yBuff[i] > 235)
				yBuff[i] = 235;
		}
		for (int i = 0; i < framew * frameh / 4; i++) {
			if (uBuff[i] < 16)
				uBuff[i] = 16;
			if (vBuff[i] < 16)
				vBuff[i] = 16;
			if (uBuff[i] > 240)
				uBuff[i] = 240;
			if (vBuff[i] > 240)
				vBuff[i] = 240;
		}
		for (int i = 0; i < 40; i++) {
			fwrite(yBuff, 1, framew * frameh, yuvFile);
			fwrite(uBuff, 1, (framew * frameh) / 4, yuvFile);
			fwrite(vBuff, 1, (framew * frameh) / 4, yuvFile);
		}
		free(rgbData);
		free(yBuff);
		free(vBuff);
		free(uBuff);
		fclose(bmpFile);

}

三、实验结果

命令行参数设置:

 

使用YUV播放器播放:

 

 

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值