实验一:色彩空间转换实验
实验目的 :
1.学会从计算和程序的角度分析问题
通过完成本实验,理解计算思维,即从问题出发,通过逐步分析和分解,把原问题转化为可用程序方式解决的问题。在此过程中设计出一个解决方案。
2.进一步理解彩色空间的概念并掌握不同彩色空间转换的基本方程。
3.通过逐步运行程序,掌握编程细节:如查找表的设计,内存分配,对U和V信号进行下采样,文件读写过程等。掌握程序调试的基本方法。
具体思路:
1.首先从源头理解来,本实验可以认为是由两大步组成:
第一步:将给的down.rgb转化为对应的yuv文件,再用yuv viewer显示
第二步:再将得到的yuv文件,转换回去,重新化为rgb文件,再用pyuv显示,对比转化前后的区别
所以,从思路而言,我们便知道,解决这个问题就一定要用到电视原理里所学过的RGB和YUV之间的转换公式:
即RGB到YUV:
但是在本实验中,对分量信号进行8比特量化时,共分为256个等间隔的量化级。为了防止信号变动造成过载,在256级上端留20级,下端留16级作为信号超越动态范围的保护带,即色差信号零电平对应码电平128,则由RGB到YUV转换后的实际色差信号,即,我们在本实验中的转化公式为:
而同时,也要注意在代码中计算得出的每一个点的U/V的大小都应控制在16~235的区间内,以防止信号变动造成过载。
而在转换的这个过程中,还需要注意的一点在于:
我们需要转化为的YUV文件是4:2:0格式的,也就是说,转化后的YUV文件垂直水平分解力都是原来RGB文件的1/2。即新的YUV文件中,亮度的取样点数仍然是256256,但U和V两个色度分量的取样点数都下降为了128128,在新的形式之下,我们可以近似的认为,可以按照如下图所示的一组来进行分析:
也就是说,在上图中,圆形的点即为新图像的亮度点,而每四个亮度点共用一组色度信号。故在计算每个色度点取值的时候,我们需要用到原本RGB图像中的该位置处四点的值。此处可由此代码理解:
int u = 0;
for (int i = 0; i < w * h;)//caculate u
{
U_BUFFER[u] = (RED_BUFFER[i] + RED_BUFFER[i + 1] + RED_BUFFER[i + w] + RED_BUFFER[i + w + 1]) / 4 * (-0.1684) +
(GREEN_BUFFER[i] + GREEN_BUFFER[i + 1] + GREEN_BUFFER[i + w] + GREEN_BUFFER[i + w + 1]) / 4 * (-0.3316) +
(BLUE_BUFFER[i] + BLUE_BUFFER[i + 1] + BLUE_BUFFER[i + w] + BLUE_BUFFER[i + w + 1]) / 4 * (0.500) + 128;
if (U_BUFFER[u] > 235)
{
U_BUFFER[u] = 235;
}
if (U_BUFFER[u] < 16)
{
U_BUFFER[u] = 16;
}
if (i % 256 == 254)
{
i = i + 258;
}
else
{
i = i + 2;
}
u++;
}
2.在得知了如何进行计算之后,其他的操作其实大体和上一次的实验差不多:
第一步:程序的初始化,打开RGB文件,并事先建立好输出YUV文件的路径(利fopen函数)
第二步:计算RGB文件的长度(使用fseek和ftell函数),其实在本实验中,这一步也可以省略,因为我们已知是256256像素的RGB文件,也就是说RGB文件的长度为256256*3
第三步:设立缓冲区,利用fread函数读取RGB文件,并将RGB文件的中的数据全部写入缓冲区。
第四步:将RGB_BUFFER中混在一起的RGB数据分开到RED,GREEN和BLUE分别的BUFFER中,而后便可以通过分开后的BUFFER按照上面所述的方法计算相应的YUV值,并将之写入到YUV相应的BUFFER中
第五步:将YUV相应的BUFFER中的内容写出(利用fwrite函数),最后我们就可以在文件里看见一个生成的YUV文件。
第六步:释放缓冲区。
处理前的原图片:
具体代码实现:
#include<iostream>
#include"rgb2yuv.h"
using namespace std;
int main(int argc, char* argv[])
{
FILE* RGB = NULL;
FILE* YUV = NULL;
int w = 256;
int h = 256;
if ((fopen_s(&RGB,"down.rgb", "rb")) != 0)
{
cout << "Failed to open the RGB file!" << endl;
}
else
{
cout << "Successfully opened down.rgb!" << endl;
}
if ((fopen_s(&YUV,"argv[2]", "wb")) != 0)
{
cout << "Failed to write in the YUV file!" << endl;
}
else
{
cout << "Successfully written YUV!" << endl;
}
fseek(RGB, 0L, SEEK_END);
int size;
size = ftell(RGB);
fseek(RGB, 0L, SEEK_SET);//caculate the length of the rgb file
unsigned char* RGB_BUFFER = new unsigned char[size];
unsigned char* Y_BUFFER = new unsigned char[size / 3];
unsigned char* U_BUFFER = new unsigned char[size / 12];
unsigned char* V_BUFFER = new unsigned char[size / 12];
RGB_BUFFER = new unsigned char[size];
fread(RGB_BUFFER, sizeof(unsigned char), size, RGB);
unsigned char* RED_BUFFER = new unsigned char[size / 3];
unsigned char* GREEN_BUFFER = new unsigned char[size / 3];
unsigned char* BLUE_BUFFER = new unsigned char[size / 3];
//in order to caculate U,V,it will be more clearly to see if we figure out RGB
for (int i = 0; i < size / 3; i++)
{
RED_BUFFER[i] = RGB_BUFFER[3 * i + 2];
}
for (int i = 0; i < size / 3; i++)
{
GREEN_BUFFER[i] = RGB_BUFFER[3 * i + 1];
}
for (int i = 0; i < size / 3; i++)
{
BLUE_BUFFER[i] = RGB_BUFFER[3 * i];
}
for (int i = 0; i < w * h; i++)//caculate Y
{
Y_BUFFER[i] = RED_BUFFER[i] * 0.2990 +
GREEN_BUFFER[i] * 0.5870 +
BLUE_BUFFER[i] * 0.1140;
//Y=0.2990*Red+0.5870*Green+0.1440*Blue
if (Y_BUFFER[i] > 235)
{
Y_BUFFER[i] = 235;
}
if (Y_BUFFER[i] < 16)
{
Y_BUFFER[i] = 16;
}
//In order to avoid the overload of the luminace, so we need to reserve some space
}
int u = 0;
for (int i = 0; i < w * h;)//caculate u
{
U_BUFFER[u] = (RED_BUFFER[i] + RED_BUFFER[i + 1] + RED_BUFFER[i + w] + RED_BUFFER[i + w + 1]) / 4 * (-0.1684) +
(GREEN_BUFFER[i] + GREEN_BUFFER[i + 1] + GREEN_BUFFER[i + w] + GREEN_BUFFER[i + w + 1]) / 4 * (-0.3316) +
(BLUE_BUFFER[i] + BLUE_BUFFER[i + 1] + BLUE_BUFFER[i + w] + BLUE_BUFFER[i + w + 1]) / 4 * (0.500) + 128;
if (U_BUFFER[u] > 235)
{
U_BUFFER[u] = 235;
}
if (U_BUFFER[u] < 16)
{
U_BUFFER[u] = 16;
}
if (i % 256 == 254)
{
i = i + 258;
}
else
{
i = i + 2;
}
u++;
}
int v = 0;
for (int i = 0; i < w * h;)//caculate v
{
V_BUFFER[v] = (RED_BUFFER[i] + RED_BUFFER[i + 1] + RED_BUFFER[i + w] + RED_BUFFER[i + w + 1]) / 4 * (0.5) +
(GREEN_BUFFER[i] + GREEN_BUFFER[i + 1] + GREEN_BUFFER[i + w] + GREEN_BUFFER[i + w + 1]) / 4 * (-0.4187) +
(BLUE_BUFFER[i] + BLUE_BUFFER[i + 1] + BLUE_BUFFER[i + w] + BLUE_BUFFER[i + w + 1]) / 4 * (-0.0813) + 128;
if (V_BUFFER[v] > 235)
{
V_BUFFER[v] = 235;
}
if (V_BUFFER[v] < 16)
{
V_BUFFER[v] = 16;
}
if (i % 256 == 254)
{
i = i + 258;
}
else
{
i = i + 2;
}
v++;
}
fwrite(Y_BUFFER, sizeof(unsigned char), w * h, YUV);
fwrite(U_BUFFER, sizeof(unsigned char), w * h / 4, YUV);
fwrite(V_BUFFER, sizeof(unsigned char), w * h / 4, YUV);
free(RGB_BUFFER);
free(Y_BUFFER);
free(U_BUFFER);
free(V_BUFFER);
free(RED_BUFFER);
free(GREEN_BUFFER);
free(BLUE_BUFFER);
}
程序运行结果截图:
处理后用PYUV打开后存储的bmp图片:
第二部分的实验基本思路和实验一大同小异,需要注意的点仍然是YUV转化为RGB时的相关计算。
具体代码和处理后结果如下:
#include<iostream>
#include"yuv2rgb.h"
using namespace std;
int main(int argc, char* argv[])
{
FILE* YUV = NULL;
FILE* RGB = NULL;
int w = 256;
int h = 256;
if ((fopen_s(&YUV, "argv[2].yuv", "rb")) != 0)
{
cout << "Failed to open the YUV file!" << endl;
}
else
{
cout << "File successfully opened!" << endl;
}
if ((fopen_s(&RGB, "argv[2]", "wb")) != 0)
{
cout << "Failed to write in the RGB file!" << endl;
}
else
{
cout << "Successfully written RGB!" << endl;
}
fseek(YUV, 0L, SEEK_END);
int size;
size = ftell(YUV);
fseek(YUV, 0L, SEEK_SET);
unsigned char* YUV_BUFFER = new unsigned char[size];
unsigned char* Y_BUFFER = new unsigned char[size * 2 / 3];
unsigned char* U_BUFFER = new unsigned char[size / 6];
unsigned char* V_BUFFER = new unsigned char[size / 6];
YUV_BUFFER = new unsigned char[size];
fread(YUV_BUFFER, sizeof(unsigned char), size,YUV);
unsigned char* RED_BUFFER = new unsigned char[size * 2 / 3];
unsigned char* GREEN_BUFFER = new unsigned char[size * 2 / 3];
unsigned char* BLUE_BUFFER = new unsigned char[size * 2 / 3];
for (int i = 0; i < (size * 2 / 3); i++)
{
Y_BUFFER[i] = YUV_BUFFER[i];
}
for (int i = 0; i < (size * 1 / 6); i++)
{
U_BUFFER[i] = YUV_BUFFER[i + (size * 2 / 3)];
}
for (int i = 0; i < (size * 1 / 6); i++)
{
V_BUFFER[i] = YUV_BUFFER[i + (size * 5 / 6)];
}
int r = 0;
for (int i = 0; i < size * 2 / 3;)
{
RED_BUFFER[i] = Y_BUFFER[i] + 1.4020 * (V_BUFFER[r] - 128);
RED_BUFFER[i + 1] = Y_BUFFER[i+1] + 1.4020 * (V_BUFFER[r] - 128);
RED_BUFFER[i+256] = Y_BUFFER[i+256] + 1.4020 * (V_BUFFER[r] - 128);
RED_BUFFER[i+257] = Y_BUFFER[i+257] + 1.4020 * (V_BUFFER[r] - 128);
if (i % 256 == 254)
{
i = i + 258;
}
else
{
i = i + 2;
}
r++;
}
int g = 0;
for (int i = 0; i < size * 2 / 3;)
{
GREEN_BUFFER[i] = Y_BUFFER[i] - 0.3441 * (U_BUFFER[g] - 128) - 0.7139 * (V_BUFFER[g] - 128);
GREEN_BUFFER[i+1] = Y_BUFFER[i+1] - 0.3441 * (U_BUFFER[g] - 128) - 0.7139 * (V_BUFFER[g] - 128);
GREEN_BUFFER[i+256] = Y_BUFFER[i+256] - 0.3441 * (U_BUFFER[g] - 128) - 0.7139 * (V_BUFFER[g] - 128);
GREEN_BUFFER[i+257] = Y_BUFFER[i+257] - 0.3441 * (U_BUFFER[g] - 128) - 0.7139 * (V_BUFFER[g] - 128);
if (i % 256 == 254)
{
i = i + 258;
}
else
{
i = i + 2;
}
g++;
}
int b = 0;
for (int i = 0; i < size * 2 / 3;)
{
BLUE_BUFFER[i] = Y_BUFFER[i] + 1.7718 * (U_BUFFER[b] - 128) - 0.0013 * (V_BUFFER[b] - 128);
BLUE_BUFFER[i+1] = Y_BUFFER[i+1] + 1.7718 * (U_BUFFER[b] - 128) - 0.0013 * (V_BUFFER[b] - 128);
BLUE_BUFFER[i+256] = Y_BUFFER[i+256] + 1.7718 * (U_BUFFER[b] - 128) - 0.0013 * (V_BUFFER[b] - 128);
BLUE_BUFFER[i+257] = Y_BUFFER[i+257] + 1.7718 * (U_BUFFER[b] - 128) - 0.0013 * (V_BUFFER[b] - 128);
if (i % 256 == 254)
{
i = i + 258;
}
else
{
i = i + 2;
}
b++;
}
fwrite(BLUE_BUFFER, sizeof(unsigned char), size * 2 / 3, RGB);
fwrite(GREEN_BUFFER, sizeof(unsigned char), size * 2 / 3, RGB);
fwrite(RED_BUFFER, sizeof(unsigned char), size * 2 / 3, RGB);
free(YUV_BUFFER);
free(Y_BUFFER);
free(U_BUFFER);
free(V_BUFFER);
free(RED_BUFFER);
free(GREEN_BUFFER);
free(BLUE_BUFFER);
}
程序运行结果:
实验反思:
实验中的调试错误过程:
1.在昨晚rgb2yuv之后,做yuv2rgb的时候,复制之前生成的YUV文件到新的工程目录的时候,不要修改文件名,不要给YUV文件手动添加后缀.yuv,这样虽然添加之后YUV文件仍然可以正常打开,但是程序读取的时候会报错。
2.要清楚YUV文件存储时,其内部的存储顺序,以及在读取YUV和RGB文件的时候都要注意循环的写法。
实验结论:
转化后的YUV文件和RGB文件打开后的图片和原图片几乎没有明显差异。