【数据压缩(三)】基于C++完成rgb文件和yuv文件的互相转换

前言

本次实验在老师的带领下已经完成了一半(rgb转yuv),在后续yuv转rgb的代码中本次不再采用过去的一个main函数走到底的方法,尝试把方法函数和主函数分块处理。

一、实验要求

  • 基本要求(必做)编写RGB转化为YUV程序,重点掌握函数定义,部分查找表的初始化和调用,缓冲区分配。将得到的RGB文件转换为YUV文件,用YUV Viewer播放器观看,验证是否正确。
  • 提高要求(可选)编写将YUV转换为RGB的程序。将给定的实验数据用该程序转换为RGB文件。并与原RGB文件进行比较,如果有误差,分析误差来自何处。
  • 总结RGB和YUV彩色空间转换的转换公式及编程实现的算法并写成实验报告。

二、rgb转yuv

1、实验原理

  • 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.1140BU128=0.1684R0.3316G+0.5BV128=0.5R0.4187G0.0813B

  • 码电平分配以及数字表达式
    在对分量信号进行8比特均匀量化时,共分为256个等间隔的量化级。为了防止信号变动造成过载,在256级上端留20级,下端留16级作为信号超越动态范围的保护带。
    色差信号经过归一化处理后,动态范围为-0.5一0.5,让色差零电平对应码电平128,色差信号总共占225个量化级。在256级上端留15级,下端留16级作为信号超越动态范围的保护带。
  • 色度格式
    4:2:0格式是指色差信号U,V的取样频率为亮度信号取样频率的四分之一,在水平方向和垂直方向上的取样点数均为Y的一半。在程序中先将4:4:4格式的YUV数据得出,然后UV数据以2*2的方块为单元取平均,最后得到4:2:0格式数据。

2、代码调试

运行结果:
在这里插入图片描述
运用YUVviewersPlus打开输出文件down_new.yuv查看:

3、调试中遇到的问题

起初按照所学要求更改一些配置之后一直无法正常运行,反反复复重做几遍后,偶然发现问题出在活动解决方案平台这里,将平台从x86改为x64后成功,不过不太明白具体原因,这里记录一下。
在这里插入图片描述

三、yuv转rgb

1、实验原理

yuv转rgb公式:
将已知的rgb转yuv公式化为矩阵式:
A = [ 0.2990 0.5870 0.1140 − 0.1684 − 0.3316 0.5 0.5 − 0.4187 − 0.0813 ] A= \begin{bmatrix} 0.2990 & 0.5870 & 0.1140 \\ -0.1684 & −0.3316 & 0.5 \\ 0.5 & -0.4187 & -0.0813 \end{bmatrix} \quad A=0.29900.16840.50.58700.33160.41870.11400.50.0813

[ Y , U − 128 , V − 128 ] T = A ∗ [ R , G , B ] T [Y,U-128,V-128]^T =A*[R,G,B]^T [Y,U128,V128]T=A[R,G,B]T
等式左右两侧左乘A的逆矩阵得:
[ R , G , B ] T = A − 1 ∗ [ Y , U − 128 , V − 128 ] T [R,G,B]^T=A^{-1}*[Y,U-128,V-128]^T [R,G,B]T=A1[Y,U128,V128]T
借助matlab进行运算得到:
A − 1 = [ 1 0 1.4020 1 − 0.3441 − 0.7139 1 1.7718 − 0.0013 ] A^{-1}= \begin{bmatrix} 1& 0& 1.4020\\ 1& -0.3441 & -0.7139\\ 1& 1.7718& -0.0013 \end{bmatrix} \quad A1=11100.34411.77181.40200.71390.0013
故yuv转rgb公式如下:
R = Y + 1.4020 ( V − 128 ) G = Y − 0.3441 ( U − 128 ) − 0.7139 ( V − 128 ) B = Y + 1.7718 ( U − 128 ) − 0.0013 ( V − 128 ) R=Y+1.4020(V-128)\\ G=Y-0.3441(U-128)-0.7139(V-128)\\ B=Y+1.7718(U-128)-0.0013(V-128) R=Y+1.4020V128G=Y0.3441U1280.7139V128B=Y+1.7718U1280.0013V128

2、代码实现

1)头文件yuv2rgb.h

int YUV2RGB(unsigned char* yuv, unsigned char* rgb, int height, int width);
int YUV420TOYUV444(unsigned char* yuv420, unsigned char* yuv444, int height, int width);


void InitLookupTable();
float judge(float i);

2)主函数文件main.cpp

#define _CRT_SECURE_NO_DEPRECATE
#include "yuv2rgb.h"
#include<iostream>

using namespace std;

int main(int argc, char** argv)
{
	//定义变量
	int frameWidth;
	int frameHeight;
	char* rgbFileName = NULL;
	char* yuvFileName = NULL;
	FILE* rgbFile = NULL;
	FILE* yuvFile = NULL;
	unsigned char* yuv420Buf = NULL;
	unsigned char* yuv444Buf = NULL;
	unsigned char* rgbBuf = NULL;

	//传入参数
	yuvFileName = argv[1];
	rgbFileName = argv[2];
	frameWidth = atoi(argv[3]);
	frameHeight = atoi(argv[4]);

	//分配空间
	yuv420Buf = (unsigned char*)malloc(frameWidth * frameHeight * 3 / 2 * sizeof(unsigned char));
	yuv444Buf = (unsigned char*)malloc(frameWidth * frameHeight * 3 * sizeof(unsigned char));
	rgbBuf = (unsigned char*)malloc(frameWidth * frameHeight * 3);

	//打开文件
	yuvFile = fopen(yuvFileName, "rb");
	if (yuvFile == NULL)
	{
		printf("cannot find yuv file\n");
		exit(1);
	}
	else
	{
		cout << "The input yuv file is " << yuvFileName << endl;
	}

	rgbFile = fopen(rgbFileName, "wb");
	if (rgbFile == NULL)
	{
		printf("cannot find rgb file\n");
		exit(1);
	}
	else
	{
		cout << "The input rgb file is " << rgbFileName << endl;
	}

	//读取数据
	fread(yuv420Buf, 1, frameWidth * frameHeight * 3 / 2, yuvFile);
	
	//采样格式转换
	YUV420TOYUV444(yuv420Buf, yuv444Buf, frameHeight, frameWidth);
	
	//debug检查格式转换成功没
	//fwrite(yuv444Buf, 1, frameHeight * frameWidth * 3, rgbFile);

	//yuv转rgb
	YUV2RGB(yuv444Buf, rgbBuf, frameHeight, frameWidth);
	
	//写入文件
	fwrite(rgbBuf, 1, frameHeight * frameWidth * 3, rgbFile);

	//关闭文件
	fclose(yuvFile);
	fclose(rgbFile);

	return 0;
}


3)方法函数文件yuv2rgb.cpp

a.文件开始部分

引头文件,定义用在查找表中的全局变量。

#include "yuv2rgb.h"
#include<iostream>
using namespace std;

static float YUVRGB14020[256], YUVRGB03441[256], YUVRGB07139[256], YUVRGB17718[256], YUVRGB00013[256];

b.YUV420TOYUV444函数

用来将原有的420格式的yuv文件转换为444格式的yuv文件,方便后续转rgb的处理。

int YUV420TOYUV444(unsigned char* yuv420, unsigned char* yuv444, int height, int width)
{
	//定义变量,分配空间
	unsigned char* y = NULL, * ou = NULL, * ov = NULL;
	unsigned char* u = NULL, * v = NULL;
	ou = (unsigned char*)malloc(height * width / 4 * sizeof(unsigned char));
	ov = (unsigned char*)malloc(height * width / 4 * sizeof(unsigned char));
	y = (unsigned char*)malloc(height * width * sizeof(unsigned char));
	u = (unsigned char*)malloc(height * width * sizeof(unsigned char));
	v = (unsigned char*)malloc(height * width * sizeof(unsigned char));

	//分离yuv数据
	for (int i = 0; i < height * width; i++)
	{
		*(y + i) = *yuv420;
		yuv420++;
	}

	for (int i = 0; i < height * width * 1 / 4; i++)
	{
		*(ou + i) = *yuv420;
		yuv420++;
	}

	for (int i = 0; i < height * width * 1 / 4; i++)
	{
		*(ov + i) = *yuv420;
		yuv420++;
	}

	//采样格式转换
	for (int i = 0; i < height; i++)
	{
		for (int j = 0; j < width; j++)
		{
			*(u + i * width + j) = *(ou + (i / 2) * (width / 2) + j / 2);
			*(v + i * width + j) = *(ov + (i / 2) * (width / 2) + j / 2);
		}
	}

	//整理到444格式的yuv中
	for (int i = 0; i < height * width; i++)
	{
		*(yuv444 + i) = *y;
		*(yuv444 + i + height * width) = *u;
		*(yuv444 + i + height * width * 2) = *v;
		y++;
		u++;
		v++;
	}

	return 0;
}
c.InitLookupTable查找表函数

数字过多,做查找表方便函数里数学公式的运用。

void InitLookupTable()
{
	int i;

	for (i = 0; i < 256; i++) YUVRGB14020[i] = (float)1.4020 * (i - 128);
	for (i = 0; i < 256; i++) YUVRGB03441[i] = (float)0.3441 * (i - 128);
	for (i = 0; i < 256; i++) YUVRGB07139[i] = (float)0.7139 * (i - 128);
	for (i = 0; i < 256; i++) YUVRGB17718[i] = (float)1.7718 * (i - 128);
	for (i = 0; i < 256; i++) YUVRGB00013[i] = (float)0.0013 * (i - 128);
}
d.judge函数

用来控制转换后的rgb结果溢出造成图像失真。

float judge(float i)
{
	//控制在0~255内
	if (i > 255) { i = 255; }
	if (i < 0) { i = 0; }
	return i;
}
e.YUV2RGB函数

实现444格式的yuv文件向rgb文件转换。

int YUV2RGB(unsigned char* yuv, unsigned char* rgb, int height, int width)
{
	//引入查找表
	InitLookupTable();

	//定义变量,分配空间
	unsigned char* y = NULL, * u = NULL, * v = NULL;
	unsigned char* r = NULL, * g = NULL, * b = NULL;
	y = (unsigned char*)malloc(height * width * sizeof(unsigned char));
	u = (unsigned char*)malloc(height * width * sizeof(unsigned char));
	v = (unsigned char*)malloc(height * width * sizeof(unsigned char));
	r = (unsigned char*)malloc(height * width * sizeof(unsigned char));
	g = (unsigned char*)malloc(height * width * sizeof(unsigned char));
	b = (unsigned char*)malloc(height * width * sizeof(unsigned char));

	//分离yuv变量
	for (int i = 0; i < height; i++)
	{
		for (int j = 0; j < width; j++)
		{
			*(y + i * width + j) = *(yuv + i * width + j);
			*(u + i * width + j) = *(yuv + i * width + j + height * width);
			*(v + i * width + j) = *(yuv + i * width + j + height * width * 2);
		}
	}

	//利用公式求rgb
	for (int i = 0; i < height; i++)
	{
		for (int j = 0; j < width; j++)
		{
			*(r + i * width + j) = (unsigned char)judge((float)*y + YUVRGB14020[*v]);
			*(g + i * width + j) = (unsigned char)judge((float)*y - YUVRGB03441[*u] - YUVRGB07139[*v]);
			*(b + i * width + j) = (unsigned char)judge((float)*y + YUVRGB17718[*u] - YUVRGB00013[*v]);
			y++;
			u++;
			v++;
		}
	}

	//按照BGR顺序排列将分离的rgb分量放入整合的rgb中
	for (int i = 0; i < height * width; i++)
	{
		*(rgb + i * 3) = *b;
		*(rgb + i * 3 + 1) = *g;
		*(rgb + i * 3 + 2) = *r;
		b++;
		g++;
		r++;
	}

	return 0;
}

3、运行结果与分析

在这里插入图片描述
用YUVviewersPlus打开输出的图像并与原rgb图像做对比:
在这里插入图片描述
对比两图片可看到基本上没有什么差异,但是放大点看发现程序输出的图像和原rgb图在一些细节地方仍有些许色彩差异,思考后认为其原因为:

  • down_new.rgb是由down.yuv转换过来的,而down.yuv是420格式的,在转化为444格式的yuv文件过程中,444格式的yuv文件uv分量存在信息的丢失,造成在色彩急剧变化的区域存在较为明显的差异。

4、遇到的问题和解决

先按照自己的理解一口气把代码写完了,输出的文件可以说是全失真的,找又找不到问题,debug的过程中非常痛苦,一些稍大的问题如下:

1)对指针的误用。

用法1:

	//分离yuv数据
	for (int i = 0; i < height * width; i++)
	{
		*(y + i) = *yuv420;
		yuv420++;
	}

用法2:

	//分离yuv数据
	for (int i = 0; i < height * width; i++)
	{
		y = yuv420;
		y++;
		yuv420++;
	}

方法2中*y指针在循环结束时已经指向了结尾,在后续的使用中还要再用循环调回来才能重新用*y的开头,为了避免多余操作,最后还是用了方法1来实现。

2)找不到错误的位置

在输出图像频繁失真后,回过头来找问题发现真的大海捞针。。搁置了几天之后突然想到一个方法可以稍微定位一下问题所在位置:

  • 先将主函数分成两部分:
    一部分是将420格式yuv转换为444格式yuv;
    另一部分为444格式yuv转换为rgb格式。
    将主函数第二部分内容屏蔽掉之后,加上
//debug检查格式转换成功没
fwrite(yuv444Buf, 1, frameHeight * frameWidth * 3, rgbFile);

用YUVviewersPlus先打开看了看,发现在第一部分的实现中已经出错,又仔细查看修改了YUV420TOYUV444函数的逻辑,最后成功。

3)色彩溢出

本来没有设置judge函数,输出的图片存在少量但极为明显的红色和蓝色点点,问了问同学,最后设置上了judge函数,把溢出的部分设为定值。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值