实验目的
- 编写程序将RGB图片文件转换为YUV色彩空间。其中,RGB文件各像素以BGR顺序按行依次排列存储,YUV文件按全部Y数据、全部U数据、全部V数据存储,均为8bit量化。
- 编写程序将图片从YUV色彩空间转换回RGB色彩空间,并于原RGB文件进行比较,分析误差。
实验原理
- RGB与YUV空间的相互转换:
由电视原理可知,亮度和色差信号的构成如下:
Y=0.2990R+0.5870G+0.1140B
R-Y=0.7010R-0.5870G-0.1140B
B-Y=-0.2990R-0.5870G+0.8860B
为了使色差信号的动态范围控制在0.5之间,需要进行归一化,对色差信号引入压缩
系数。归一化后的色差信号为:
U=-0.1684R-0.3316G+0.5B
V=0.5R-0.4187G-0.0813B - 码电平分配:
在对亮度分量信号进行8比特均匀量化时,共分为256个等间隔的量化级。为了防止信号
变动造成过载,在256级上端留20级,下端留16级作为信号超越动态范围的保护带。
色差信号经过归一化处理后,动态范围为-0.5-0.5,让色差零电平对应码电平128,
色差信号总共占225个量化级。在256级上端留15级,下端留16级作为信号超越动态范围
的保护带。 - 色度格式:
在数字电视中,对亮度信号和两个色差信号的分量信号有以 下几种取样格式:
• 4:4:4:表示RGB取样,RGB信号的取样点数量一样。
• 4:2:2:色差信号U和V在水平方向取样点数为Y的二分之一,而垂直方向取样点数与Y相同。
• 4:2:0:色差信号U和V在水平和垂直方向的取样点数均为Y的二分之一。
• 4:1:1:色差信号Cr和Cb在水平方向取样点数为Y的四分之一,而 垂直方向取样点数与Y相同。
本次实验中采用4:2:0色度取样格式。
实验素材
本次实验所用RGB文件的图片如下所示:
IDE使用visual studio 2019,使用easyx库显示图片。
实验结果
编写的程序运行结果如下所示:
-
原始图片
-
转换至YUV空间后使用YUVViewer查看
-
从YUV空间转换回RGB空间
-
经过转换的文件与源文件的误差(像素误差值做平方处理)
-
统计并导出各像素值的误差情况,用Excel绘图统计
经图表可知,转换造成的误差值多分布在15以内。
误差分析
造成色彩空间转换的误差来源有:
- 计算误差,由上文转换公式可知,直接计算后的结果是一个浮点型数据,但实际存储和表示图片像素都是使用的8bit unsigned char型数据,舍弃小数点后小数造成误差。
- 保护边带造成的误差,在实际计算中YUV数据都会进入保护边带内,但根据转换要求需要将进入边带的数值更改为边界值,因此造成了转换误差。
- 色度采样误差,由于采用4:2:0色度采样格式,每四个像素共用一个色度采样值,因此导致了一部分色度信息的丢失,转换回RGB色彩空间时导致误差。
程序代码
其中,须向main函数传入参数 down.rgb 256 256
rgb_raw.h
#ifndef RGB_img
#define RGB_img
#pragma once
typedef unsigned char uint1;
typedef unsigned short uint2;
typedef unsigned int uint4;
typedef int int4;
#pragma pack(push)
#pragma pack(2)
typedef struct {
uint1 b;
uint1 g;
uint1 r;
}BGR;
class RGB_raw_image
{
protected:
int4 width;
int4 height;
int4 size;
BGR* data;
#pragma pack(pop)
public:
explicit RGB_raw_image(uint4 w, uint4 h);
explicit RGB_raw_image(uint4 w, uint4 h, const char* path);
RGB_raw_image(const RGB_raw_image& input);
virtual ~RGB_raw_image();
bool open(uint4 w, uint4 h, const char* path);
bool save(const char* filePath);
bool clear();
uint4 get_size();
uint4 get_width();
uint4 get_height();
BGR* get_pixel(uint4 x, uint4 y);
BGR* get_data();
void show();
uint1 clamp_RGB(int t);
RGB_raw_image operator -(const RGB_raw_image& input);
};
#endif
rgb_raw.cpp
#include "RGB_raw.h"
#include <iostream>
#include <fstream>
#include <conio.h>
#include <easyx.h> //using easyx to draw picture
using namespace std;
uint1 RGB_raw_image::clamp_RGB(int t)
{
if (t < 0) {
return 0;
}
else if (t > 255) {
return 255;
}
else {
return t;
}
}
RGB_raw_image::RGB_raw_image(uint4 w, uint4 h)
{
width = w;
height = h;
size = width * height * 3;
data = new BGR[width * height];
}
RGB_raw_image::RGB_raw_image(uint4 w, uint4 h, const char* path)
{
try {
data = NULL;
if (!open(w, h, path)) {
size = 0;
throw("Fail to load file");
}
}
catch (const char* msg) {
cerr << msg << " " << path << endl;
}
}
RGB_raw_image::RGB_raw_image(const RGB_raw_image& input)
{
this->width = input.width;
this->height = input.height;
this->size = input.size;
this->data = new BGR[this->width * this->height];
//copy the original data to destination object
memcpy(this->data, input.data, this->size);
}
RGB_raw_image::~RGB_raw_image()
{
delete[] data;
}
bool RGB_raw_image::open(uint4 w, uint4 h, const char* path)
{
width = w;
height = h;
ifstream file_in(path, ios::binary);
if (file_in.is_open())
{
istream::pos_type start_pos = file_in.tellg();
file_in.seekg(0, ios_base::end);
istream::pos_type end_pos = file_in.tellg();
file_in.seekg(start_pos);
istream::pos_type file_size = end_pos - start_pos;
size = file_size;
if (size != width * height * 3) {
cerr << __func__ << " The file's size is wrong!" << endl;
return false;
}
else {
data = new BGR[width * height];
file_in.read((char*)data, size);
return true;
}
file_in.close();
}
else {
return false;
}
}
bool RGB_raw_image::save(const char* filePath)
{
std::ofstream file_out(filePath, ios::binary); //ios::binary means open a binary file.
if (file_out.is_open()) {
file_out.write((char*)data, size);
file_out.close();
return true;
}
else {
cout << __func__ << "Failed to open output file" << filePath << endl;
return false;
}
}
bool RGB_raw_image::clear()
{
delete[] data;
width = 0;
height = 0;
size = 0;
return true;
}
uint4 RGB_raw_image::get_size()
{
return size;
}
uint4 RGB_raw_image::get_width()
{
return width;
}
uint4 RGB_raw_image::get_height()
{
return height;
}
BGR* RGB_raw_image::get_pixel(uint4 x, uint4 y)
{
if (x < width && y < height) {
uint4 index = y * width + x;
return data + index;
}
else {
cerr << __func__ << "Position out of size!" << endl;
return NULL;
}
}
BGR* RGB_raw_image::get_data()
{
return data;
}
void RGB_raw_image::show()
{
initgraph(width, height, SHOWCONSOLE);
cout << "Loading..." << endl;
BGR* bgr;
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
bgr = this->get_pixel(j, i);
putpixel(j, i, RGB(bgr->r, bgr->g, bgr->b));
}
}
cout << "Finish" << endl;
}
RGB_raw_image RGB_raw_image::operator-(