数据压缩_实验一_彩色空间转换 RGB2YUV / YUV2RGB

彩色空间转换实验

一、实验原理

(1)彩色空间转换公式

  • YUV与RGB空间相互转换

由电视原理可知,亮度和色差信号的构成如下:
Y = 0.2990 R + 0.5870 G + 0.1140 B R − Y = 0.7010 R − 0.5870 G − 0.1140 B B − Y = − 0.2990 R − 0.5870 G + 0.8660 B \begin{array}{c}Y=0.2990R+0.5870G+0.1140B\\ R-Y=0.7010R-0.5870G-0.1140B\\ B-Y=-0.2990R-0.5870G+0.8660B\end{array} Y=0.2990R+0.5870G+0.1140BRY=0.7010R0.5870G0.1140BBY=0.2990R0.5870G+0.8660B
为了使色差信号的动态范围控制在0.5之间,需要进行归一化,对色差信号引入压缩系数,归一化后的色差信号为:
U = − 0.1684 R − 0.3316 G + 0.5 B V = 0.5 R − 0.4187 G − 0.0813 B \begin{array}{c}U=-0.1684R-0.3316G+0.5B\\ V=0.5R-0.4187G-0.0813B\end{array} U=0.1684R0.3316G+0.5BV=0.5R0.4187G0.0813B

  • 亮电平信号量化后的码电平分配

对分量信号进行8比特均匀量化处理后,动态范围为-0.5~0.5,让色差信号对应码电平128,色差信号总攻占225个量化级。在256级上端留15级,下端留16级作为信号超越动态范围的保护带。

(2)色度格式

YUV图像主流采样格式有以下三种:

  • YUV 4:4:4采样

  • YUV 4:2:2采样

  • YUV 4:2:0采样

    ​ YUV 4:2:0 采样,并不是指只采样 U 分量而不采样 V 分量。而是指,在每一行扫描时,只扫描一种色度分量(U 或者 V),和 Y 分量按照 2 : 1 的方式采样。比如,第一行扫描时,YU 按照 2 : 1 的方式采样,那么第二行扫描时,YV 分量按照 2:1 的方式采样。对于每个色度分量来说,它的水平方向和竖直方向的采样和 Y 分量相比都是 2:1 。如下图所示:

二、实验关键步骤

(1)利用main函数中参数argv[]传递图像分辨率及文件名

argc 是 argument count的缩写,表示传入main函数的参数个数;

argv 是 argument vector的缩写,表示传入main函数的参数序列或指针,并且第一个参数argv[0]一定是程序的名称,并且包含了程序所在的完整路径。

在VS2019项目属性窗口添加传入参数,如图:

相关代码如下:
int main(int argc,char *argv[]){
	int height, width;
	height = atoi(argv[1]);
	width = atoi(argv[2]);
 ... 
}

(2)初始化查找表

YUV2RGB、RGB2YUV均涉及大量浮点数计算,利用查找表可大大减少计算量。查找表初始化在程序开头完成。

初始化函数 Init_table( ) 如下:

void Init_table() {
	for (int i = 0; i < 256; i++) {
		RGB2YUV02990[i] = (float)0.2990 * i;
		RGB2YUV05870[i] = (float)0.5870 * i;
		RGB2YUV01140[i] = (float)0.1140 * i;
		RGB2YUV01684[i] = (float)0.1684 * i;
		RGB2YUV03316[i] = (float)0.3316 * i;
		RGB2YUV05000[i] = (float)0.5000 * i;
		RGB2YUV04187[i] = (float)0.4187 * i;
		RGB2YUV00813[i] = (float)0.0813 * i;

		YUV2RGB14075[i] = (float)1.4075 * (i - 128);
		YUV2RGB03455[i] = (float)0.3455 * (i - 128);
		YUV2RGB07169[i] = (float)0.7169 * (i - 128);
		YUV2RGB1779[i] = (float)1.779 * (i - 128);
	}
}

(3)申请动态数组

  • 定义指向动态数组的指针

  • 用 malloc( ) 函数申请动态数组

    注意程序末尾要 free( ) !!!

相关代码如下:

	// 申请动态数组
	unsigned char* Red = NULL, * Green = NULL, * Blue = NULL;
	unsigned char* Y = NULL, * U = NULL, * V = NULL;
	unsigned char* U_yuv = NULL, * V_yuv = NULL;

	Red = (unsigned char*)malloc(height * width);
	Green = (unsigned char*)malloc(height * width);
	Blue = (unsigned char*)malloc(height * width);
	Y = (unsigned char*)malloc(height * width);
	U = (unsigned char*)malloc(height * width);
	V = (unsigned char*)malloc(height * width);

	U_yuv = (unsigned char*)malloc(height * width / 4);
	V_yuv = (unsigned char*)malloc(height * width / 4);

(5)UV分量上采样

由于读入图像为4:2:0格式,在进行向RGB转换前应先对UV分量上采样,保证每个像素点都有一一对应的YUV分量。具体方法是将相邻四点UV值用同一个UV值表示。

相关代码如下:

// 对uv进行上采样
void UpSample_YUV(int Height, int Width, unsigned char* U, unsigned char* V, unsigned char* U_yuv, unsigned char* V_yuv) {
	int k = 0;
	for (int i = 0; i < Width*Height/4; i++) {
		*(U + k) = *(U_yuv + i);
		*(U + k + 1) = *(U_yuv + i);
		*(U + k + Width) = *(U_yuv + i);
		*(U + k + Width + 1) = *(U_yuv + i);

		*(V + k) = *(V_yuv + i);
		*(V + k + 1) = *(V_yuv + i);
		*(V + k + Width) = *(V_yuv + i);
		*(V + k + Width + 1) = *(V_yuv + i);

		if ((i+1) % (Width / 2) == 0) {
			k = k + 2 + Width;
		}
		else {
			k = k + 2;
		}
	}
}

(6)YUV2RGB

利用上文公式进行转换,注意考虑数值溢出问题!

相关代码如下:

// 计算RGB
void Caculate_RGB(int height, int width, unsigned char* Red, unsigned char* Green, unsigned char* Blue, unsigned char* Y, unsigned char* U, unsigned char* V) {
	float temp;
	for (int i = 0; i < height*width; i++) {
		// 计算Red
		temp = *(Y + i) + YUV2RGB14075[*(V + i)];
		temp = temp > 255 ? 255 : temp;
		temp = temp < 0 ? 0 : temp;
		*(Red + i) = (unsigned char)temp;
		// 计算Green
		temp = *(Y + i) - YUV2RGB03455[*(U + i)] - YUV2RGB07169[*(V + i)];
		temp = temp > 255 ? 255 : temp;
		temp = temp < 0 ? 0 : temp;
		*(Green + i) = (unsigned char)temp;
		// 计算Blue
		temp = *(Y + i) + YUV2RGB1779[*(U + i)];
		temp = temp > 255 ? 255 : temp;
		temp = temp < 0 ? 0 : temp;
		*(Blue + i) = (unsigned char)temp;
	}
}

(7)RGB2YUV

在256级上端留15级,下端留16级作为信号超越动态范围的保护带。

利用上文公式进行转换,注意考虑保护带问题!

相关代码如下:

// 计算YUV
void Caculate_YUV(int height, int width, unsigned char* Red, unsigned char* Green, unsigned char* Blue, unsigned char* Y, unsigned char* U, unsigned char* V) {
	float temp;
	for (int i = 0; i < height * width; i++) {
		// 处理Y分量
		temp = RGB2YUV02990[*(Red + i)] + RGB2YUV05870[*(Green + i)] + RGB2YUV01140[*(Blue + i)];
		temp = temp > 236 ? 236 : temp;
		temp = temp < 16 ? 16 : temp;
		*(Y + i) = (unsigned char)temp;
		// 处理U分量
		temp = -RGB2YUV01684[*(Red + i)] - RGB2YUV03316[*(Green + i)] + RGB2YUV05000[*(Blue + i)] + 128;
		temp = temp > 236 ? 236 : temp;
		temp = temp < 16 ? 16 : temp;
		*(U + i) = (unsigned char)temp;
		// 处理V分量
		temp = RGB2YUV05000[*(Red + i)] - RGB2YUV04187[*(Green + i)] - RGB2YUV00813[*(Blue + i)] + 128;
		temp = temp > 236 ? 236 : temp;
		temp = temp < 16 ? 16 : temp;
		*(V + i) = (unsigned char)temp;
	}
}

(8)UV分量下采样

需要低通滤波。一种最简便的低通滤波器是把4:4:4格式的4个像素UV值进行平均得到4:2:0的像素的UV值。

相关代码如下:

// 对UV进行下采样
void DownSample_YUV(int Height, int Width, unsigned char *U,unsigned char *V,unsigned char * U_yuv, unsigned char* V_yuv) {
	// 处理UV分量
	int k = 0;
	float average_u, average_v;
	for (int i = 0; i < Height * Width / 4; i++) {
		average_u = (float)(*(U + k) + *(U + k + 1) + *(U + k + Width) + *(U + k + 1 + Width)) / 4.0;
		average_v = (float)(*(V + k) + *(V + k + 1) + *(V + k + Width) + *(V + k + 1 + Width)) / 4.0;
		*(U_yuv + i) = (unsigned char)average_u;
		*(V_yuv + i) = (unsigned char)average_v;
    
		if ((i+1) % (Width / 2) == 0) {
			k = k + 2 + Width;
		}
		else {
			k = k + 2;
		}
	}
}

(9)关闭文件 回收空间

相关代码如下:

	free(Red);
	free(Green);
	free(Blue);
	free(Y);
	free(U);
	free(V);
	free(U_yuv);
	free(V_yuv);
	
	RGB_File.close();
	YUV_File.close();
	

三、实验结果验证

原始图像:

转换图像前后对比:

四、实验结论

  1. 4:4:4格式UV分量下采样变为4:2:0后,图像质量差别不大,但图像大小明显减小。
  2. RGB格式可与YUV格式图像相互转换,图像样式、质量不变。

五、错误总结

  • 问题一

    与原图存在细微色差,经检查,转换公式系数出错。

  • 问题二

出现神秘波纹???经检查,UV分量下采样过程中,指针操作出错。

  • 问题三

    释放动态数组时出错,提示“wrote to memory after end of heap buffer”。经检查,UV分量上采样过程中指针操作出错。

附完整程序

  • RGB2YUV.h
#pragma once
#ifndef RGB2YUV_H_
#define RGB2YUV_H_

#include <iostream>
#include <fstream>
#include <stdlib.h>
#include <malloc.h>
using namespace std;

// 声明全局变量
extern float RGB2YUV02990[256], RGB2YUV05870[256], RGB2YUV01140[256], RGB2YUV01684[256],
RGB2YUV03316[256], RGB2YUV05000[256], RGB2YUV04187[256], RGB2YUV00813[256];

extern float YUV2RGB14075[256], YUV2RGB03455[256], YUV2RGB07169[256], YUV2RGB1779[256];

void Init_table();

void Caculate_RGB(int height,int width,unsigned char*Red, unsigned char* Green, unsigned char* Blue,unsigned char *Y,unsigned char* U, unsigned char* V);
void Caculate_YUV(int height, int width, unsigned char* Red, unsigned char* Green, unsigned char* Blue, unsigned char* Y, unsigned char* U, unsigned char* V);

void ReadRGB(ifstream& File_in, int Height, int Width,unsigned char* Red, unsigned char* Green, unsigned char* Blue);
void ReadYUV(ifstream& File_in, int Height, int Width, unsigned char* Y, unsigned char* U, unsigned char* V);

void WriteYUV(int Height, int Width, unsigned char* Y, unsigned char* U, unsigned char* V);
void WriteRGB(int Height, int Width, unsigned char* Red, unsigned char* Green, unsigned char* Blue);

void DownSample_YUV(int Height, int Width, unsigned char* U, unsigned char* V, unsigned char* U_yuv, unsigned char* V_yuv);
void UpSample_YUV(int Height, int Width, unsigned char* U, unsigned char* V, unsigned char* U_yuv, unsigned char* V_yuv);
#endif // !RGB2YUV_H_

  • Lab_1_RGB2YUV.cpp
// Lab_1_RGB2YUV.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。

#include "RGB2YUV.h"

// 初始化索引表
float RGB2YUV02990[256], RGB2YUV05870[256], RGB2YUV01140[256], RGB2YUV01684[256],
RGB2YUV03316[256], RGB2YUV05000[256], RGB2YUV04187[256], RGB2YUV00813[256];

float YUV2RGB14075[256],YUV2RGB03455[256],YUV2RGB07169[256],YUV2RGB1779[256];

int main(int argc,char *argv[]){
	int height, width;
	height = atoi(argv[1]);
	width = atoi(argv[2]);

	Init_table();

	// 申请动态数组
	unsigned char* Red = NULL, * Green = NULL, * Blue = NULL;
	unsigned char* Y = NULL, * U = NULL, * V = NULL;
	unsigned char* U_yuv = NULL, * V_yuv = NULL;

	Red = (unsigned char*)malloc(height * width);
	Green = (unsigned char*)malloc(height * width);
	Blue = (unsigned char*)malloc(height * width);
	Y = (unsigned char*)malloc(height * width);
	U = (unsigned char*)malloc(height * width);
	V = (unsigned char*)malloc(height * width);

	U_yuv = (unsigned char*)malloc(height * width / 4);
	V_yuv = (unsigned char*)malloc(height * width / 4);
	
	
	// 读入yuv文件
	ifstream YUV_File;
	YUV_File.open(argv[3], ios::in | ios::binary);
	if (!YUV_File) {
		cout << "Open Failed." << endl;
		return 0;
	}
	ReadYUV(YUV_File, height, width, Y, U_yuv, V_yuv);
	UpSample_YUV(height, width, U, V, U_yuv, V_yuv);
	Caculate_RGB(height, width, Red, Green, Blue, Y, U, V);
	WriteRGB(height, width, Red, Green, Blue);

	
	// 读入rgb文件
	ifstream RGB_File;
	RGB_File.open(argv[4],ios::in|ios::binary);
	if (!RGB_File) {	
		cout << "Open Failed." << endl;
		return 0;
	}

	ReadRGB(RGB_File, height, width, Red, Green, Blue);
	Caculate_YUV(height, width,Red,Green,Blue,Y,U,V);
	DownSample_YUV(height, width,U,V,U_yuv,V_yuv);
	WriteYUV(height, width,Y, U_yuv, V_yuv);
	
	free(Red);
	free(Green);
	free(Blue);
	free(Y);
	free(U);
	free(V);
	free(U_yuv);
	free(V_yuv);
	
	RGB_File.close();
	YUV_File.close();

	return 0;
}


  • func.cpp
#include "RGB2YUV.h"

void Init_table() {
	for (int i = 0; i < 256; i++) {
		RGB2YUV02990[i] = (float)0.2990 * i;
		RGB2YUV05870[i] = (float)0.5870 * i;
		RGB2YUV01140[i] = (float)0.1140 * i;
		RGB2YUV01684[i] = (float)0.1684 * i;
		RGB2YUV03316[i] = (float)0.3316 * i;
		RGB2YUV05000[i] = (float)0.5000 * i;
		RGB2YUV04187[i] = (float)0.4187 * i;
		RGB2YUV00813[i] = (float)0.0813 * i;

		YUV2RGB14075[i] = (float)1.4075 * (i - 128);
		YUV2RGB03455[i] = (float)0.3455 * (i - 128);
		YUV2RGB07169[i] = (float)0.7169 * (i - 128);
		YUV2RGB1779[i] = (float)1.779 * (i - 128);
	}
}
// 计算YUV
void Caculate_YUV(int height, int width, unsigned char* Red, unsigned char* Green, unsigned char* Blue, unsigned char* Y, unsigned char* U, unsigned char* V) {
	float temp;
	for (int i = 0; i < height * width; i++) {
		// 处理Y分量
		temp = RGB2YUV02990[*(Red + i)] + RGB2YUV05870[*(Green + i)] + RGB2YUV01140[*(Blue + i)];
		temp = temp > 236 ? 236 : temp;
		temp = temp < 16 ? 16 : temp;
		*(Y + i) = (unsigned char)temp;
		// 处理U分量
		temp = -RGB2YUV01684[*(Red + i)] - RGB2YUV03316[*(Green + i)] + RGB2YUV05000[*(Blue + i)] + 128;
		temp = temp > 236 ? 236 : temp;
		temp = temp < 16 ? 16 : temp;
		*(U + i) = (unsigned char)temp;
		// 处理V分量
		temp = RGB2YUV05000[*(Red + i)] - RGB2YUV04187[*(Green + i)] - RGB2YUV00813[*(Blue + i)] + 128;
		temp = temp > 236 ? 236 : temp;
		temp = temp < 16 ? 16 : temp;
		*(V + i) = (unsigned char)temp;
	}
}
// 计算RGB
void Caculate_RGB(int height, int width, unsigned char* Red, unsigned char* Green, unsigned char* Blue, unsigned char* Y, unsigned char* U, unsigned char* V) {
	float temp;
	for (int i = 0; i < height*width; i++) {
		// 计算Red
		temp = *(Y + i) + YUV2RGB14075[*(V + i)];
		temp = temp > 255 ? 255 : temp;
		temp = temp < 0 ? 0 : temp;
		*(Red + i) = (unsigned char)temp;
		// 计算Green
		temp = *(Y + i) - YUV2RGB03455[*(U + i)] - YUV2RGB07169[*(V + i)];
		temp = temp > 255 ? 255 : temp;
		temp = temp < 0 ? 0 : temp;
		*(Green + i) = (unsigned char)temp;
		// 计算Blue
		temp = *(Y + i) + YUV2RGB1779[*(U + i)];
		temp = temp > 255 ? 255 : temp;
		temp = temp < 0 ? 0 : temp;
		*(Blue + i) = (unsigned char)temp;
	}
}
// 读入RGB文件
void ReadRGB(ifstream& File_in, int Height, int Width, unsigned char* Red, unsigned char* Green, unsigned char* Blue) {
	for (int i = 0; i < Height*Width; i++) {
		File_in.read((char*)(Blue + i), sizeof(unsigned char));
		File_in.read((char*)(Green + i), sizeof(unsigned char));
		File_in.read((char*)(Red + i), sizeof(unsigned char));
	}
	File_in.close();
}
// 读入YUV文件422格式
void ReadYUV(ifstream& File_in, int Height, int Width, unsigned char* Y, unsigned char* U, unsigned char* V) {
	for (int i = 0; i < Width*Height; i++) {
		File_in.read((char*)(Y + i), sizeof(unsigned char));
	}
	for (int i = 0; i < Width * Height/4; i++) {
		File_in.read((char*)(U + i), sizeof(unsigned char));
	}
	for (int i = 0; i < Width * Height/4; i++) {
		File_in.read((char*)(V + i), sizeof(unsigned char));
	}
	File_in.close();
}
// 写入YUV文件422格式
void WriteYUV(int Height, int Width,unsigned char* Y, unsigned char* U, unsigned char* V) {
	ofstream File_out;
	File_out.open("NewImage.yuv", ios::binary,ios::trunc);
	if (!File_out) {
		cout << "Failed to open NewImage.yuv" << endl;
	}
	else {
		File_out.write((char*)Y, Height * Width);
		File_out.write((char*)U, Height * Width / 4);
		File_out.write((char*)V, Height * Width / 4);
		File_out.close();
	}
}
// 写入RGB文件
void WriteRGB(int Height, int Width, unsigned char* Red, unsigned char* Green, unsigned char* Blue) {
	ofstream File_out;
	File_out.open("NewImage.rgb", ios::binary, ios::trunc);
	if (!File_out) {
		cout << "Failed to open NewImage.yuv" << endl;
	}
	else {
		for (int i = 0; i < Width*Height; i++) {
			File_out.write((char*)(Blue + i), sizeof(unsigned char));
			File_out.write((char*)(Green + i), sizeof(unsigned char));
			File_out.write((char*)(Red + i), sizeof(unsigned char));
		}
		File_out.close();
	}
}

// 对UV进行下采样
void DownSample_YUV(int Height, int Width, unsigned char *U,unsigned char *V,unsigned char * U_yuv, unsigned char* V_yuv) {
	// 处理UV分量
	int k = 0;
	float average_u, average_v;
	for (int i = 0; i < Height * Width / 4; i++) {
		average_u = (float)(*(U + k) + *(U + k + 1) + *(U + k + Width) + *(U + k + 1 + Width)) / 4.0;
		average_v = (float)(*(V + k) + *(V + k + 1) + *(V + k + Width) + *(V + k + 1 + Width)) / 4.0;
		*(U_yuv + i) = (unsigned char)average_u;
		*(V_yuv + i) = (unsigned char)average_v;

		if ((i+1) % (Width / 2) == 0) {
			k = k + 2 + Width;
		}
		else {
			k = k + 2;
		}
	}
}
// 对uv进行上采样
void UpSample_YUV(int Height, int Width, unsigned char* U, unsigned char* V, unsigned char* U_yuv, unsigned char* V_yuv) {
	int k = 0;
	for (int i = 0; i < Width*Height/4; i++) {
		*(U + k) = *(U_yuv + i);
		*(U + k + 1) = *(U_yuv + i);
		*(U + k + Width) = *(U_yuv + i);
		*(U + k + Width + 1) = *(U_yuv + i);

		*(V + k) = *(V_yuv + i);
		*(V + k + 1) = *(V_yuv + i);
		*(V + k + Width) = *(V_yuv + i);
		*(V + k + Width + 1) = *(V_yuv + i);

		if ((i+1) % (Width / 2) == 0) {
			k = k + 2 + Width;
		}
		else {
			k = k + 2;
		}
	}
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值