一、内容拆解
(1) 编写RGB转化为YUV程序,重点掌握函数定义,部分查找表的初始化和调用,缓冲区分配。将得到的RGB文转换为YUV文件,用YUV Viewer播放器观看,验证是否正确。
(2) 编写将YUV转换为RGB的程序。将给定的实验数据用该程序转换为RGB文件。并与原RGB文件进行比较,如果有误差,分析误差来自何处。
==>
两次转换 => RGB与YUV420存储方式,转换公式
代码实现=>部分查找表是什么,怎么用?
分析误差=>误差怎么呈现,什么原因
二、思路概述
1. RGB->YUV
a. 读取rgb文件,新建yuv文件;
b. 开辟缓存区,rgb文件以RGB开辟一个3倍hw的数组,第0位B,第1位G,第2位B,第3位B……;yuv文件存储形式420,开辟一个hw存Y,两个h*w/4分别存U、V;
c. 根据公式建立部分查找表;
d. 调用部分查找表,每一组rgb确定一个Y,每偶数行偶数列rgb确定一个U和V,确保三者比例为4:2:0;
e. YUV取值范围限制,避免溢出;
f. 导出数据存入新建的yuv文件。
2. YUV->RGB
a. 读取上一yuv文件,新建rgb文件;
b. 开辟缓存区,rgb文件以RGB开辟一个3倍hw的数组,第0位B,第1位G,第2位B,第3位B……;yuv文件存储形式420,开辟一个hw存Y,两个h*w/4分别存U、V;
c. 根据公式建立部分查找表;
d. 调用部分查找表,每4个Y、1个U、1个V确定4个R、G、B;
e. RGB取值范围限制,避免溢出;
f. 导出数据存入新建的rgb文件。
g. 用Python 作图比较两个rgb文件,调整代码,分析误差。
三、关键代码
1. 查表法
(1) 好处
对于本实验,将一些事先计算好的结果存储在常量数组中,节省计算开销。
(2) 本实验用的是部分查找表,但是我也看到一篇博客说可以尝试完全查找表:
G=Y–0.343×(U–128)–0.714×(V–128) =>> G =
yig2g_table[y][uv2ig_table[u][v],而RB本身就只和YU或YV相关,所以这样我们一共需要4个8*8的二维表格,需要占用4乘2的16次方共256K内存.
看上去就很难,我应该不会写
RGB2YUV
float rgb2yuv02990[256], rgb2yuv05870[256], rgb2yuv01140[256], rgb2yuv01684[256],
rgb2yuv03316[256], rgb2yuv05000[256], rgb2yuv04187[256], rgb2yuv00813[256];
void Initrgb_table() {
for (int i = 0; i < 256; i++)
{
rgb2yuv02990[i] = (float)0.299 * i;
rgb2yuv05870[i] = (float)0.587 * i;
rgb2yuv01140[i] = (float)0.114 * i;
rgb2yuv01684[i] = (float)0.1684 * i;
rgb2yuv03316[i] = (float)0.3316 * i;
rgb2yuv05000[i] = (float)0.5 * i;
rgb2yuv04187[i] = (float)0.4187 * i;
rgb2yuv00813[i] = (float)0.0813 * i;
}
}
YUV2RGB
float yuv2rgb14030[256], yuv2rgb03430[256], yuv2rgb07140[256], yuv2rgb1770[256];
void Inityuv_table()
{
for (int i = 0; i < 256; i++)
{
yuv2rgb14030[i] = (float)1.4030 * (i - 128);
yuv2rgb03430[i] = (float)0.3430 * (i - 128);
yuv2rgb07140[i] = (float)0.7140 * (i - 128);
yuv2rgb1770[i] = (float)1.770 * (i - 128);
}
}
2. RGB->YUV
(1)开辟缓存区,YUV2RGB类似,不赘述
int Width = 256, Height = 256;
unsigned char* RGB;
unsigned char* Y, * U, * V;
unsigned char* R, * G, * B;
RGB = (unsigned char*)malloc(sizeof(char) * (Width * Height * 3));
R = (unsigned char*)malloc(sizeof(unsigned char) * (Width * Height));
G = (unsigned char*)malloc(sizeof(unsigned char) * (Width * Height));
B = (unsigned char*)malloc(sizeof(unsigned char) * (Width * Height));
Y = (unsigned char*)malloc(sizeof(char) * (Width * Height));
U = (unsigned char*)malloc(sizeof(char) * (Width * Height / 4));
V = (unsigned char*)malloc(sizeof(char) * (Width * Height / 4));
(2) 调用部分查找表,转换得到yuv数据
int y = 0;
for (int i = 0; i < Width * Height * 3; i = i + 3)
{
Initrgb_table();
Y[y] = rgb2yuv01140[RGB[i]] + rgb2yuv05870[RGB[i + 1]] + rgb2yuv02990[RGB[i + 2]];
//限制Y分量范围
if (Y[y] > 235)
Y[y] = 235;
if (Y[y] < 16)
Y[y] = 16;
y++;
}
UV略微复杂,在这里我看了很多博客,代码复现都或多或少有点问题,姑且一看
unsigned char* b = NULL;
unsigned char* g = NULL;
unsigned char* r = NULL;
unsigned char* u = NULL;
unsigned char* v = NULL;
b = (unsigned char*)RGB;
u = (unsigned char*)U;
v = (unsigned char*)V;
int m = 0, n = 0;
for (int j = 0; j < Height; j++)
{
for (int i = 0; i < Width; i++)
{
g = b + 1;
r = b + 2;
if (i%2 == 0 && j%2 ==0)
{
Initrgb_table();
*u = (unsigned char)(rgb2yuv05000[*b] - rgb2yuv03316[*g] - rgb2yuv01684[*r] + 128);
*v = (unsigned char)(-rgb2yuv00813[*b] - rgb2yuv04187[*g] + rgb2yuv05000[*r] + 128);
u++;
v++;
}
b += 3;
}
}
没有加限制,unsigned char*类型进行范围限制,图片偏绿,如果没有限制,效果反而还可以。
为了最终效果,我非常果断地没加……
3. YUV->RGB
(1) 调用部分查找表,转换得到rgb数据
void yuv2rgb(unsigned char* R, unsigned char* G, unsigned char* B, unsigned char* Y, unsigned char* U, unsigned char* V, int Width, int Height)
{
int j = 0;
for (int i = 0; i < Width * Height;)
{
Inityuv_table();
R[i] = Y[i] + yuv2rgb14030[V[j]];
R[i + 1] = Y[i + 1] + yuv2rgb14030[V[j]];
R[i + Width] = Y[i + Width] + yuv2rgb14030[V[j]];
R[i + Width + 1] = Y[i + Width + 1] + yuv2rgb14030[V[j]];
G[i] = Y[i] - yuv2rgb03430[U[j]] - yuv2rgb07140[V[j]];
G[i + 1] = Y[i + 1] - yuv2rgb03430[U[j]] - yuv2rgb07140[V[j]];
G[i + Width] = Y[i + Width] - yuv2rgb03430[U[j]] - yuv2rgb07140[V[j]];
G[i + Width + 1] = Y[i + Width + 1] - yuv2rgb03430[U[j]] - yuv2rgb07140[V[j]];
B[i] = Y[i] + yuv2rgb1770[U[j]];
B[i + 1] = Y[i + 1] + yuv2rgb1770[U[j]];
B[i + Width] = Y[i + Width] + yuv2rgb1770[U[j]];
B[i + Width + 1] = Y[i + Width + 1] + yuv2rgb1770[U[j]];
//限制RGB范围
if (R[i] < 0)
R[i] = 0;
if (R[i] > 255)
R[i] = 255;
if (G[i] < 0)
G[i] = 0;
if (G[i] > 255)
G[i] = 255;
if (B[i] < 0)
B[i] = 0;
if (B[i] > 255)
B[i] = 255;
if (i % 256 == 254)
i = i + 256 + 2;
else
i = i + 2;
j++;
}
}
(2) 分析两个rgb文件存在的误差(还是用上次的Jupyter代码画图)
比较可以看出,在0-25处,R的波动最大,这与图像中大面积绿、蓝色块有关,其他区域RGB曲线的更毛躁,这与YUV复现时缺少部分UV分量数据造成像素值跳跃有关,整体来看,rgb复现成果较好。
四、总结吐槽
- 从前辈博客看到的:一个既能播放RGB也能播放YUV的播放器,分享:http://wwww.236.6122.net/uploadFile/2013/vooya.rar。
- 上采样放大图像(或称为上采样(upsampling)或图像插值(interpolating))的主要目的是放大原图像,从而可以显示在更高分辨率的显示设备上。下采样缩小图像(或称为下采样(subsampled)或降采样(downsampled))的主要目的有两个:①使得图像符合显示区域的大小;②生成对应图像的缩略图。在实验中表现为,下采样我们用偶数行偶数列的RGB进行转换UV,上采样我们用4个Y、1个U、1个V来转换RGB,实际上就是将420转换为444;
- 转换公式问题,真的是看了很多博客,包括有总结类博客,但是依旧有问题,这里简单列举一下:
第一种这里的公式rgb取值范围为[0,1],y范围[0,1],uv范围[-0.5,0.5]
Y=0.2990R+0.5870G+0.1140B
R-Y=0.7010R-0.5870G-0.1140B
B-Y=-0.2990R-0.5870G+0.8860B
U=-0.1684R-0.3316G+0.5B
V=0.5R-0.4187G-0.0813B
第二种这里的公式rgb、yuv取值范围都为[0,255],防止溢出,需要加范围限制
R=Y+1.403×(V−128)
G=Y–0.343×(U–128)–0.714×(V–128)
B=Y+1.770×(U–128)
Y=0.2990R+0.5870G+0.1140B
U=-0.1684R-0.3316G+0.5B+128
V=0.5R-0.4187G-0.0813B+128 - 不要拖延,事情本来就很多,一拖什么都做不了。