实验一:
编写RGB转化为YUV程序,重点掌握函数定义,部分查找表的初始化和调用,缓冲区分配。将得到的RGB文件转换为YUV文件,用YUV Viewer播放器观看,验证是否正确。
编写将YUV转换为RGB的程序。将给定的实验数据用该程序转换为RGB文件。并与原
RGB文件进行比较,如果有误差,分析误差来自何处。
之前写过一版单纯 4 : 4 : 4 4:4:4 4:4:4的 R G B RGB RGB文件与 4 : 4 : 4 4:4:4 4:4:4的 Y U V YUV YUV文件的转换,链接如下,此版文章在前文的基础上进行了转换公式的推导以及采样的实现,若是只关注两种类型的转换可以直接去不采样版查看代码及思路。
文章目录
(一)YUV和RGB的转换公式及文件存储格式
R = ( 298 × Y + 411 × V − 57376 ) > > 8 G = ( 298 × Y − 101 × U − 211 × V + 35168 ) > > 8 B = ( 298 × Y + 519 × U − 71200 ) > > 8 Y = ( 66 × R + 129 × G + 25 × B ) > > 8 + 16 U = ( − 38 × R − 74 × G + 112 × B ) > > 8 + 128 V = ( 112 × R − 94 × G − 18 × B ) > > 8 + 128 \begin{aligned} &R=(298\times Y+411\times V-57376)>>8\\ &G=(298\times Y-101\times U-211\times V+35168)>>8\\ &B=(298\times Y+519\times U-71200)>>8\\ &Y=(66\times R+129\times G+25\times B)>>8+16\\ &U=(-38\times R-74\times G+112\times B)>>8+128\\ &V=(112\times R-94\times G-18\times B)>>8+128 \end{aligned} R=(298×Y+411×V−57376)>>8G=(298×Y−101×U−211×V+35168)>>8B=(298×Y+519×U−71200)>>8Y=(66×R+129×G+25×B)>>8+16U=(−38×R−74×G+112×B)>>8+128V=(112×R−94×G−18×B)>>8+128
以上公式是已经量化过的公式。
1.1 转换公式推导
1.1.1 模拟信号的转换公式
根据电视原理的知识,我们可以知道得到如下公式:
Y
=
0.2990
×
R
+
0.5870
×
G
+
0.1140
×
B
U
=
B
−
Y
=
−
0.2990
×
R
−
0.5870
×
G
+
0.8860
×
B
V
=
R
−
Y
=
0.7010
×
R
−
0.5870
×
G
−
0.1140
×
B
\begin{aligned} &Y=0.2990\times R+0.5870\times G+0.1140\times B\\ &U=B-Y=-0.2990\times R-0.5870\times G+0.8860\times B\\ &V=R-Y=0.7010\times R-0.5870\times G-0.1140\times B \end{aligned}
Y=0.2990×R+0.5870×G+0.1140×BU=B−Y=−0.2990×R−0.5870×G+0.8860×BV=R−Y=0.7010×R−0.5870×G−0.1140×B
1.1.2 数字信号的转换公式
归一化
为了便于处理,模拟信号转换为数字信号的时候需要进行归一化处理,使得色差信号的动态范围控制在 − 0.5 ∼ 0.5 -0.5\sim0.5 −0.5∼0.5之间。
归一话之后得到公式:
U
′
=
0.564
×
(
B
−
Y
)
=
0.564
×
(
−
0.2990
×
R
−
0.5870
×
G
+
0.8860
×
B
)
V
′
=
0.713
×
(
R
−
Y
)
=
0.713
×
(
0.7010
×
R
−
0.5870
×
G
−
0.1140
×
B
)
\begin{aligned} &U'=0.564\times (B-Y)=0.564\times (-0.2990\times R-0.5870\times G+0.8860\times B)\\ &V'=0.713\times (R-Y)=0.713\times (0.7010\times R-0.5870\times G-0.1140\times B) \end{aligned}
U′=0.564×(B−Y)=0.564×(−0.2990×R−0.5870×G+0.8860×B)V′=0.713×(R−Y)=0.713×(0.7010×R−0.5870×G−0.1140×B)
由于上述
U
′
、
V
′
U'、V'
U′、V′的取值范围在
±
350
m
v
\pm 350mv
±350mv之间,所以需要引入
+
350
m
v
+350mv
+350mv的偏置。公式变为:
U
′
′
=
0.564
×
(
B
−
Y
)
+
0.35
V
′
′
=
0.713
×
(
R
−
Y
)
+
0.35
\begin{aligned} &U''=0.564\times (B-Y)+0.35\\ &V''=0.713\times (R-Y)+0.35 \end{aligned}
U′′=0.564×(B−Y)+0.35V′′=0.713×(R−Y)+0.35
亮度信号量化后码电平分配
亮度信号量化后的码电平分配如图所示(图源《数字电视广播原理与应用》P36):
可见,亮度信号的峰值白电平对应码电平235,消隐电平对应码电平16,为了预防信号变动造成过载,上端留20级、下端留16级作为信号超越动态范围的保护带。其中码电平0和255为保护电平,是不允许出现在视频数据流中的,码字00和FF用于传送同步信息。亮度信号动态范围共占220个量化级。
色差信号量化后码电平分配
色差信号的码电平分配如图所示,图源《数字电视广播原理与应用》P37):
色差信号 C b 和 C r C_b和C_r Cb和Cr峰值的峰值白电平对应码电平240,0电平对应码电平16,为了预防信号变动造成过载,上端留15级,下端留16级作为信号超越动态范围的保护带。色差信号动态范围共占225个量化级。
码电平数字表达式
亮度信号和色差信号量化以后取其最邻近的整数作为码电平值,其数字化表达式为:
D
Y
=
I
N
T
[
(
219
Y
+
16
)
×
2
n
−
8
]
D
C
B
=
I
N
T
[
(
224
C
b
+
128
)
×
2
n
−
8
]
D
C
R
=
I
N
T
[
(
224
C
r
+
128
)
×
2
n
−
8
]
\begin{aligned} &D_Y=INT[(219Y+16)\times 2^{n-8}]\\ &D_{CB}=INT[(224C_b+128)\times 2^{n-8}]\\ &D_{CR}=INT[(224C_r+128)\times 2^{n-8}]\end{aligned}
DY=INT[(219Y+16)×2n−8]DCB=INT[(224Cb+128)×2n−8]DCR=INT[(224Cr+128)×2n−8]
将前面所推得的
Y
,
U
′
′
,
V
′
′
Y,U'',V''
Y,U′′,V′′带入上式,可得:
Y
=
(
66
×
R
+
129
×
G
+
25
×
B
)
>
>
8
+
16
U
=
(
−
38
×
R
−
74
×
G
+
112
×
B
)
>
>
8
+
128
V
=
(
112
×
R
−
94
×
G
−
18
×
B
)
>
>
8
+
128
\begin{aligned} &Y=(66\times R+129\times G+25\times B)>>8+16\\ &U=(-38\times R-74\times G+112\times B)>>8+128\\ &V=(112\times R-94\times G-18\times B)>>8+128\end{aligned}
Y=(66×R+129×G+25×B)>>8+16U=(−38×R−74×G+112×B)>>8+128V=(112×R−94×G−18×B)>>8+128
将式子写成矩阵的形式:
[
Y
U
V
]
=
1
256
[
66
129
25
−
38
−
74
112
112
−
94
−
18
]
[
R
G
B
]
+
[
16
128
128
]
\begin{aligned}\begin{bmatrix}Y\\U\\V\end{bmatrix}=\frac{1}{256}\begin{bmatrix}66&129&25\\-38&-74&112\\112&-94&-18\end{bmatrix}\begin{bmatrix}R\\G\\B\end{bmatrix}+\begin{bmatrix}16\\128\\128\end{bmatrix}\end{aligned}
⎣⎡YUV⎦⎤=2561⎣⎡66−38112129−74−9425112−18⎦⎤⎣⎡RGB⎦⎤+⎣⎡16128128⎦⎤
令矩阵 [ 66 129 25 − 38 − 74 112 112 − 94 − 18 ] \begin{bmatrix}66&129&25\\-38&-74&112\\112&-94&-18\end{bmatrix} ⎣⎡66−38112129−74−9425112−18⎦⎤为矩阵 A A A,则原式可转化为: [ R G B ] = 256 A − 1 [ Y − 16 U − 128 V − 128 ] \begin{aligned}\begin{bmatrix}R\\G\\B\end{bmatrix}=256A^{-1}\begin{bmatrix}Y-16\\U-128\\V-128\end{bmatrix}\end{aligned} ⎣⎡RGB⎦⎤=256A−1⎣⎡Y−16U−128V−128⎦⎤
由于 A − 1 A^{-1} A−1的数量级过于小,因此先将 A A A中的每个元素缩小为原来的 1 256 × 256 \frac{1}{256\times 256} 256×2561得到 A 1 A_1 A1。则上式转换为: [ R G B ] = A 1 − 1 [ Y − 16 G − 128 B − 128 ] ÷ 256 \begin{aligned}\begin{bmatrix}R\\G\\B\end{bmatrix}=A_1^{-1}\begin{bmatrix}Y-16\\G-128\\B-128\end{bmatrix}\div 256\end{aligned} ⎣⎡RGB⎦⎤=A1−1⎣⎡Y−16G−128B−128⎦⎤÷256
将系数取整,可得下式:
R
=
(
298
×
Y
+
411
×
V
−
57376
)
>
>
8
G
=
(
298
×
Y
−
101
×
U
−
211
×
V
+
35168
)
>
>
8
B
=
(
298
×
Y
+
519
×
U
−
71200
)
>
>
8
\begin{aligned} &R=(298\times Y+411\times V-57376) >>8 \\ &G=(298\times Y-101\times U-211\times V+35168)>>8 \\ &B=(298\times Y+519\times U-71200)>>8 \end{aligned}
R=(298×Y+411×V−57376)>>8G=(298×Y−101×U−211×V+35168)>>8B=(298×Y+519×U−71200)>>8
1.2 文件的存储格式
查阅资料可知, R G B RGB RGB文件的存储格式为 B G R B G R B G R ⋯ BGR BGR BGR\cdots BGRBGRBGR⋯, Y U V YUV YUV文件的存储格式为先存全部的 Y Y Y,再存全部的 U U U,最后存全部的 V V V。
(二)main函数的命令行参数
2.1 表示方法
一个程序的 m a i n ( ) main() main()函数可以包含两个参数:
- 第一个参数为 i n t int int类型;
- 第二个参数为字符串数组;
通常情况下,将第一个参数命名为 a r g c argc argc,第二个参数为 a r g v argv argv。由于字符串数组在函数头中的声明可以有两种形式,所以 m a i n ( ) main() main()函数也有两种写法。
-
m a i n ( ) main() main()函数写法一:
int main(int argc, char** argv) { return 0; }
-
m a i n ( ) main() main()函数写法二:
int main(int argc, char* argv[]) { return 0; }
2.2 使用方法
-
参数的含义:
int argc:表示字符串的数量。argc = 1 + 用户输入的字符串数目,argc的值由操作系统自动完成计算,程序员不需要对其进行赋值。
char argv[]*:存放的是多个字符串,字符串的形式如下:
argv[0] = 可执行文件的名称。例如change.exe。(这个字符串不需要用户输入,与argc相同,操作系统可自动生成。
argv[1] = 字符串1
argv[2] = 字符串2
argv[3] = 字符串3
⋮ \vdots ⋮
-
编程模式下如何进行参数输入?
使用平台为 V i s u a l S t u d i o 2019 Visual Studio 2019 VisualStudio2019,需要使用的文件为 d o w n . r g b down.rgb down.rgb,需要生成的文件为 u p . y u v , c h o . r g b up.yuv,cho.rgb up.yuv,cho.rgb,参数输入的步骤如下图流程所示:
1.打开上方任务栏调试界面的属性窗口 | ![]() |
---|---|
2.选择配置属性中的调试 | ![]() |
3.按照要求修改命令参数 | ![]() |
(三)彩色空间转换(不采样)代码初步实现
由上述知识,可以轻易地进行彩色空间转换的初步实现。代码由头文件 r g b 2 y u v . h , y u v 2 r g b . h rgb2yuv.h,yuv2rgb.h rgb2yuv.h,yuv2rgb.h和源文件 m a i n . c p p , r g b 2 y u v . c p p , y u v 2 r g b . c p p main.cpp,rgb2yuv.cpp,yuv2rgb.cpp main.cpp,rgb2yuv.cpp,yuv2rgb.cpp组成。
解决方案资源管理器如下图所示:
实验代码如下:
main.cpp
#include <iostream>
#include <cstdio>
#include <fstream>
#include "rgb2yuv.h"
#include "yuv2rgb.h"
using namespace std;
#define size 196608
#define usize 65536
#define vsize 131072
using namespace std;
int main(int argc, char** argv)
{
ifstream infile(argv[1],ios::binary);
ofstream outYUV(argv[2], ios::binary);
ofstream outRGB(argv[3], ios::binary);
if (!infile) { cout << "error to open file1!" << endl; }
if (!outYUV) { cout << "error to open file2" << endl; }
if (!outRGB) { cout << "error to open file3" << endl; }
unsigned char* in = new unsigned char[size];
unsigned char* YUV = new unsigned char[size];
unsigned char* RGB = new unsigned char[size];
infile.read((char*)in, size);
rgb2yuv(in,YUV,size, usize, vsize);//第一次转换
yuv2rgb(YUV, RGB, usize, vsize);//第二次转换
/*for (int i = 0; i < size; i++)
{
if (abs(in[i] - RGB[i]) > 5)
cout << "i=" << i << " in[" << i << "]=" << int(in[i]) << " RGB[" << i << "]=" << int(RGB[i]) << endl;
}*/
outYUV.write((char*)YUV, size);
outRGB.write((char*)RGB, size);
delete in;
delete YUV;
delete RGB;
infile.close();
outYUV.close();
outRGB.close();
return 0;
}
rgb2yuv.h
#pragma once
void rgb2yuv(unsigned char* rgb, unsigned char* yuv, int size,int usize,int vsize);
rgb2yuv.cpp
void rgb2yuv(unsigned char* rgb, unsigned char* yuv,int size,int usize,int vsize)
{
unsigned char r, g, b, y, u, v;
int j = 0;
for (int i = 0;i < size;)
{
b = *(rgb + i);
g = *(rgb + i + 1);
r = *(rgb + i + 2);
y = ((66 * r + 129 * g + 25 * b) >> 8) + 16;
u = ((-38 * r - 74 * g + 112 * b) >> 8) + 128;
v = ((112 * r - 94 * g - 18 * b) >> 8) + 128;
*(yuv + j) = y;
*(yuv + j + usize) = u;
*(yuv + j + vsize) = v;
i = i + 3;//每个rgb为1组
j++;
}
}
yuv2rgb.h
#pragma once
void yuv2rgb(unsigned char* yuv, unsigned char* rgb,int usize,int vsize);
yuv2rgb.cpp
#pragma once
#include "yuv2rgb.h"
#include <iostream>
using namespace std;
void yuv2rgb(unsigned char* yuv, unsigned char* rgb,int usize,int vsize)
{
unsigned char r, g, b, y, u, v;
int j = 0;
for (int i = 0; i < usize; i++)
{
y = *(yuv + i);
u = *(yuv + i + usize);
v = *(yuv + i + vsize);
r = (298 * y + 411 * v - 57344) >> 8;
g = (298 * y - 101 * u - 211 * v + 34739) >> 8;
b = (298 * y + 519 * u - 71117) >> 8;
*(rgb + j) = b;
*(rgb + j + 1) = g;
*(rgb + j + 2) = r;
j = j + 3;
}
}
实验结果
down.rgb | up.yuv | cho.rgb |
---|---|---|
![]() | ![]() | ![]() |
其中, d o w n . r g b down.rgb down.rgb和 c h o . r g b cho.rgb cho.rgb使用 Y U V v i e w e r P l u s YUVviewerPlus YUVviewerPlus打开的打开方式为:
打开的图像是倒置的图像(由于
b
m
p
bmp
bmp图像格式是倒着存储的,所以
.
r
g
b
.rgb
.rgb图像用
b
m
p
bmp
bmp方式打开时会倒)。上述表格中的图片为了便于分辨,已经用微信进行过旋转,但是
Y
U
V
YUV
YUV文件和
R
G
B
RGB
RGB文件之间依然有镜像的翻转,不过不影响观看与比对。
u p . y u v up.yuv up.yuv使用 Y U V v i e w e r P l u s YUVviewerPlus YUVviewerPlus打开的方式为:
由三幅图像的对比图可知, R G B t o Y U V RGB to YUV RGBtoYUV的实验成功完成,而 Y U V t o R G B YUVtoRGB YUVtoRGB的实验有一点问题,转出的 c h o . r g b cho.rgb cho.rgb图像中有较多红色的杂点。
(四)实验错误原因分析及代码修改
错误修改
推断可得,在进行 Y U V t o R G B YUVtoRGB YUVtoRGB的转换时,得到的 R G B RGB RGB三个数据可能超过了 u n s i g n e d c h a r unsigned\ char unsigned char类型可以表示的范围,即可能 < 0 <0 <0或者 > 255 >255 >255。
因此需要对 y u v 2 r g b . c p p yuv2rgb.cpp yuv2rgb.cpp文件进行适当的修正, > 255 >255 >255的值都直接 = 255 =255 =255, < 0 <0 <0的值都直接 = 0 =0 =0。
修改后 y u v 2 r g b . c p p yuv2rgb.cpp yuv2rgb.cpp的如下:
#pragma once
#include "yuv2rgb.h"
#include <iostream>
using namespace std;
void yuv2rgb(unsigned char* yuv, unsigned char* rgb,int usize,int vsize)
{
int r, g, b, y, u, v;
int j = 0;
for (int i = 0; i < usize; i++)
{
y = int(*(yuv + i));
u = int(*(yuv + i + usize));
v = int(*(yuv + i + vsize));
r = (298 * y + 411 * v - 57344) >> 8;
if (r > 255) { r = 255; }
if (r < 0) { r = 0; }
g = (298 * y - 101 * u - 211 * v + 34739) >> 8;
if (g > 255) { g = 255; }
if (g < 0) { g = 0; }
b = (298 * y + 519 * u - 71117) >> 8;
if (b > 255) { b = 255; }
if (b < 0) { b = 0; }
*(rgb + j) = unsigned char(b);
*(rgb + j + 1) = unsigned char(g);
*(rgb + j + 2) = unsigned char(r);
j = j + 3;
}
}
实验结果
down.rgb | up.yuv | cho.rgb |
---|---|---|
![]() | ![]() | ![]() |
至此,差不多完成了 R G B t o Y U V RGB to YUV RGBtoYUV和 Y U V t o R G B YUVtoRGB YUVtoRGB两个实验。
(五)优化代码(使用查找表的方法)
利用查找表,对代码进行了优化。代码由头文件 y u v r g b . h yuvrgb.h yuvrgb.h和源文件 m a i n . c p p , y u v r g b . c p p main.cpp,yuvrgb.cpp main.cpp,yuvrgb.cpp组成。
解决方案资源管理器如下图所示:
main.cpp
#include <iostream>
#include <cstdio>
#include <fstream>
#include "yuvrgb.h"
using namespace std;
#define size 196608
#define usize 65536
#define vsize 131072
#define height 256
#define weight 256
//查找表初始化
int* RGBYUV298 = new int[256];
int* RGBYUV411 = new int[256];
int* RGBYUV101 = new int[256];
int* RGBYUV211 = new int[256];
int* RGBYUV519 = new int[256];
int* RGBYUV66 = new int[256];
int* RGBYUV129 = new int[256];
int* RGBYUV25 = new int[256];
int* RGBYUV38 = new int[256];
int* RGBYUV74 = new int[256];
int* RGBYUV112 = new int[256];
int* RGBYUV94 = new int[256];
int* RGBYUV18 = new int[256];
int main(int argc, char** argv)
{
initLookupTable();
ifstream infile(argv[1],ios::binary);
ofstream outYUV(argv[2], ios::binary);
ofstream outRGB(argv[3], ios::binary);
if (!infile) { cout << "error to open file1!" << endl; }
if (!outYUV) { cout << "error to open file2" << endl; }
if (!outRGB) { cout << "error to open file3" << endl; }
unsigned char* infi = new unsigned char[size];
unsigned char* YUVfi = new unsigned char[size];
unsigned char* RGBfi = new unsigned char[size];
infile.read((char*)infi, size);
rgb2yuv(infi, YUVfi, size, usize, vsize);
yuv2rgb(YUVfi, RGBfi, usize, vsize);
outYUV.write((char*)YUVfi, size);
outRGB.write((char*)RGBfi, size);
fileend(infi,YUVfi,RGBfi);
infile.close();
outYUV.close();
outRGB.close();
return 0;
}
yuvrgb.h
#pragma once
void yuv2rgb(unsigned char* yuv, unsigned char* rgb,int usize,int vsize);
void rgb2yuv(unsigned char* rgb, unsigned char* yuv, int size, int usize, int vsize);
void initLookupTable();
void fileend(unsigned char* infi, unsigned char* YUVfi, unsigned char* RGBfi);
yuvrgb.cpp
#pragma once
#include "yuvrgb.h"
#include <iostream>
using namespace std;
extern int* RGBYUV298;
extern int* RGBYUV411;
extern int* RGBYUV101;
extern int* RGBYUV211;
extern int* RGBYUV519;
extern int* RGBYUV66 ;
extern int* RGBYUV129;
extern int* RGBYUV25 ;
extern int* RGBYUV38 ;
extern int* RGBYUV74 ;
extern int* RGBYUV112;
extern int* RGBYUV94 ;
extern int* RGBYUV18 ;
void initLookupTable()
{
for (int i = 0; i < 256; i++)
{
RGBYUV298[i] = 298 * i;
RGBYUV411[i] = 411 * i;
RGBYUV101[i] = 101 * i;
RGBYUV211[i] = 211 * i;
RGBYUV519[i] = 519 * i;
RGBYUV66[i] = 66 * i;
RGBYUV129[i] = 129 * i;
RGBYUV25[i] = 25 * i;
RGBYUV38[i] = 38 * i;
RGBYUV74[i] = 74 * i;
RGBYUV112[i] = 112 * i;
RGBYUV94[i] = 94 * i;
RGBYUV18[i] = 18 * i;
}
}
void yuv2rgb(unsigned char* yuv, unsigned char* rgb,int usize,int vsize)
{
int r, g, b, y, u, v;
int j = 0;
for (int i = 0; i < usize; i++)
{
y = int(*(yuv + i));
u = int(*(yuv + i + usize));
v = int(*(yuv + i + vsize));
/*r = (298 * y + 411 * v - 57344) >> 8;*/
r = (RGBYUV298[y]+ RGBYUV411[v]-57344)>>8;
if (r > 255) { r = 255; }
if (r < 0) { r = 0; }
/*g = (298 * y - 101 * u - 211 * v + 34739) >> 8;*/
g = (RGBYUV298[y] - RGBYUV101[u] - RGBYUV211[v] + 34739) >> 8;
if (g > 255) { g = 255; }
if (g < 0) { g = 0; }
/*b = (298 * y + 519 * u - 71117) >> 8;*/
b = (RGBYUV298[y] + RGBYUV519[u] - 71117) >> 8;
if (b > 255) { b = 255; }
if (b < 0) { b = 0; }
*(rgb + j) = unsigned char(b);
*(rgb + j + 1) = unsigned char(g);
*(rgb + j + 2) = unsigned char(r);
j = j + 3;
}
}
void rgb2yuv(unsigned char* rgb, unsigned char* yuv, int size, int usize, int vsize)
{
int r, g, b, y, u, v;
int j = 0;
for (int i = 0; i < size;)
{
b = int(*(rgb + i));
g = int(*(rgb + i + 1));
r = int(*(rgb + i + 2));
/*y = ((66 * r + 129 * g + 25 * b) >> 8) + 16;*/
y = ((RGBYUV66[r] + RGBYUV129[g] + RGBYUV25[b]) >> 8) + 16;
/*u = ((-38 * r - 74 * g + 112 * b) >> 8) + 128;*/
u = ((-RGBYUV38[r] - RGBYUV74[g] + RGBYUV112[b]) >> 8) + 128;
/*v = ((112 * r - 94 * g - 18 * b) >> 8) + 128;*/
v = ((RGBYUV112[r] - RGBYUV94[g] - RGBYUV18[b]) >> 8) + 128;
/*if ((y > 255) || (u > 255) || (v > 255) || (y < 0) || (u < 0) || (v < 0))
{
cout << "y=" << y << "u=" << u << "v=" << v << endl;
}*/
*(yuv + j) = unsigned char(y);
*(yuv + j + usize) = unsigned char(u);
*(yuv + j + vsize) = unsigned char(v);
i = i + 3;//每个rgb为1组
j++;
}
}
void fileend(unsigned char* infi, unsigned char* YUVfi, unsigned char* RGBfi)
{
delete infi;
delete YUVfi;
delete RGBfi;
delete RGBYUV298;
delete RGBYUV411;
delete RGBYUV101;
delete RGBYUV211;
delete RGBYUV519;
delete RGBYUV66;
delete RGBYUV129;
delete RGBYUV25;
delete RGBYUV38;
delete RGBYUV74;
delete RGBYUV112;
delete RGBYUV94;
delete RGBYUV18;
}
实验结果
down.rgb | up.yuv | cho.rgb |
---|---|---|
![]() | ![]() | ![]() |
至此,完成了4:4:4的 R G B RGB RGB文件与4:4:4的 Y U V YUV YUV文件之间的转换。
(六)彩色空间转换(采样)代码实现
如图所示是4:2:0的色度取样格式。因此可以得到从4:4:4的 Y U V YUV YUV文件转换为4:2:0的 Y U V YUV YUV文件的思路,即保留原有的 Y Y Y分量, U U U信号和 V V V信号都是取奇数行的奇数点。所以在原有的代码上稍加改动,按照上述思路,添加一个由4:4:4的 Y U V YUV YUV文件得到4:2:0的 Y U V YUV YUV文件的函数,可以实现从 R G B RGB RGB文件到 4 : 2 : 0 Y U V 4:2:0YUV 4:2:0YUV文件的转换。
关于从 4 : 2 : 0 Y U V 4:2:0YUV 4:2:0YUV文件到 R G B RGB RGB文件的转换,由于像素之间的相关性,可以想到,保留原有的 Y Y Y分量, U V UV UV分量可以通过复制来得到缺失的 U V UV UV分量,重新转换成4:4:4的 Y U V YUV YUV文件。按照上述思路,添加一个由4:2:0的 Y U V YUV YUV文件得到4:4:4的 Y U V YUV YUV文件的函数,可以实现从 4 : 2 : 0 Y U V 4:2:0YUV 4:2:0YUV文件到 R G B RGB RGB文件的转换。
实验代码如下:
main.cpp
#include <iostream>
#include <cstdio>
#include <fstream>
#include "yuvrgb.h"
using namespace std;
#define size 196608
#define csize 98304
#define usize 65536
#define vsize 131072
#define height 256
#define weight 256
//查找表初始化
int* RGBYUV298 = new int[256];
int* RGBYUV411 = new int[256];
int* RGBYUV101 = new int[256];
int* RGBYUV211 = new int[256];
int* RGBYUV519 = new int[256];
int* RGBYUV66 = new int[256];
int* RGBYUV129 = new int[256];
int* RGBYUV25 = new int[256];
int* RGBYUV38 = new int[256];
int* RGBYUV74 = new int[256];
int* RGBYUV112 = new int[256];
int* RGBYUV94 = new int[256];
int* RGBYUV18 = new int[256];
int main(int argc, char** argv)
{
initLookupTable();
ifstream infile(argv[1],ios::binary);
ofstream outYUV444(argv[2], ios::binary);
ofstream outYUV420(argv[3], ios::binary);
ofstream outYUV4442(argv[4], ios::binary);
ofstream outRGB(argv[5], ios::binary);
if (!infile) { cout << "error to open file1!" << endl; }
if (!outYUV444) { cout << "error to open file2" << endl; }
if (!outYUV420) { cout << "error to open file3" << endl; }
if (!outYUV4442) { cout << "error to open file4" << endl; }
if (!outRGB) { cout << "error to open file5" << endl; }
unsigned char* infi = new unsigned char[size];
unsigned char* YUV444fi = new unsigned char[size];
unsigned char* YUV420fi = new unsigned char[csize];
unsigned char* YUV4442fi = new unsigned char[size];
unsigned char* RGBfi = new unsigned char[size];
infile.read((char*)infi, size);
rgb2yuv(infi, YUV444fi, size, usize, vsize);
yuv444Tyuv420(YUV444fi,YUV420fi,size,weight);
yuv420Tyuv444(YUV420fi, YUV4442fi, size, weight);
yuv2rgb(YUV4442fi, RGBfi, usize, vsize);
outYUV444.write((char*)YUV444fi, size);
outYUV420.write((char*)YUV420fi, csize);
outYUV4442.write((char*)YUV4442fi, size);
outRGB.write((char*)RGBfi, csize);
fileend(infi,YUV444fi,YUV420fi,RGBfi);
infile.close();
outYUV444.close();
outYUV420.close();
outRGB.close();
return 0;
}
yuvrgb.h
#pragma once
void initLookupTable();
void yuv2rgb(unsigned char* yuv, unsigned char* rgb,int usize,int vsize);
void rgb2yuv(unsigned char* rgb, unsigned char* yuv, int size, int usize, int vsize);
void fileend(unsigned char* infi, unsigned char* YUV444fi, unsigned char* YUV420fi, unsigned char* RGBfi);
void yuv444Tyuv420(unsigned char* yuv444,unsigned char* yuv420,int size,int weight);
void yuv420Tyuv444(unsigned char* YUV420, unsigned char* YUV444, int size, int weight);
yuvrgb.cpp
#pragma once
#include "yuvrgb.h"
#include <iostream>
using namespace std;
extern int* RGBYUV298;
extern int* RGBYUV411;
extern int* RGBYUV101;
extern int* RGBYUV211;
extern int* RGBYUV519;
extern int* RGBYUV66 ;
extern int* RGBYUV129;
extern int* RGBYUV25 ;
extern int* RGBYUV38 ;
extern int* RGBYUV74 ;
extern int* RGBYUV112;
extern int* RGBYUV94 ;
extern int* RGBYUV18 ;
void initLookupTable()
{
for (int i = 0; i < 256; i++)
{
RGBYUV298[i] = 298 * i;
RGBYUV411[i] = 411 * i;
RGBYUV101[i] = 101 * i;
RGBYUV211[i] = 211 * i;
RGBYUV519[i] = 519 * i;
RGBYUV66[i] = 66 * i;
RGBYUV129[i] = 129 * i;
RGBYUV25[i] = 25 * i;
RGBYUV38[i] = 38 * i;
RGBYUV74[i] = 74 * i;
RGBYUV112[i] = 112 * i;
RGBYUV94[i] = 94 * i;
RGBYUV18[i] = 18 * i;
}
}
void yuv2rgb(unsigned char* yuv, unsigned char* rgb,int usize,int vsize)
{
int r, g, b, y, u, v;
int j = 0;
for (int i = 0; i < usize; i++)
{
y = int(*(yuv + i));
u = int(*(yuv + i + usize));
v = int(*(yuv + i + vsize));
r = (RGBYUV298[y]+ RGBYUV411[v]-57344)>>8;
if (r > 255) { r = 255; }
if (r < 0) { r = 0; }
g = (RGBYUV298[y] - RGBYUV101[u] - RGBYUV211[v] + 34739) >> 8;
if (g > 255) { g = 255; }
if (g < 0) { g = 0; }
b = (RGBYUV298[y] + RGBYUV519[u] - 71117) >> 8;
if (b > 255) { b = 255; }
if (b < 0) { b = 0; }
*(rgb + j) = unsigned char(b);
*(rgb + j + 1) = unsigned char(g);
*(rgb + j + 2) = unsigned char(r);
j = j + 3;
}
}
void rgb2yuv(unsigned char* rgb, unsigned char* yuv, int size, int usize, int vsize)
{
int r, g, b, y, u, v;
int j = 0;
for (int i = 0; i < size;)
{
b = int(*(rgb + i));
g = int(*(rgb + i + 1));
r = int(*(rgb + i + 2));
y = ((RGBYUV66[r] + RGBYUV129[g] + RGBYUV25[b]) >> 8) + 16;
u = ((-RGBYUV38[r] - RGBYUV74[g] + RGBYUV112[b]) >> 8) + 128;
v = ((RGBYUV112[r] - RGBYUV94[g] - RGBYUV18[b]) >> 8) + 128;
*(yuv + j) = unsigned char(y);
*(yuv + j + usize) = unsigned char(u);
*(yuv + j + vsize) = unsigned char(v);
i = i + 3;//每个rgb为1组
j++;
}
}
void yuv444Tyuv420(unsigned char* yuv444, unsigned char* yuv420,int size,int weight)
{
int Ysize = size / 3;
int j = 0;
for (int i = 0; i < Ysize; i++)//Y分量的计算
{
*(yuv420 + j) = *(yuv444 + i);
j++;
}
for (int i = 0; i < Ysize; )//U分量的计算
{
if (i % 2 == 1)
{
i++;
continue;
}
if ((i) % (2 * weight) == 0)
{
i = i + weight;
continue;
}
*(yuv420 + j) = *(yuv444 + i + Ysize);
j++;
i++;
}
for (int i = 0; i < Ysize; )//U分量的计算
{
if (i % 2 == 1)
{
i++;
continue;
}
if ((i) % (2 * weight) == 0)
{
i = i + weight;
continue;
}
*(yuv420 + j) = *(yuv444 + i + Ysize + Ysize);
j++;
i++;
}
}
void yuv420Tyuv444(unsigned char* YUV420, unsigned char* YUV444, int size, int weight)
{
int Ysize = size / 3;
int j = 0;
for (int i = 0; i < Ysize; i++)//Y分量的计算
{
*(YUV444 + i) = *(YUV420 + j);
j++;
}
for (int i = 0; i < Ysize; )//U分量的计算
{
if (i % 2 == 1)
{
j = j - 1;
*(YUV444 + i + Ysize) = *(YUV420 + j);
i++;
j++;
continue;
}
if ((i) % (2 * weight) == 0)
{
j = j - weight/2;
*(YUV444 + i + Ysize) = *(YUV420 + j);
i++;
j++;
continue;
}
*(YUV444 + i + Ysize) = *(YUV420 + j);
j++;
i++;
}
for (int i = 0; i < Ysize; )//U分量的计算
{
if (i % 2 == 1)
{
j = j - 1;
*(YUV444 + i + Ysize + Ysize) = *(YUV420 + j);
i++;
j++;
continue;
}
if ((i) % (2 * weight) == 0)
{
j = j - weight/2;
*(YUV444 + i + Ysize + Ysize) = *(YUV420 + j);
i++;
j++;
continue;
}
*(YUV444 + i + Ysize + Ysize) = *(YUV420 + j);
j++;
i++;
}
}
void fileend(unsigned char* infi, unsigned char* YUV444fi,unsigned char* YUV420fi, unsigned char* RGBfi)
{
delete infi;
delete YUV444fi;
delete YUV420fi;
delete RGBfi;
delete RGBYUV298;
delete RGBYUV411;
delete RGBYUV101;
delete RGBYUV211;
delete RGBYUV519;
delete RGBYUV66;
delete RGBYUV129;
delete RGBYUV25;
delete RGBYUV38;
delete RGBYUV74;
delete RGBYUV112;
delete RGBYUV94;
delete RGBYUV18;
}
实验结果
实验结果有5幅图,最开始的 d o w n . r g b down.rgb down.rgb,转换成4:4:4的 d o w n 444. y u v down444.yuv down444.yuv,再转换成4:2:0的 d o w n 420. y u v down420.yuv down420.yuv,再转成4:4:4的 d o w n 4442. y u v down4442.yuv down4442.yuv,再转成 c h o . r g b cho.rgb cho.rgb。结果如下表:
down | 444 | 420 | 4442 | cho |
---|---|---|---|---|
![]() | ![]() | ![]() | ![]() | ![]() |
至此,全部工作已完成。
(七)实验误差分析
误差来源可能是下述原因:
- R G B RGB RGB文件和 Y U V YUV YUV文件相互转换的公式推导时,经过了多次量化和小数点的舍去,使得转换公式本身便存在误差。
- 在4:4:4取样格式转换为4:2:0取样格式时,舍去了较多的色差信号。
- 在4:2:0取样格式转换为4:4:4取样格式时,使用了同一点的像素值来代替缺失点的像素。
- 在文件转换的过程中,产生了数据的溢出,并将溢出的点向上变为0或者向下变为255,也导致了误差的出现。