彩色空间转换实验
基本原理
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 Y=0.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=B−Y=−0.2990R−0.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=R−Y=0.7010R−0.5870G−0.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
U′=0.564(B−Y)=−0.1684R−0.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(V−Y)=0.5R−0.4187G−0.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)×2n−8
D U = I N T ( 224 U ′ + 128 ) × 2 n − 8 D_U=INT(224U'+128)\times{2^{n-8}} DU=INT(224U′+128)×2n−8
D V = I N T ( 224 V ′ + 128 ) × 2 n − 8 D_V=INT(224V'+128)\times{2^{n-8}} DV=INT(224V′+128)×2n−8
其中 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)×2n−8
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)×2n−8
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)×2n−8
以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×28−n−16
U ’ = D U × 2 8 − n − 128 224 U’=\frac{D_U\times{2^{8-n}}-128}{224} U’=224DU×28−n−128
V ′ = D V × 2 8 − n − 128 224 V'=\frac{D_V\times{2^{8-n}}-128}{224} V′=224DV×28−n−128
之后可以利用:
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.2568−0.14480.43920.5041−0.29130.36770.09790.43920.0714⎦⎤⎣⎡RGB⎦⎤+⎣⎡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.0001−0.39182.01713.9701−2.0168−0.02879⎦⎤⎣⎡DY−16DU−128DV−128⎦⎤
同样,计算完成后向下取整即可。
然而实际计算中却发现这种计算方法由于rgb转换成yuv时经过了量化,而且各项的系数较大,小数点级别的误差就会造成与真实值较大的偏离,误差很大,于是采用上面描述的方法,先转换成
Y
,
U
′
,
V
′
Y,U',V'
Y,U′,V′再利用
Y
,
U
′
,
V
′
Y,U',V'
Y,U′,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.0000−0.39172.01701.5960−0.8127−0.0014⎦⎤⎣⎡DY−16DU−128DV−128⎦⎤
数据类型的分析
YUV
取样结构
这组图图每一行即为一行像素点,以Y为基准,蓝色和红色表示 C b / U C_b/U Cb/U和 C r / V C_r/V Cr/V
取样结构 | 图示 |
---|---|
4:4:4 | ![]() |
4:2:2 | ![]() |
4:1:1 | ![]() |
4:2:0格式1 | ![]() |
4: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)
名称 | 占用空间 | 内容 |
---|---|---|
bfType | 2B | 标识,就是"BM" |
bfSize | 4B | 整个BMP文件的大小 |
bfReserbed1/2 | 4B | 保留字 |
bfOffBits | 4B | 偏移数,即位图文件头+位图信息+调色板的大小 |
PS:数据是按照字节倒着存储的,如在文件中为12 34 56 78,那么实际数据则是78 56 34 12。
位图信息头
名称 | 占用空间 | 内容 |
---|---|---|
biSize | 4B | 位图信息头的大小,为40 |
biWidth | 4B | 位图宽度,单位为像素 |
biHeight | 4B | 位图高度,单位是像素 |
biPlanes | 2B | u固定值为1 |
biBitCount | 2B | 每个像素的位数,1-黑白图,4为16色,8为256色,24为真彩色 |
biCompression | 4B | 压缩方式,0为不压缩 |
biSizeImage | 4B | 位图全部像素占用的字节数,BI_RGB时可以设置为0 |
biXPelsPerMeter | 4B | 水平分辨率(像素/米) |
biYpelsPerMeter | 4B | 垂直分辨率(像素/米) |
biClrUsed | 4B | 位图使用的颜色数,如果为0则颜色数为2的biBitCount次方 |
biClrlmportant | 4B | 重要的颜色数,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);
}
实验结果与总结
过程与结果
-
首先设定命令参数
为了简便,自己写了一个函数,只要输入一个文件以及分辨率即可,自动生成其他文件名;当然也可以手工指定。这里使用缺省的方法,只输入原始文件和它的分辨率。
-
之后先将原始文件转换成bmp,这里命名为downo.bmp,o表示原来的图像。
-
随后转换成4:2:0格式的yuv文件
转换完成后可以看到文件大小恰好是原始文件的一半,用YUV播放器验证:
文件可以正常打开。 -
将4:2:0格式的yuv文件转换成rgb格式
-
将转换成的rgb格式转换成bmp文件,这里叫downp.rgb,p表示经过处理。
-
对比两个文件,统计不同的像素数和像素值的方差
总结
转换公式
经过量化后的公式:
[
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.2568−0.14480.43920.5041−0.29130.36770.09790.43920.0714⎦⎤⎣⎡RGB⎦⎤+⎣⎡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.0000−0.39172.01701.5960−0.8127−0.0014⎦⎤⎣⎡DY−16DU−128DV−128⎦⎤
误差来源
可以看到误差还是挺多的,对于误差大致有如下来源:
- rgb转换成yuv时需要进行量化
- 浮点计算误差
- 4:2:2格式取样会y和u会各舍去1/4的像素点
- yuv转换成rgb时又会有浮点计算误差和量化误差
- 计算过程会导致部分数据溢出