彩色空间转换实验实验报告

彩色空间转换实验

基本原理

RGB与YUV色彩空间的基础知识

RGB
RGB计色系统

RGB计色系统的创立是为了准确地对颜色进行表述和计算。1931年CIE对三基色做了统一规定,如下表:

标准色光波长
红基色光700nm
绿基色光546.1nm
蓝基色光435.8nm

至于为什么选择它们,则是因为它们的获取方法简单,色度稳定准确,而且可以混配出来的颜色较多。

为了计算方便,还要规定基色的单位。

物理计色系统规定:各以1各单位的三种基色光混合后,恰能产出等能白光。经过实验,选取了如下基色单位:

  • 700nm,光通量1光瓦的红光作为1个红基色单位。
  • 546.1nm,光通量4.5907光瓦的绿光为1个绿基色单位。
  • 435.9nm,光通量0.0601光瓦的蓝光为1个蓝基色单位。

至此,任何一个色光均可以表示为:

F = R ( R ) + G ( G ) + B ( B ) F=R(R)+G(G)+B(B) F=R(R)+G(G)+B(B)

其中 ( R ) , ( G ) , ( B ) (R),(G),(B) (R),(G),(B)即为上面提到的基色单位。

XYZ计色系统

在XYZ计色系统中,三个基色单位为 ( X ) , ( Y ) , ( Z ) (X),(Y),(Z) (X),(Y),(Z),任何一个颜色均可以表示为:

F = X ( x ) + Y ( Y ) + Z ( Z ) F=X(x)+Y(Y)+Z(Z) F=X(x)+Y(Y)+Z(Z)

XYZ系统的建立是为了克服RGB计色系统的缺点,它有以下特点:

  • 用他们配出实际颜色时,XYZ均为正值。
  • 合成光的亮度仅由Y决定,并规定1(Y)的光通量为1光瓦。
  • X = Y = Z X=Y=Z X=Y=Z,且大于零时,代表等能白光即E白。

根据特点,列出方程可以计算得到:

( Y ) = 0.4185 ( R ) − 0.0192 ( G ) + 0.0009 ( B ) (Y) = 0.4185(R) - 0.0192(G) + 0.0009(B) (Y)=0.4185(R)0.0192(G)+0.0009(B)

(X),(Z)同理。通过这组方程联立可以得到系数X,Y,Z与R,G,B的关系。

显像三基色计色系统

以上两个计色系统均是用来进行理论分析的,实际上我们指通常所指的R,G,B并不是RGB计色系统中的三基色,而是取决于的荧光粉,称为电视三基色,分别用 ( R e ) , ( G e ) , ( G b ) (R_e),(G_e),(G_b) (Re),(Ge),(Gb)表示,并且NTSC制中有:

1 ( R e ) + 1 ( G e ) + 1 ( B e ) = 1 C 白 1(R_e)+1(G_e)+1(B_e)=1C白 1(Re)+1(Ge)+1(Be)=1C

通过计算可以得到对应的亮度公式:

Y = 0.2990 R + 0.5870 G + 0.1140 B Y=0.2990R+0.5870G+0.1140B Y=0.2990R+0.5870G+0.1140B

这就是我们最常用的亮度公式。

YUV

YUV中的Y其实是来自于XYZ标准记色系统中的Y。

YUV与RGB的转换
模拟信号

Y = 0.2990 R + 0.5870 G + 0.1140 B Y=0.2990R+0.5870G+0.1140B Y0.2990R+0.5870G+0.1140B

U = B − Y = − 0.2990 R − 0.5870 G + 0.8860 B U=B-Y=-0.2990R-0.5870G+0.8860B U=BY0.2990R0.5870G+0.8860B

V = R − Y = 0.7010 R − 0.5870 G − 0.1140 B V=R-Y=0.7010R-0.5870G-0.1140B V=RY0.7010R0.5870G0.1140B

数字信号

为了便于处理,模拟信号变为数字信号时需要进行归一化处理,使得色差信号的动态范围控制在-0.5-0.5之间。

归一化之后有:

U ′ = 0.564 ( B − Y ) = − 0.1684 R − 0.3316 G + 0.5 B U'=0.564(B-Y)=-0.1684R-0.3316G+0.5B U0.564(BY)=0.1684R0.3316G+0.5B
V ′ = 0.713 ( V − Y ) = 0.5 R − 0.4187 G − 0.0813 B V'=0.713(V-Y)=0.5R-0.4187G-0.0813B V=0.713(VY)=0.5R0.4187G0.0813B

亮度信号量化后的电平分配

在这里插入图片描述
如图所示,在8bit均匀量化中,亮度信号占220个量化级,峰值电平对应235,消隐电平对应16。为了防止信号变动造成过载,在256级上端留20级,下端留16级作为信号超越动态范围的保护带。 其中0与255为保护电平,不允许出现在视频数据流中。

色差信号量化后的电平分配

量化

以8bit为例,色差信号经过归一化处理后,动态范围为-0.5-0.5,让色差零电平对应码电平128,色差信号总共占225个量化级。在256级上端留15级,下端留16级作为信号超越动态范围的保护带。

表达式

基于上述分析,可以得到表达式:

D Y = I N T ( 219 Y + 16 ) × 2 n − 8 D_Y=INT(219Y+16)\times{2^{n-8}} DY=INT(219Y+16)×2n8

D U = I N T ( 224 U ′ + 128 ) × 2 n − 8 D_U=INT(224U'+128)\times{2^{n-8}} DU=INT(224U+128)×2n8

D V = I N T ( 224 V ′ + 128 ) × 2 n − 8 D_V=INT(224V'+128)\times{2^{n-8}} DV=INT(224V+128)×2n8

其中 D Y , D U , D V D_Y,D_U,D_V DY,DU,DV即为计算机(数字信号)中的数值, I N T ( ) INT() INT()为向下取整, n n n为量化比特数。

上述过程中默认r、g、b的值为0-1之间的,而计算机中的数据则是在0-255范围内,所以修需要将上述公式中r、g、b的从0-255映射到0-1。

即:

D Y = I N T ( 219 Y 225 + 16 ) × 2 n − 8 D_Y=INT(\frac{219Y}{225} + 16)\times{2^{n-8}} DY=INT(225219Y+16)×2n8

D U = I N T ( 224 U ′ 225 + 128 ) × 2 n − 8 D_U=INT(\frac{224U'}{225}+128)\times{2^{n-8}} DU=INT(225224U+128)×2n8

D V = I N T ( 224 V 225 ′ + 128 ) × 2 n − 8 D_V=INT(\frac{224V}{225}'+128)\times{2^{n-8}} DV=INT(225224V+128)×2n8

以8bit为例:

D Y = I N T ( 219 Y 225 ) + 16 D_Y=INT(\frac{219Y}{225})+16 DY=INT(225219Y)+16

D U = I N T ( 224 U ′ 225 ) + 128 D_U=INT(\frac{224U'}{225})+128 DU=INT(225224U)+128

D V = I N T ( 224 V 225 ) + 128 D_V=INT(\frac{224V}{225})+128 DV=INT(225224V)+128

那么很容易就可以得到逆公式:

Y = D Y × 2 8 − n − 16 219 Y=\frac{D_Y\times{2^{8-n}}-16}{219} Y=219DY×28n16

U ’ = D U × 2 8 − n − 128 224 U’=\frac{D_U\times{2^{8-n}}-128}{224} U=224DU×28n128

V ′ = D V × 2 8 − n − 128 224 V'=\frac{D_V\times{2^{8-n}}-128}{224} V=224DV×28n128

之后可以利用:

B = U ′ 0.564 + Y B=\frac{U'}{0.564}+Y B=0.564U+Y

R = V ′ 0.713 + Y R=\frac{V'}{0.713}+Y R=0.713V+Y

来进行还原。

最终表达式

以8bit量化为例最终的表达式如下:
[ D Y D U D V ] = [ 0.2568 0.5041 0.0979 − 0.1448 − 0.2913 0.4392 0.4392 0.3677 0.0714 ] [ R G B ] + [ 16 128 128 ] \left[\begin{matrix} D_Y\\ D_U\\ D_V \end{matrix} \right]=\left[\begin{matrix} 0.2568& 0.5041 &0.0979\\ -0.1448&-0.2913&0.4392 \\ 0.4392&0.3677&0.0714 \\ \end{matrix} \right]\left[\begin{matrix} R\\ G\\ B \end{matrix} \right]+ \left[\begin{matrix} 16\\ 128\\ 128 \end{matrix} \right] DYDUDV=0.25680.14480.43920.50410.29130.36770.09790.43920.0714RGB+16128128
计算所得的结果后向下取整即为数字化的Y、U、V。

直接利用逆来求反变换也很容易得出:
[ R G B ] = [ − 2.8958 0.0001 3.9701 3.2285 − 0.3918 − 2.0168 1.1866 2.0171 − 0.02879 ] [ D Y − 16 D U − 128 D V − 128 ] \left[\begin{matrix} R\\ G\\ B \end{matrix} \right]=\left[\begin{matrix} -2.8958& 0.0001 & 3.9701\\ 3.2285 & -0.3918 & -2.0168\\ 1.1866& 2.0171& -0.02879 \end{matrix} \right]\left[\begin{matrix} D_Y-16\\ D_U-128\\ {D_V-128} \end{matrix} \right] RGB=2.89583.22851.18660.00010.39182.01713.97012.01680.02879DY16DU128DV128
同样,计算完成后向下取整即可。

然而实际计算中却发现这种计算方法由于rgb转换成yuv时经过了量化,而且各项的系数较大,小数点级别的误差就会造成与真实值较大的偏离,误差很大,于是采用上面描述的方法,先转换成 Y , U ′ , V ′ Y,U',V' Y,U,V再利用 Y , U ′ , V ′ Y,U',V' YU,V R , G , B R,G,B R,G,B的关系联立方程求解,最后得到的公式如下:
[ R G B ] = [ 1.1644 0.0000 1.5960 1.1644 − 0.3917 − 0.8127 1.1644 2.0170 − 0.0014 ] [ D Y − 16 D U − 128 D V − 128 ] \left[\begin{matrix} R\\ G\\ B \end{matrix} \right]=\left[\begin{matrix} 1.1644 &0.0000 &1.5960\\ 1.1644 &-0.3917 &-0.8127\\ 1.1644 &2.0170& -0.0014 \end{matrix} \right]\left[\begin{matrix} D_Y-16\\ D_U-128\\ D_V-128 \end{matrix} \right] RGB=1.16441.16441.16440.00000.39172.01701.59600.81270.0014DY16DU128DV128

数据类型的分析

YUV

取样结构

这组图图每一行即为一行像素点,以Y为基准,蓝色和红色表示 C b / U C_b/U Cb/U C r / V C_r/V Cr/V

取样结构图示
4:4:44:4:4
4:2:24:2:2
4:1:14:1:1
4:2:0格式14:2:0-1
4:2:0格式24:2:0-2
存储结构

一下均指一帧的情况,实际上是一帧内容按照相应格式存储完成后,再存储下一帧。

YUVY(4:2:2)

Y 0 C b 0 Y 1 C r 0 Y 2 C b 1 Y 3 C r 2 ⋯ Y_0C_{b_0}Y_1C_{r_0}Y_2C_{b_1}Y_3C_{r_2}\cdots Y0Cb0Y1Cr0Y2Cb1Y3Cr2
Y Y Y后跟 C b C_b Cb Y Y Y后跟 C r C_r Cr交替出现。

UYVY(4:2:2)

C b 0 Y 0 C r 0 Y 1 C b 1 Y 2 C r 2 Y 3 ⋯ C_{b_0}Y_0C_{r_0}Y_1C_{b_1}Y_2C_{r_2}Y_3\cdots Cb0Y0Cr0Y1Cb1Y2Cr2Y3
C b C_b Cb后跟 Y Y Y C r C_r Cr后跟 Y Y Y交替出现。

YUV422P(4:2:2)

先存储完所有Y,再存储完所有 C b C_b Cb,最后再存储所有 C r C_r Cr

YV12,YU12(4:2:0)

先存储完所有Y,再存储完所有 C b C_b Cb,最后再存储所有 C r C_r Cr

NV12,NV21(4:2:0)

先存储完所有Y,之后 C b x C_{b_x} Cbx C b x + 1 C_{b_{x+1}} Cbx+1 C r x C_{r_x} Crx C r x + 1 C_{r_{x+1}} Crx+1的结构交替出现,即两字节 C b C_b Cb之后再存储两字节 C r C_r Cr

本次为YV12格式。

RGB/BMP

与BMP文件相比,本次所给的rgb文件只缺少了文件头和信息头。为了方便观察结果,本文先打算讲rgb文件添加文件头变成bmp文件。

位图文件头(BITMAPFILEHEADER)
名称占用空间内容
bfType2B标识,就是"BM"
bfSize4B整个BMP文件的大小
bfReserbed1/24B保留字
bfOffBits4B偏移数,即位图文件头+位图信息+调色板的大小

PS:数据是按照字节倒着存储的,如在文件中为12 34 56 78,那么实际数据则是78 56 34 12。

位图信息头
名称占用空间内容
biSize4B位图信息头的大小,为40
biWidth4B位图宽度,单位为像素
biHeight4B位图高度,单位是像素
biPlanes2Bu固定值为1
biBitCount2B每个像素的位数,1-黑白图,4为16色,8为256色,24为真彩色
biCompression4B压缩方式,0为不压缩
biSizeImage4B位图全部像素占用的字节数,BI_RGB时可以设置为0
biXPelsPerMeter4B水平分辨率(像素/米)
biYpelsPerMeter4B垂直分辨率(像素/米)
biClrUsed4B位图使用的颜色数,如果为0则颜色数为2的biBitCount次方
biClrlmportant4B重要的颜色数,0表示所有颜色都重要

为了简便,自己实现的时候并未采用这么冗长的名称写法。

颜色表

即本次所给的文件部分,按照B、G、R顺序排列,每个像素占用1个字节,用来表示颜色。

相关编程知识

文件

C语言是用FILE指针来进行文件操作。

FILE指针

FILE指针定义如下:

typedef struct 
{ 
    short level;  /*缓冲区满空程度*/ 
    unsigned flags;  /*文件状态标志*/ 
    char fd;     /*文件描述符*/ 
    unsigned char hold;   /*无缓冲则不读取字符*/ 
    short bsize;  /*缓冲区大小*/ 
    unsigned char *buffer; /*数据缓冲区*/ 
    unsigned char *curp;   /*当前位置指针*/ 
    unsigned istemp; /*临时文件指示器*/ 
    short token;  /*用于有效性检查*/ 
} FILE;
文件的打开与关闭

fopen

定义:

FILE *fopen(char *filename, char *mode);

  • filename为文件路径
  • mode为打开模式

打开模式

打开模式描述
r只读,打开已有文件,不能写
w只写,创建或打开,覆盖已有文件
a追加,创建或打开,在已有文件末尾追加
r+读写,打开已有文件
w+读写,创建或打开,覆盖已有文件
a+读写,创建或打开,在已有文件末尾追加
t按文本方式打开 (缺省)
b按二进制方式打开

fclose()

定义:

int fclose(FILE *fp);

若成功返回0,否则返回EOF(-1)

常用函数
  • 单个字符或者字
    • fputc,fgetc,putc,getc,putw,getw
  • 格式化输入输出
    • fprintf,fscanf
  • 二进制文件读写
    • fread,fwrite

其使用方法和c++中流的名称类似的函数一致。

因为C中文件读写较为繁琐,本次还是采用流式文件输入输出。

详见C++文件读写

问题的分析与实现

流程分析

转化为BMP
  • 由于所给rgb文件没有文件头和信息头,无法用照片浏览器打开,所以首先应该添加文件头。
  • 根据rgb文件实际属性向文件头填充数据。
  • 由于bmp文件rgb是从下向上存储,故还需要对rgb数据进行反向。
  • 将文件头和信息头与反向后的rgb数据合并
  • 输出数据
RGB转YUV
  • 根据文件保存的格式,从rgb文件中读取r,g,b的数值
  • 计算实际y,u,v分量的大小
  • 按照yuv文件的格式输出数据
YUV转RGB
  • 根据文件存储的格式从yuv文件中读取y,u,v文件的值
  • 计算实际r,g,b分量的大小
  • 按照rgb文件的格式输出数据

代码实现

header.h
  • 完成一些定义的头文件,包含
    • 函数定义
    • bmp文件信息头和文件头的定义
#pragma once

#include <iostream>
#include <fstream>
#include <cstdio>
using namespace std;
int char2int(char *s);
void rgb2bmp(unsigned char* inBuffer, int width, int height, string &outPath);
void rgb2yuv(unsigned char* inBuffer, int width, int height, string &outPath);
void yuv2rgb(unsigned char* inBuffer, int width, int height, string &outPath);
void Compare(string &pathA, string &pathB);
#pragma pack(1)
struct bmpHeader
{
	short type = 0;
	unsigned int size = 0;
	unsigned int reserved = 0;
	unsigned int offBits = 0;
};
struct bmpInfo
{
	unsigned int size = 40;
	unsigned int width = 0;
	unsigned int height = 0;
	short planes = 1;
	short count = 24;
	unsigned int compressed = 0;
	unsigned int imageSize = 0;
	unsigned int xPels = 0;
	unsigned int yPels = 0;
	unsigned int colorNum = 0;
	unsigned int importantColor = 0;
};
functions.cpp
  • 实现字符串转换成int
  • 实现两个rgb文件的比较
#include "header.h"

int char2int(char *s)
{
	int ans = 0, pos = 0;
	while (s[pos])
	{
		ans = ans * 10 + (s[pos++] - '0');
	}
	return ans;
}
void Compare(string& pathA, string& pathB)
{
	ifstream inA(pathA, ios::binary), inB(pathB, ios::binary);
	if (!inA || !inB) { cout << pathA << " or " << pathB << " open failed." << endl; exit(1); }
	inA.seekg(0,ios::end);
	int size = inA.tellg();
	inA.seekg(0, ios::beg);

	unsigned char* a = new unsigned char[size], * b = new unsigned char[size];
	inA.read((char *)a, size); inB.read((char* )b, size);
	int num = 0, variance = 0;
	for (int i = 0; i < size; ++i)
	{
		if (a[i] != b[i])
		{
			num++;
			variance += (a[i] - b[i]) * (a[i] - b[i]);
		}
	}
	cout << "variance: " << variance << ", the number of different pixels: " << num << endl;
	inA.close(); inB.close();
	for (auto i : { &a, &b })
		if (*i != nullptr)
			delete[] *i;
}

RGB2BMP.cpp
  • 将纯rgb数据转换为bmp图像的函数。
#include "header.h"
void rgb2bmp(unsigned char* buffer, int width, int height, string &outPath)
{
	ofstream out(outPath, ios::binary);
	if (!out) { cout << outPath << " open failed." << endl; exit(0); }
	bmpHeader header; bmpInfo info;
	unsigned int size;
	header.type = 0x4d42;
	size = header.size = width * height * 3;
	header.offBits = 54;
	info.width = width;
	info.height = height;
	out.write((char*)&header, sizeof(bmpHeader));
	out.write((char*)&info, sizeof(info));
	unsigned char* outBuffer = new unsigned char[size];
	for (int h = 0; h < height; ++h)
	{
		int W = width * 3;
		for (int w = 0; w < W; ++w)
		{
			outBuffer[(height - 1 - h) * W + w] = buffer[h * W + w];
		}
	}
	out.write((char*)outBuffer, size);
	delete[] outBuffer;
	out.close();
}
RGB2YUV.cpp
  • 将rgb数据转换为yuv数据的函数。
#include "header.h"

bool haveRGBTable = 0;
const int maxn = 256;
float rgb02568[maxn],rgb05401[maxn],rgb00979[maxn];
float rgb01448[maxn], rgb02913[maxn], rgb04392[maxn];
float rgb03677[maxn], rgb00714[maxn];
void getRGBTable()
{
	for (int i = 0; i < 256; i++)
	{
		rgb00714[i] = 0.0714 * i;
		rgb00979[i] = 0.0979 * i;
		rgb01448[i] = 0.1448 * i;
		rgb02568[i] = 0.2568 * i;
		rgb02913[i] = 0.2913 * i;
		rgb03677[i] = 0.3677 * i;
		rgb04392[i] = 0.4392 * i;
		rgb05401[i] = 0.5401 * i;
	}

}
void rgb2yuv(unsigned char* inBuffer, int width, int height, string &outPath)
{
	if (!haveRGBTable)
	{
		haveRGBTable = 1;
		getRGBTable();
	}
	ofstream out(outPath, ios::binary);
	if (!out.is_open()) { cout << outPath << " open failed." << endl; exit(0); }
	int size = width * height;
	unsigned char* Y, * U, * V;
	Y = new unsigned char[size];
	for (auto i : { &U, &V }) *i = new unsigned char[size/4];

	int pos = 0;
	for (int i = 0; i < size; ++i)
	{
		unsigned char r, g, b, j = 0;
		for (auto c : { &b, &g, &r }) *c = inBuffer[i * 3 + (j++)];

		Y[i] = int(rgb02568[r] + rgb05401[g] + rgb00979[b]) + 16;
		int h = i / width, w = i % width;
		if ((h & 1) || (w & 1)) continue;
		U[pos] = int(-rgb01448[r] - rgb02913[g] + rgb04392[b]) + 128;
		V[pos ++] = int(rgb04392[r] - rgb03677[g] - rgb00714[b]) + 128;
	}
	out.write((char*)Y, size);
	for( auto i : {&U, &V}) out.write((char*)(*i), size / 4);
	out.close();
	for (auto i : { &Y, &U, &V }) if ((*i) != nullptr) delete[] * i;
	return;
}
YUV2RGB.cpp
  • 实现将yuv数据转化为rgb数据的函数。
#include "header.h"
unsigned char organized(int n)
{
	if (n > 255) n = 255;
	if (n < 0) n = 0;
	return (unsigned char)n;
}
const int maxn = 256;
float yuv11644[maxn], yuv15960[maxn];
float yuv03917[maxn], yuv08127[maxn];
float yuv20170[maxn], yuv00014[maxn];
bool haveYUVTable = 0;
void getYUVTable()
{
	for (int i = 0; i < 256; ++i)
	{
		yuv00014[i] = 0.0014 * (i - 128);
		yuv03917[i] = 0.3917 * (i - 128);
		yuv08127[i] = 0.8127 * (i - 128);
		yuv11644[i] = 1.1644 * (i - 16);
		yuv15960[i] = 1.5960 * (i - 128);
		yuv20170[i] = 2.0170 * (i - 128);
	}
}
void yuv2rgb(unsigned char* inBuffer, int width, int height, string &outPath)
{
	ofstream out(outPath, ios::binary);
	if (!out.is_open()) { cout << outPath << " open failed." << endl; exit(1); }

	if (!haveYUVTable)
	{
		haveYUVTable = 1;
		getYUVTable();
	}
	unsigned char* Y, * U, * V;
	int size = width * height;
	Y = new unsigned char[size];
	for (auto i : { &U, &V }) *i = new unsigned char[size / 4];
	int pos = 0;
	for (int i = 0; i < size; ++i) Y[i] = inBuffer[pos++];
	for (int i = 0; i < size / 4; ++i) U[i] = inBuffer[pos++];
	for (int i = 0; i < size / 4; ++i) V[i] = inBuffer[pos++];
	
	for (int i = 0; i < size; ++i)
	{
		int y, u, v, r, g, b;
		int h = i / width, w = i % width, pos;
		if (w & 1) w--; w >>= 1;
		if (h & 1) h--; h >>= 1;
		pos = h * width / 2 + w;
		y = Y[i]; u = U[pos]; v = V[pos];
		r = yuv11644[y] + yuv15960[v];
		g = yuv11644[y] - yuv03917[u] - yuv08127[v];
		b = yuv11644[y] + yuv20170[u]- yuv00014[v];
		out << organized(b) << organized(g) << organized(r);
	}
	for (auto i : { &Y, &U, &V })
	{
		if (*i != nullptr) delete[] * i;
	}
	return;
}
main.cpp
  • 调用上述函数并做验证。
#include "header.h"
int main()
{
    string inRGB = (string)__argv[1];
    string outRGB = "", outYUV = "", outBmpOrigin = "", outBmpProssed = "";
    int height = char2int(__argv[2]), width = char2int(__argv[3]);
    if (__argc == 4)
    {
        string tmp;
        for (auto i : inRGB)
        {
            if (i == '.') break;
            tmp.push_back(i);
        }
        outRGB = tmp + "p.rgb";
        outYUV = tmp + ".yuv";
        outBmpOrigin = tmp + "o.bmp";
        outBmpProssed = tmp + "p.bmp";
    }
    else
    {
        int pos = 3;
        for (auto s : { &outRGB, &outYUV, &outBmpOrigin, &outBmpProssed })
            *s = (string)__argv[++pos];
    }

    int imageSize = height * width * 3;
    unsigned char* rgbBuffer = new unsigned char[imageSize];
    ifstream in(inRGB, ios::binary);
    if (!in)
    {
        cout << inRGB << " open failed." << endl;
        exit(1);
    }
    in.read((char*)rgbBuffer, imageSize);

    rgb2bmp(rgbBuffer, width, height, outBmpOrigin);
    rgb2yuv(rgbBuffer, width, height, outYUV);
    in.close();
    if (rgbBuffer != nullptr) delete[] rgbBuffer;

    in.open(outYUV, ios::binary);
    if (!in.is_open()) { cout << outYUV << " open failed." << endl; exit(1); }
    unsigned char* yuvBuffer = new unsigned char[imageSize];
    in.read((char*)yuvBuffer, imageSize);
    yuv2rgb(yuvBuffer, width, height, outRGB);
    in.close();
    if (yuvBuffer != nullptr) delete[] yuvBuffer;

    in.open(outRGB, ios::binary);
    if (!in.is_open()) { cout << outRGB << " open failed." << endl; exit(1); }
    rgbBuffer = new unsigned char[imageSize];
    in.read((char * )rgbBuffer, imageSize);
    rgb2bmp(rgbBuffer, width, height, outBmpProssed);
    in.close();
    if (rgbBuffer != nullptr) delete[] rgbBuffer;

    Compare(inRGB, outRGB);
}


实验结果与总结

过程与结果

  1. 首先设定命令参数

    为了简便,自己写了一个函数,只要输入一个文件以及分辨率即可,自动生成其他文件名;当然也可以手工指定。这里使用缺省的方法,只输入原始文件和它的分辨率。

    在这里插入图片描述

  2. 之后先将原始文件转换成bmp,这里命名为downo.bmp,o表示原来的图像。
    原始rgb

  3. 随后转换成4:2:0格式的yuv文件
    文件大小
    转换完成后可以看到文件大小恰好是原始文件的一半,用YUV播放器验证:
    yuv
    文件可以正常打开。

  4. 将4:2:0格式的yuv文件转换成rgb格式

  5. 将转换成的rgb格式转换成bmp文件,这里叫downp.rgb,p表示经过处理。
    对比

  6. 对比两个文件,统计不同的像素数和像素值的方差
    差别

总结

转换公式

经过量化后的公式:
[ D Y D U D V ] = [ 0.2568 0.5041 0.0979 − 0.1448 − 0.2913 0.4392 0.4392 0.3677 0.0714 ] [ R G B ] + [ 16 128 128 ] \left[\begin{matrix} D_Y\\ D_U\\ D_V \end{matrix} \right]=\left[\begin{matrix} 0.2568& 0.5041 &0.0979\\ -0.1448&-0.2913&0.4392 \\ 0.4392&0.3677&0.0714 \\ \end{matrix} \right]\left[\begin{matrix} R\\ G\\ B \end{matrix} \right]+ \left[\begin{matrix} 16\\ 128\\ 128 \end{matrix} \right] DYDUDV=0.25680.14480.43920.50410.29130.36770.09790.43920.0714RGB+16128128

[ R G B ] = [ 1.1644 0.0000 1.5960 1.1644 − 0.3917 − 0.8127 1.1644 2.0170 − 0.0014 ] [ D Y − 16 D U − 128 D V − 128 ] \left[\begin{matrix} R\\ G\\ B \end{matrix} \right]=\left[\begin{matrix} 1.1644 &0.0000 &1.5960\\ 1.1644 &-0.3917 &-0.8127\\ 1.1644 &2.0170& -0.0014 \end{matrix} \right]\left[\begin{matrix} D_Y-16\\ D_U-128\\ D_V-128 \end{matrix} \right] RGB=1.16441.16441.16440.00000.39172.01701.59600.81270.0014DY16DU128DV128

误差来源

可以看到误差还是挺多的,对于误差大致有如下来源:

  • rgb转换成yuv时需要进行量化
  • 浮点计算误差
  • 4:2:2格式取样会y和u会各舍去1/4的像素点
  • yuv转换成rgb时又会有浮点计算误差和量化误差
  • 计算过程会导致部分数据溢出
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值