在数字电视技术中,rgb信号转化为yuv数字信号传输应当需要以下步骤:
然而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特指模拟电视,与数字的压缩系数不同。
调试时注意到代码在读取文件时是使用的老写法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;
}
根据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);
主函数用来读写文件、调用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;
}
实现具体的转化功能,包括:
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;
}
由于公式中的值是 ( 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);
}
与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;
}
调用了查找表,返回单个像素转化后的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);
}
将实验一中转化后的yuv文件作为输入,输出rgb文件,用作业一中python写的rgb查看器显示图片,结果如下:
实验结果肉眼难以分辨差别,还是用作业一的py跑了以下rgb值的概率分布:
发现转换后的RGB文件分布曲线的抖动更大了,变化不那么平滑了。