文章目录
一、实验要求
- 阅读并调试rgb转yuv的代码
- 参考代码,自行编写yuv到rgb的转换程序
- 与原RGB文件进行比较,分析误差及其来源。
二、RGB2YUV实验
1、实验原理
在数字电视技术中,rgb信号转化为yuv数字信号传输应当需要以下步骤:
- 色彩空间的变换: Y → R − Y , B − Y Y\rightarrow R-Y,B-Y Y→R−Y,B−Y
- 归一化(控制动态范围): R − Y , B − Y → C r , C b R-Y,B-Y\rightarrow Cr,Cb R−Y,B−Y→Cr,Cb
- 量化:将
(
−
0.5
,
0.5
)
(-0.5,0.5)
(−0.5,0.5)的
C
r
,
C
b
Cr,Cb
Cr,Cb信号8比特/10比特量化。
(为了防止传输的信号变动,需要设置几级作为保护带)
然而yuv文件并不像数字电视信号一样需要考虑传输时的电平保护,所以文件转化时并不需要设置保护带。
只需得到色彩空间的转化公式:
Y
=
0.2990
R
+
0.5870
G
+
0.1140
B
Y=0.2990R+0.5870G+0.1140B
Y=0.2990R+0.5870G+0.1140B
U
=
−
0.1684
R
−
0.3316
G
+
0.5
B
+
128
U=-0.1684R-0.3316G+0.5B+128
U=−0.1684R−0.3316G+0.5B+128
V
=
0.5
R
−
0.4187
G
−
0.0813
B
+
128
V=0.5R-0.4187G-0.0813B+128
V=0.5R−0.4187G−0.0813B+128
这里的 U 、 V U、V U、V其实是《电视原理》课程中的 C b 、 C r Cb、Cr Cb、Cr,电视原理中的 U , V U,V U,V特指模拟电视,与数字的压缩系数不同。
2、代码调试:
解决错误
调试时注意到代码在读取文件时是使用的老写法fread()
,应当在 项目
-属性
中设置SDL检查为否,以忽略错误。
查找表
代码使用了查找表的方法,用空间换时间。在自编代码时,也应注意查找表的使用。
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、实验结果
跑通代码,使用YUVviewerPlus
显示输出结果如下:
三、YUV2RGB实验
1、实验原理
根据RGB转YUV的转化公式,可以得到转化中的系数矩阵
A
A
A,通过矩阵运算可以计算YUV到RGB的变化矩阵为
A
−
1
A^{-1}
A−1。如下:
A
[
R
G
B
]
=
[
Y
U
−
128
V
−
128
]
A\begin{bmatrix} R\\ G\\B \end{bmatrix}=\begin{bmatrix} Y\\ U -128\\V-128 \end{bmatrix}
A⎣⎡RGB⎦⎤=⎣⎡YU−128V−128⎦⎤左乘
A
−
1
A^{-1}
A−1
[
R
G
B
]
=
A
−
1
[
Y
U
−
128
V
−
128
]
\begin{bmatrix} R\\ G\\B \end{bmatrix}=A^{-1}\begin{bmatrix} Y\\ U -128\\V-128 \end{bmatrix}
⎣⎡RGB⎦⎤=A−1⎣⎡YU−128V−128⎦⎤
其中
A
=
[
0.299
0.587
0.114
−
0.1684
−
0.3316
0.5
0.5
−
0.4187
−
0.0813
]
A=\begin{bmatrix} 0.299 & 0.587 &0.114\\ -0.1684&-0.3316&0.5&\\0.5&-0.4187&-0.0813 \end{bmatrix}
A=⎣⎡0.299−0.16840.50.587−0.3316−0.41870.1140.5−0.0813⎦⎤
计算得:
A
−
1
=
[
1.075269
−
0.000040
1.507514
1.075269
−
0.344081
−
0.608359
1.075269
1.771792
0.104267
]
A^{-1}=\begin{bmatrix} 1.075269&-0.000040&1.507514\\ 1.075269&-0.344081&-0.608359\\ 1.075269 &1.771792 &0.104267 \end{bmatrix}
A−1=⎣⎡1.0752691.0752691.075269−0.000040−0.3440811.7717921.507514−0.6083590.104267⎦⎤
对结果保留四位小数,得到YUV转RGB的公式:
R
=
1.0753
∗
Y
+
1.5075
∗
(
V
−
128
)
;
R = 1.0753 * Y + 1.5075 * (V - 128);
R=1.0753∗Y+1.5075∗(V−128);
G
=
1.0753
∗
Y
−
0.3441
∗
(
U
−
128
)
−
0.6084
∗
(
V
−
128
)
;
G = 1.0753 * Y - 0.3441 * (U - 128) - 0.6084 * (V - 128);
G=1.0753∗Y−0.3441∗(U−128)−0.6084∗(V−128);
B
=
1.0753
∗
Y
+
1.7718
∗
(
U
−
128
)
+
0.1043
∗
(
V
−
128
)
;
B = 1.0753 * Y + 1.7718 * (U - 128)+0.1043 * (V - 128);
B=1.0753∗Y+1.7718∗(U−128)+0.1043∗(V−128);
2、实验过程
主函数
主函数用来读写文件、调用YUV2RGB函数。
int main()
{
//读yuv文件
FILE* file1, * file2;
fopen_s(&file1, "down.yuv", "rb");
unsigned char* y_buffer = new unsigned char[height * width];
unsigned char* u_buffer = new unsigned char[height * width*0.25];
unsigned char* v_buffer = new unsigned char[height * width*0.25];
fread(y_buffer, sizeof(unsigned char), height * width , file1);
fread(u_buffer, sizeof(unsigned char), height * width*0.25, file1);
fread(v_buffer, sizeof(unsigned char), height * width * 0.25, file1);
fclose(file1);
//转化函数
unsigned char* rgb_buffer = YUV2RGB(y_buffer, u_buffer, v_buffer);
//写入文件
fopen_s(&file2, "output.rgb", "wb");
fwrite(rgb_buffer, sizeof(unsigned char), height * width*3, file2);
fclose(file2);
return 0;
}
YUV2RGB函数
实现具体的转化功能,包括:
- 初始化查找表
- 扩展 U U U、 V V V分量
- 转化为RGB分量并按照rgb文件的格式排列
unsigned char* YUV2RGB(unsigned char* y_buffer,unsigned char* u_buffer, unsigned char* v_buffer)
{
//初始化查找表
InitLookupTable();
//扩展U、V
unsigned char* u_buffer_extend = extendUV(u_buffer, height, width);
unsigned char* v_buffer_extend = extendUV(v_buffer, height, width);
//转化为RGB
unsigned char* r_buffer = new unsigned char[height * width];
unsigned char* g_buffer = new unsigned char[height * width];
unsigned char* b_buffer = new unsigned char[height * width];
for (int i = 0; i < height * width; i++) {
r_buffer[i] = getR(y_buffer[i], u_buffer_extend[i], v_buffer_extend[i]);
g_buffer[i] = getG(y_buffer[i], u_buffer_extend[i], v_buffer_extend[i]);
b_buffer[i] = getB(y_buffer[i], u_buffer_extend[i], v_buffer_extend[i]);
}
//BGR排列
unsigned char* rgb_buffer = new unsigned char[height * width * 3];
for (int i = 0; i < height * width; i++) {
rgb_buffer[3 * i] = b_buffer[i];
rgb_buffer[3 * i + 1] = g_buffer[i];
rgb_buffer[3 * i + 2] = r_buffer[i];
}
return rgb_buffer;
}
初始化查找表:InitLookupTable()
由于公式中的值是 ( U − 128 ) 、 ( V − 128 ) (U-128)、(V-128) (U−128)、(V−128)所以查找表的初始化也有部分需要减去128:
static float YUV2RGB10753[256], YUV2RGB15075[256];
static float YUV2RGB03441[256], YUV2RGB06084[256];
static float YUV2RGB17718[256], YUV2RGB01043[256];
void InitLookupTable()
{
int i;
for (i = 0; i < 256; i++) YUV2RGB10753[i] = (float)1.0753 * i;
for (i = 0; i < 256; i++) YUV2RGB15075[i] = (float)1.5075 * (i - 128);
for (i = 0; i < 256; i++) YUV2RGB03441[i] = (float)0.3441 * (i - 128);
for (i = 0; i < 256; i++) YUV2RGB06084[i] = (float)0.6084 * (i - 128);
for (i = 0; i < 256; i++) YUV2RGB17718[i] = (float)1.7718 * (i - 128);
for (i = 0; i < 256; i++) YUV2RGB01043[i] = (float)0.1043 * (i - 128);
}
扩展UV分量:extendUV()函数
与RGB2YUV的downsample相对应,这个函数先将U或V分量按照行、列扩展为原来的4倍大。
420取样是行列各取原来的一半,那么反过来扩展时,新buffer的第i行、j列对应的是原buffer的第(i整除2)行、第(j整除2)列。
unsigned char* extendUV(unsigned char* buffer,int h,int w)
{
unsigned char* buffer_extend = new unsigned char[h * w];
for (int i = 0; i < h; i++) {
for (int j = 0; j < w; j++) {
buffer_extend[i * w + j] = buffer[int(i/2)*(w/2) + int(j/2)];
}
}
return buffer_extend;
}
单个像素转RGB:getR()、getG()、getB()
调用了查找表,返回单个像素转化后的R、G、B值
unsigned char getR(unsigned char Y, unsigned char U, unsigned char V)
{
double R = YUV2RGB10753[Y] + YUV2RGB15075[V];
return limitValue(R);
}
unsigned char getG(unsigned char Y, unsigned char U, unsigned char V)
{
double G = YUV2RGB10753[Y] - YUV2RGB03441[U] - YUV2RGB06084*[V];
return limitValue(G);
}
unsigned char getB(unsigned char Y, unsigned char U, unsigned char V)
{
double B = YUV2RGB10753[Y] + YUV2RGB17718[U]+ YUV2RGB01043[V];
return limitValue(B);
}
其中、limitValue()
函数用于防止计算结果溢出,将结果截断在0~255范围内。
int limitValue(int value)
{
return value > 255 ? 255:
(value < 0 ? 0 : value);
}
3、实验结果
将实验一中转化后的yuv文件作为输入,输出rgb文件,用作业一中python写的rgb查看器显示图片,结果如下:
4、误差分析
分布统计
实验结果肉眼难以分辨差别,还是用作业一的py跑了以下rgb值的概率分布:
发现转换后的RGB文件分布曲线的抖动更大了,变化不那么平滑了。
误差来源
- YUV420的downsample: YUV420为了便于传输,U、V分量只有原本的1/4。对于原本色度变化缓慢的图像区域,这种下采样造成的影响不大;但对于色度急剧变化的区域,下采样就导致色度信息被丢失了。因此在概率分布图中,R、G、B三原色的级数变化都没有原来那么平缓了。
- 运算过程的舍入误差: 按公式计算过程中,只保留了四位小数,最后也是向下取整,一定程度造成舍入误差。
四、实验总结
- 查找表是很重要的: 一开始并不理解查找表的意义,后来发现如果需要处理大量文件,如视频文件,这种以空间换时间的方法是很重要的。
- 冗余和压缩: 即使损失了近一半的大小,3/4的色度信息,YUV文件仍然在肉眼上观感并不差。这说明人眼的确是有灵敏度限制的,在视频压缩和处理中,应当运用好这种视觉冗余。
- 程序的工程化: 从老师提供的代码工程中收获到了很多。
- 将一些需要输入的信息作为文件的参数,避免修改代码本身,编译后也能直接使用。
- 对各种错误情况考虑周全,并有对应的措施。
- 分模块写代码,可读性大大提高。