实验报告 RGB2YUV&YUV2RGB
1.编写RGB转化为YUV程序,重点掌握函数定义,部分查找表的初始化和调用,缓冲区
分配。将得到的RGB文件转换为YUV文件,用YUV Viewer播放器观看,验证是否正确。
2.编写将YUV转换为RGB的程序。将给定的实验数据用该程序转换为RGB文件。并与原
RGB文件进行比较,如果有误差,分析误差来自何处。
一、两种色彩空间的转换公式
两种色彩空间RGB和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
色差信号可直接用相应的基色与亮度作差得到
B
−
Y
=
−
0.2990
R
−
0.5870
G
+
0.8860
B
B - Y = - 0.2990R - 0.5870G + 0.8860B
B−Y=−0.2990R−0.5870G+0.8860B
R
−
Y
=
0.7010
R
−
0.5870
G
+
0.1140
B
R - Y = 0.7010R - 0.5870G + 0.1140B
R−Y=0.7010R−0.5870G+0.1140B
在实际应用中,我们要将色差信号的动态范围设置为-0.5~0.5之间,因此我们要将上述色差信号系数的最大值映射到0.5,得到的色差信号记为U V
U
=
−
0.1684
R
−
0.3316
G
+
0.5
B
{U = - 0.1684R - 0.3316G + 0.5B}
U=−0.1684R−0.3316G+0.5B
V
=
0.5
R
−
0.4187
G
−
0.0813
B
{V = 0.5R - 0.4187G - 0.0813B}
V=0.5R−0.4187G−0.0813B
在存储时,我们要将色差信号的零电平对应到128。
综上,可得到由RGB色彩空间转换到YUV色彩空间的表达式,
{
Y
=
0.299
R
+
0.5870
G
+
0.1140
B
U
=
−
0.1684
R
−
0.3316
G
+
0.5
B
+
128
V
=
0.5
R
−
0.4187
G
−
0.0813
B
+
128
\left\{ {\begin{array}{l} {Y = 0.299R + 0.5870G + 0.1140B}\\ {U = - 0.1684R - 0.3316G + 0.5B + 128}\\ {V = 0.5R - 0.4187G - 0.0813B + 128} \end{array}} \right.
⎩⎨⎧Y=0.299R+0.5870G+0.1140BU=−0.1684R−0.3316G+0.5B+128V=0.5R−0.4187G−0.0813B+128
为了得到由YUV色彩空间转换到RGB色彩空间的表达式,我们只需将上式的系数提取出来,组成3*3的系数矩阵,求其逆矩阵,即可得到YUV2RGB的转换式系数
{
R
=
1.000
Y
+
1.4020
(
V
−
128
)
G
=
1.000
Y
−
0.3441
(
U
−
128
)
−
0.7139
(
V
−
128
)
B
=
1.000
Y
+
1.7718
(
U
−
128
)
−
0.0013
(
V
−
128
)
\left\{ {\begin{array}{l} {R = 1.000Y + 1.4020\left( {V - 128} \right)}\\ {G = 1.000Y - 0.3441\left( {U - 128} \right) - 0.7139\left( {V - 128} \right)}\\ {B = 1.000Y + 1.7718\left( {U - 128} \right) - 0.0013\left( {V - 128} \right)} \end{array}} \right.
⎩⎨⎧R=1.000Y+1.4020(V−128)G=1.000Y−0.3441(U−128)−0.7139(V−128)B=1.000Y+1.7718(U−128)−0.0013(V−128)
二、具体代码和实现成果
1.rgb2yuv
#include<stdio.h>
#include<stdlib.h>
int main(int argc, char* argv[])
{
constexpr auto wid = 256;
constexpr auto hei = 256;
//point
FILE *p_rgb;
FILE *p_yuv;
//fopen
if (fopen_s(&p_rgb, argv[1], "rb") != 0)
{
printf("打开down.rgb失败!\n");
}
if (fopen_s(&p_yuv, argv[2], "wb") != 0)
{
printf("打开down.yuv失败!\n");
}
//buffer
unsigned char* b_rgb = (unsigned char*)malloc(sizeof(unsigned char) * 3 * wid * hei);
unsigned char* b_y = (unsigned char*)malloc(sizeof(unsigned char) * wid * hei);
unsigned char* b_u = (unsigned char*)malloc(sizeof(unsigned char) * wid * hei / 4);
unsigned char* b_v = (unsigned char*)malloc(sizeof(unsigned char) * wid * hei / 4);
//fread
fread(b_rgb, sizeof(unsigned char), 3 * wid * hei, p_rgb);
//measure
//计算y
for (int i = 0; i < wid * hei; i++)
{
b_y[i] = 0.299 * b_rgb[3 * i + 2] + 0.587 * b_rgb[3 * i + 1] + 0.114 * b_rgb[3 * i];
if (b_y[i] > 235)
b_y[i] = 235;
if(b_y[i]<16)
b_y[i] = 16;
}
//计算u
int k = 0;
for(int i=0;i<hei;i++)
for (int j = 0; j < wid; j++)
{
if (i % 2 == 0 && j % 2 == 0)
{
b_u[k]= -0.1684 * b_rgb[3 * (i*wid+j) + 2] - 0.3316 * b_rgb[3 * (i*wid+j) + 1]
+ 0.5 * b_rgb[3 * (i*wid+j)]+128;
if (b_u[k] < 16)
b_u[k] = 16;
if (b_u[k] > 240)
b_u[k] = 240;
k++;
}
}
//计算v
k = 0;
for (int i = 0; i < hei; i++)
for (int j = 0; j < wid; j++)
{
if (i % 2 == 0 && j % 2 == 0)
{
b_v[k] = 0.5 * b_rgb[3 * (i * wid + j) + 2] - 0.4187 * b_rgb[3 * (i * wid + j) + 1]
- 0.0183 * b_rgb[3 * (i * wid + j)]+128;
if (b_v[k] < 16)
b_v[k] = 16;
if (b_v[k] > 240)
b_v[k] = 240;
k++;
}
}
//fwrite
fwrite(b_y, sizeof(unsigned char), wid*hei, p_yuv);
fwrite(b_u, sizeof(unsigned char), wid * hei / 4, p_yuv);
fwrite(b_v, sizeof(unsigned char), wid * hei / 4, p_yuv);
//free
free(b_rgb);
free(b_y);
free(b_u);
free(b_v);
//fclose
fclose(p_rgb);
fclose(p_yuv);
return 0;
}
得到的yuv文件用YUV播放器观看
可以看到得到的yuv文件与原rgb文件图片几乎一致。
2.rgb2yuv
#include<stdio.h>
#include<stdlib.h>
int main(int argc, char* argv[])
{
constexpr auto wid = 256;
constexpr auto hei = 256;
//point
FILE* p_rgb;
FILE* p_yuv;
//fopen
if (fopen_s(&p_rgb, argv[1], "wb") != 0)
{
printf("打开down.rgb失败!\n");
}
if (fopen_s(&p_yuv, argv[2], "rb") != 0)
{
printf("打开down.yuv失败!\n");
}
//buffer
unsigned char* b_rgb = (unsigned char*)malloc(sizeof(unsigned char) * 3 * wid * hei);
unsigned char* b_y = (unsigned char*)malloc(sizeof(unsigned char) * wid * hei);
unsigned char* b_u = (unsigned char*)malloc(sizeof(unsigned char) * wid * hei / 4);
unsigned char* b_v = (unsigned char*)malloc(sizeof(unsigned char) * wid * hei / 4);
//fread
fread(b_y, sizeof(unsigned char), wid * hei, p_yuv);
fread(b_u, sizeof(unsigned char), wid * hei / 4, p_yuv);
fread(b_v, sizeof(unsigned char), wid * hei / 4, p_yuv);
//measure
//处理y
unsigned char y[wid][hei] = { 0 };
for (int i = 0; i < hei; i++)
for (int j = 0; j < wid; j++)
{
y[i][j] = b_y[(i * wid + j)];
}
//处理uv
unsigned char u[wid][hei] = { 0 };
unsigned char v[wid][hei] = { 0 };
int k = 0;
for (int i = 0; i < hei; i++)
for (int j = 0; j < wid; j++)
{
if (i % 2 == 0 && j % 2 == 0)
{
u[i][j] = b_u[k];
v[i][j] = b_v[k];
k++;
}
else if (i % 2 == 0 && j % 2 == 1)
{
u[i][j] = u[i][j - 1];
v[i][j] = v[i][j - 1];
}
else if (i % 2 == 1 && j % 2 == 0)
{
u[i][j] = u[i - 1][j];
v[i][j] = v[i - 1][j];
}
else if (i % 2 == 1 && j % 2 == 1)
{
u[i][j] = u[i - 1][j - 1];
v[i][j] = v[i - 1][j - 1];
}
}
//计算bgr
for (int i = 0; i < hei; i++)
for (int j = 0; j < wid; j++)
{
b_rgb[3*(i*wid+j)] = y[i][j] + 1.7718 * (u[i][j] - 128) - 0.0013 * (v[i][j] - 128); //b
b_rgb[3*(i*wid+j) + 1] = y[i][j] - 0.3441 * (u[i][j] - 128) - 0.7139 * (v[i][j] - 128); //g
b_rgb[3*(i*wid+j) + 2] = y[i][j] + 1.4020 * (v[i][j] - 128); ; //r
}
//fwrite
fwrite(b_rgb, sizeof(unsigned char), 3*wid * hei, p_rgb);
//free
free(b_rgb);
free(b_y);
free(b_u);
free(b_v);
//fclose
fclose(p_rgb);
fclose(p_yuv);
return 0;
}
得到的rgb文件用matlab预览
可以发现rgb文件和初始图片大致相同,但是会存在一些红色和蓝色的坏点,对此,我们用matlab定点考察,会发现造成坏点的原因是数据溢出,一些原本为0的数值变为255了
误差原因有下:
i.运算中涉及unchar和double之间的转换,容易发生最终结果为0的数值计算为255,254.
ii.由于UV值使用4:2:0的方式采样,一部分像素UV值并不是尤其原本的RGB值计算而来,这样使用自身的Y值和非自身的UV值还原RGB,可能带来微小的误差,使数据溢出。
改进方法:
在这幅自然景物图片中,没有纯色调颜色的像素,可以使RB值大于240的像素将为10,达到无技巧修正的目的。