实验目标:将所给的RGB空间的 down.rgb 转换为YUV色彩空间的 down.yuv,再将其重新转换为RGB空间,分析结果
一.rgb与yuv格式文件的数据
要对文件中的数据进行处理,首先当然要了解这种文件格式的数据特点。
说是rgb,yuv文件,其实本质上都是没有文件头的raw文件,文件头开始读就直接是数据。而rgb与yuv的数据存储也有一定的规范,即如下图所示:
其中YUV的取样格式为4:2:0,即Y全取样的同时,U,V的水平,垂直取样频率都为其二分之一,且存储方式为先存储所有像素的Y,然后是U,最后是V。
RGB的存储方式则是一个一个像素的B,G,R值顺序存储。(B1G1R1B2G2R2…)
二.大体思路与注意事项
思路其实很简单明确:
- 读取down.rgb中的数据储存到相应的buffer中。
- 将数据经过公式变换后按顺序输入到YUV文件中。
- 将得到的yuv数据通过公式变换重新转成RGB输入到新的rgb文件中。
但其中有许多需要留意的注意事项:
1. RGB-YUV的公式选择
如果你上网查找,可以发现许多不同版本的RB-YUV公式,究其原因是有些公式的不同涉及到了是否量化归一化,是否减少计算量等问题。
此外,本次实验设计的是8比特(256级)量化的色彩,对色差信号引入了压缩系数,并进行归一化,最后得到的转化公式为:
Y
=
0.299
R
+
0.587
G
+
0.114
B
Y = 0.299R + 0.587G + 0.114B
Y=0.299R+0.587G+0.114B
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.0831 B + 128 V = 0.5R - 0.4187G - 0.0831B + 128 V=0.5R−0.4187G−0.0831B+128
R = Y + 1.402 ∗ ( V − 128 ) R = Y + 1.402 * (V - 128) R=Y+1.402∗(V−128)
G = Y − 0.34414 ∗ ( U − 128 ) − 0.71414 ∗ ( V − 128 ) G = Y - 0.34414 * (U - 128) - 0.71414 * (V - 128) G=Y−0.34414∗(U−128)−0.71414∗(V−128)
B = Y + 1.772 ( U − 128 ) B = Y + 1.772 (U - 128) B=Y+1.772(U−128)
2.量化后的码电平分配
亮度信号:256级(0-255)量化后为防止过载,上端留20级,下端留16级,即范围为16 —235
色差信号:上端留15级,下端留16级,即范围为16 —240
3.还原RGB后防止溢出
可以想到很有可能YUV转换为RGB时数值范围会超出0-255,导致溢出失真,所以有必要在程序中加入放溢出的语句。(虽然最后发现我添加的语句并没有什么用。。。)
三,具体代码(C++, Visual Studio)
header.h
#include <iostream>
#include<stdio.h>
#include<stdlib.h>
#include<math.h>
#include<malloc.h>
void rgb_to_yuv(unsigned char* rgbBuf, unsigned char* yBuf, unsigned char* uBuf, unsigned char* vBuf, int Width, int Height);
void yuv_to_rgb(unsigned char* rBuf, unsigned char* gBuf, unsigned char* bBuf, unsigned char* yBuf, unsigned char* uBuf, unsigned char* vBuf, int Width, int Height);
rgb2yuv.cpp
#include"header.h"
using namespace std;
void rgb_to_yuv(unsigned char* rgbBuf, unsigned char* yBuf, unsigned char* uBuf, unsigned char* vBuf, int Width, int Height)
{
int y = 0;
for (int i = 0; i < Width * Height * 3; i = i + 3)
{
yBuf[y] = 0.114 * rgbBuf[i] + 0.587 * rgbBuf[i+1] + 0.299 * rgbBuf[i+2];
//Y范围:16-235
if (yBuf[y] > 235)
yBuf[y] = 235;
if (yBuf[y] < 16)
yBuf[y] = 16;
y++;
}
int u = 0, v = 0;
for (int i = 0; i < Width * Height * 3;)
{
uBuf[u] = 0.5 * (rgbBuf[i] + rgbBuf[i + 3] + rgbBuf[i + Width * 3] + rgbBuf[i + Width * 3 + 3])/4
- 0.3316 * (rgbBuf[i + 1] + rgbBuf[i + 4] + rgbBuf[i + Width * 3 + 1] + rgbBuf[i + Width * 3 + 4])/4
- 0.1684 * (rgbBuf[i + 2] + rgbBuf[i + 5] + rgbBuf[i + Width * 3 + 2] + rgbBuf[i + Width * 3 + 5])/4 + 128;
vBuf[v] = -0.083 * (rgbBuf[i] + rgbBuf[i + 3] + rgbBuf[i + Width * 3] + rgbBuf[i + Width * 3 + 3])/4
- 0.4187 * (rgbBuf[i + 1] + rgbBuf[i + 4] + rgbBuf[i + Width * 3 + 1] + rgbBuf[i + Width * 3 + 4])/4
+ 0.5 * (rgbBuf[i + 2] + rgbBuf[i + 5] + rgbBuf[i + Width * 3 + 2] + rgbBuf[i + Width * 3 + 5])/4 + 128;
u++;
v++;
//U,V范围:16-240
if (uBuf[u] > 240)
uBuf[u] = 240;
if (uBuf[u] < 16)
uBuf[u] = 16;
if (vBuf[v] > 240)
vBuf[v] = 240;
if (vBuf[v] < 16)
vBuf[v] = 16;
if ((i / 3) % 256 == 254)
i = i + 258 * 3;
else
i = i + 6;
}
}
yuv2rgb.cpp
#include"header.h"
using namespace std;
void yuv_to_rgb(unsigned char* rBuf, unsigned char* gBuf, unsigned char* bBuf, unsigned char* yBuf, unsigned char* uBuf, unsigned char* vBuf, int Width, int Height)
{
int j = 0;
for (int i = 0; i < Width * Height;)
{
rBuf[i] = yBuf[i] + 1.402 * (vBuf[j] - 128);
rBuf[i + 1] = yBuf[i + 1] + 1.402 * (vBuf[j] - 128);
rBuf[i + Width] = yBuf[i + Width] + 1.402 * (vBuf[j] - 128);
rBuf[i + Width + 1] = yBuf[i + Width + 1] + 1.402 * (vBuf[j] - 128);
gBuf[i] = yBuf[i] - 0.34414 * (uBuf[j] - 128) - 0.71414 * (vBuf[j] - 128);
gBuf[i + 1] = yBuf[i + 1] - 0.34414 * (uBuf[j] - 128) - 0.71414 * (vBuf[j] - 128);
gBuf[i + Width] = yBuf[i + Width] - 0.34414 * (uBuf[j] - 128) - 0.71414 * (vBuf[j] - 128);
gBuf[i + Width + 1] = yBuf[i + Width + 1] - 0.34414 * (uBuf[j] - 128) - 0.71414 * (vBuf[j] - 128);
bBuf[i] = yBuf[i] + 1.772 * (uBuf[j] - 128);
bBuf[i + 1] = yBuf[i + 1] + 1.772 * (uBuf[j] - 128);
bBuf[i + Width] = yBuf[i + Width] + 1.772 * (uBuf[j] - 128);
bBuf[i + Width + 1] = yBuf[i + Width + 1] + 1.772 * (uBuf[j] - 128);
//防RGB溢出
if (rBuf[i] < 0)
rBuf[i] = 0;
if (rBuf[i] > 255)
rBuf[i] = 255;
if (gBuf[i] < 0)
gBuf[i] = 0;
if (gBuf[i] > 255)
gBuf[i] = 255;
if (bBuf[i] < 0)
bBuf[i] = 0;
if (bBuf[i] > 255)
bBuf[i] = 255;
if (i % 256 == 254)
i = i + 256 + 2;
else
i = i + 2;
j++;
}
}
main.cpp
#include"header.h"
using namespace std;
int main(int argc[], char* argv[])
{
int Width = 256, Height = 256;
unsigned char *rgbBuf, *yBuf, *uBuf, *vBuf;
rgbBuf = (unsigned char *)malloc(sizeof (char) * (Width * Height * 3));
yBuf = (unsigned char *)malloc(sizeof (char) * (Width * Height));
uBuf = (unsigned char *)malloc(sizeof (char) * (Width * Height / 4));
vBuf = (unsigned char *)malloc(sizeof (char) * (Width * Height / 4));
FILE *rgb = NULL;
FILE *yuv = NULL;
rgb = fopen(argv[1], "rb");//argv[1]为down.rgb文件位置
if (rgb == NULL)
{
printf("errors in opening rgb file.");
exit(0);
}
fread(rgbBuf, sizeof(unsigned char), Width * Height * 3, rgb);//读出数据
fclose(rgb);
rgb_to_yuv(rgbBuf, yBuf, uBuf, vBuf, Width, Height);//RGBtoYUV
delete []rgbBuf;
//写入down.yuv
yuv = fopen(argv[2], "wb");//argv[2]为down.yuv文件位置
if (yuv == NULL)
{
printf("errors in opening yuv file.");
exit(0);
}
fwrite(yBuf, sizeof(unsigned char), Width * Height, yuv);
fwrite(uBuf, sizeof(unsigned char), Width * Height / 4, yuv);
fwrite(vBuf, sizeof(unsigned char), Width * Height / 4, yuv);
fclose(yuv);
unsigned char *rBuf, *gBuf, *bBuf;
rBuf = (unsigned char *)malloc(sizeof (unsigned char) * (Width * Height));
gBuf = (unsigned char *)malloc(sizeof (unsigned char) * (Width * Height));
bBuf = (unsigned char *)malloc(sizeof (unsigned char) * (Width * Height));
yuv_to_rgb(rBuf, gBuf, bBuf, yBuf, uBuf, vBuf, Width, Height);//YUVtoRGB
//写入recover_down.rgb
FILE *recover_rgb = NULL;
recover_rgb = fopen(argv[3], "wb");//argv[3]为recover_down.rgb文件位置
if (rgb == NULL)
{
printf("errors in opening recover_rgb file.");
exit(0);
}
for (int i = 0; i < Width * Height; i++)
{
fwrite(&bBuf[i], sizeof(unsigned char), 1, rgb);
fwrite(&gBuf[i], sizeof(unsigned char), 1, rgb);
fwrite(&rBuf[i], sizeof(unsigned char), 1, rgb);
}
delete []bBuf;
delete []gBuf;
delete []rBuf;
fclose(recover_rgb);
return 0;
}
四,结果与分析
原始图像(down.rgb):
本实验使用YUVviewerPlus观察图像:得到的 down.yuv 结果为:
还原为rgb后的图像recover_down.rgb为:(图像反转是因为读取格式为bmp24,默认数据读取顺序颠倒了)
我们发现有明显的红点和蓝点,出现了数据溢出,添加的防溢出语句并没有起到应有的效果。
原因似乎是数据变量的问题,有进展将第一时间更新文章。。
通过实验有如下总结:
- 色彩空间转化的首要任务时理解公式的含义,灵活运用不同的转化公式
- 要考虑到码电平分配造成的影响
- 需要考虑到数据溢出的可能性
- 对raw数据进行处理要了解其数据存储格式
- 熟练运用头文件和多个cpp文件的配合,是程序尽可能简洁美观易阅读和修改