【数据压缩】使用C++实现YUV与RGB色彩空间转换

一、实验要求

  • 阅读并调试rgb转yuv的代码
  • 参考代码,自行编写yuv到rgb的转换程序
  • 与原RGB文件进行比较,分析误差及其来源。

二、RGB2YUV实验

1、实验原理

在数字电视技术中,rgb信号转化为yuv数字信号传输应当需要以下步骤:

  1. 色彩空间的变换: Y → R − Y , B − Y Y\rightarrow R-Y,B-Y YRY,BY
  2. 归一化(控制动态范围): R − Y , B − Y → C r , C b R-Y,B-Y\rightarrow Cr,Cb RY,BYCr,Cb
  3. 量化:将 ( − 0.5 , 0.5 ) (-0.5,0.5) (0.5,0.5) C r , C b Cr,Cb Cr,Cb信号8比特/10比特量化。
    (为了防止传输的信号变动,需要设置几级作为保护带)

然而yuv文件并不像数字电视信号一样需要考虑传输时的电平保护,所以文件转化时并不需要设置保护带。
只需得到色彩空间的转化公式:
Y = 0.2990 R + 0.5870 G + 0.1140 B Y=0.2990R+0.5870G+0.1140B Y0.2990R+0.5870G+0.1140B
U = − 0.1684 R − 0.3316 G + 0.5 B + 128 U=-0.1684R-0.3316G+0.5B+128 U0.1684R0.3316G+0.5B+128
V = 0.5 R − 0.4187 G − 0.0813 B + 128 V=0.5R-0.4187G-0.0813B+128 V0.5R0.4187G0.0813B+128

这里的 U 、 V U、V UV其实是《电视原理》课程中的 C b 、 C r Cb、Cr CbCr,电视原理中的 U , V U,V UV特指模拟电视,与数字的压缩系数不同。

2、代码调试:

解决错误

调试时注意到代码在读取文件时是使用的老写法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;
}

3、实验结果

跑通代码,使用YUVviewerPlus显示输出结果如下:
在这里插入图片描述

三、YUV2RGB实验

1、实验原理

根据RGB转YUV的转化公式,可以得到转化中的系数矩阵 A A A,通过矩阵运算可以计算YUV到RGB的变化矩阵为 A − 1 A^{-1} A1。如下:
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} ARGB=YU128V128左乘 A − 1 A^{-1} A1
[ 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=A1YU128V128
其中
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.2990.16840.50.5870.33160.41870.1140.50.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} A1=1.0752691.0752691.0752690.0000400.3440811.7717921.5075140.6083590.104267
对结果保留四位小数,得到YUV转RGB的公式:
R = 1.0753 ∗ Y + 1.5075 ∗ ( V − 128 ) ; R = 1.0753 * Y + 1.5075 * (V - 128); R=1.0753Y+1.5075(V128);
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.0753Y0.3441(U128)0.6084(V128);
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.0753Y+1.7718(U128)+0.1043(V128);

2、实验过程

主函数

主函数用来读写文件、调用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;
}

YUV2RGB函数

实现具体的转化功能,包括:

  • 初始化查找表
  • 扩展 U U U V V V分量
  • 转化为RGB分量并按照rgb文件的格式排列
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;
}
初始化查找表:InitLookupTable()

由于公式中的值是 ( U − 128 ) 、 ( V − 128 ) (U-128)、(V-128) (U128)(V128)所以查找表的初始化也有部分需要减去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);
}
扩展UV分量:extendUV()函数

与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;
}
单个像素转RGB:getR()、getG()、getB()

调用了查找表,返回单个像素转化后的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);
}

3、实验结果

将实验一中转化后的yuv文件作为输入,输出rgb文件,用作业一中python写的rgb查看器显示图片,结果如下:
在这里插入图片描述

4、误差分析

分布统计

实验结果肉眼难以分辨差别,还是用作业一的py跑了以下rgb值的概率分布:
在这里插入图片描述
发现转换后的RGB文件分布曲线的抖动更大了,变化不那么平滑了。

误差来源
  1. YUV420的downsample: YUV420为了便于传输,U、V分量只有原本的1/4。对于原本色度变化缓慢的图像区域,这种下采样造成的影响不大;但对于色度急剧变化的区域,下采样就导致色度信息被丢失了。因此在概率分布图中,R、G、B三原色的级数变化都没有原来那么平缓了。
  2. 运算过程的舍入误差: 按公式计算过程中,只保留了四位小数,最后也是向下取整,一定程度造成舍入误差。

四、实验总结

  • 查找表是很重要的: 一开始并不理解查找表的意义,后来发现如果需要处理大量文件,如视频文件,这种以空间换时间的方法是很重要的。
  • 冗余和压缩: 即使损失了近一半的大小,3/4的色度信息,YUV文件仍然在肉眼上观感并不差。这说明人眼的确是有灵敏度限制的,在视频压缩和处理中,应当运用好这种视觉冗余。
  • 程序的工程化: 从老师提供的代码工程中收获到了很多。
    1. 将一些需要输入的信息作为文件的参数,避免修改代码本身,编译后也能直接使用。
    2. 对各种错误情况考虑周全,并有对应的措施。
    3. 分模块写代码,可读性大大提高。
  • 1
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
YUV422转换为RAW有多种方法,其中一种常用的方法是将YUV422数据解压缩为RGB数据,再将RGB数据重新编码为RAW格式。下面是一个示例代码实现: ```c++ #include <iostream> #include <fstream> using namespace std; void YUV422toRGB(unsigned char y, unsigned char u, unsigned char v, unsigned char &r, unsigned char &g, unsigned char &b) { int r1, g1, b1; int c = y - 16, d = u - 128, e = v - 128; r1 = (298 * c + 409 * e + 128) >> 8; g1 = (298 * c - 100 * d - 208 * e + 128) >> 8; b1 = (298 * c + 516 * d + 128) >> 8; r = (r1 < 0) ? 0 : ((r1 > 255) ? 255 : r1); g = (g1 < 0) ? 0 : ((g1 > 255) ? 255 : g1); b = (b1 < 0) ? 0 : ((b1 > 255) ? 255 : b1); } int main() { ifstream input("input.yuv", ios::binary); ofstream output("output.raw", ios::binary); unsigned char y, u, v, r, g, b; int width = 640, height = 480; int y_size = width * height; int uv_size = y_size / 2; while (input.read(reinterpret_cast<char*>(&y), 1)) { input.read(reinterpret_cast<char*>(&u), 1); input.read(reinterpret_cast<char*>(&v), 1); YUV422toRGB(y, u, v, r, g, b); output.write(reinterpret_cast<char*>(&r), 1); output.write(reinterpret_cast<char*>(&g), 1); output.write(reinterpret_cast<char*>(&b), 1); } input.close(); output.close(); return 0; } ``` 该代码将从名为“input.yuv”的二进制文件中读取YUV422数据,并将每个像素的YUV转换RGB值。 然后,将RGB数据写入名为“output.raw”的二进制文件中。由于RAW格式可以是任何未压缩的像素格式,因此可以根据需要更改输出文件的扩展名和格式。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值