文章目录
彩色空间转换实验
一、实验原理
(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.1140BR−Y=0.7010R−0.5870G−0.1140BB−Y=−0.2990R−0.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.1684R−0.3316G+0.5BV=0.5R−0.4187G−0.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项目属性窗口添加传入参数,如图:
![](https://i-blog.csdnimg.cn/blog_migrate/f3dfa773ba7c767496ed4ae52a1e335d.png)
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();
三、实验结果验证
原始图像:
转换图像前后对比:
四、实验结论
- 4:4:4格式UV分量下采样变为4:2:0后,图像质量差别不大,但图像大小明显减小。
- 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;
}
}
}