基于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.1140BU−128=−0.1684R−0.3316G+0.5BV−128=0.5R−0.4187G−0.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.2990−0.16840.50.5870−0.3316−0.41870.11400.5−0.0813⎦⎤
[
Y
,
U
−
128
,
V
−
128
]
T
=
A
∗
[
R
,
G
,
B
]
T
[Y,U-128,V-128]^T =A*[R,G,B]^T
[Y,U−128,V−128]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=A−1∗[Y,U−128,V−128]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
A−1=⎣⎡1110−0.34411.77181.4020−0.7139−0.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.4020(V−128)G=Y−0.3441(U−128)−0.7139(V−128)B=Y+1.7718(U−128)−0.0013(V−128)
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
函数,把溢出的部分设为定值。