实验目的
1.学会从计算和程序的角度分析问题 通过完成本实验,理解计算思维,即从问题出发,通过逐步分析和分解,把原问题转化 为可用程序方式解决的问题。在此过程中设计出一个解决方案。
2.进一步理解彩色空间的概念并掌握不同彩色空间转换的基本方程。
3.通过逐步运行程序,掌握编程细节:如查找表的设计,内存分配,对 U 和 V 信号进 行下采样,文件读写过程等。掌握程序调试的基本方法
实验内容
1.掌握彩色空间转换的基本思想及转换公式
(1)YUV与RGB空间的相互转换 由电视原理可知,亮度和色差信号的构成如下:
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
(2) 码电平分配及数字表达式
- 亮电平信号量化后码电平分配在对分量信号进行8比特均匀量化时,共分为256个等间隔的量化级。为了防止信号变动造成过载,在256级上端留20级,下端留16级作为信号超越动态范围的保护带。
即小于16的信号都等于16,大于235的信号都等于235。 - 色差信号量化后码电平分配色差信号经过归一化处理后,动态范围为-0.5~0.5,让色差零电平对应码电平128,色差信号总共占225个量化级。在256级上端留15级,下端留16级作为信号超越动态范围的保护带。
即将上述公式求得的U、V值+128,使其范围在0~255,再令小于16的信号都等于16,大于239的信号都等于240。
(3)色度格式 4:2:0格式是指色差信号U,V的取样频率为亮度信号取样频率的四分之一,在水平方向和垂直方向上的取样点数均为Y的一半。
即每四个(2x2)像素点共用一个U、V信号,转换时,U、V取这四个像素点对应U、V数值的平均值。
2.掌握由问题到程序的实现过程
(1)掌握所用的程序语言
只有熟悉所使用的程序语言,精通语言中各种结构(包括其形式和意义),才可能实
现将所要解决的问题转换为程序。。
(2)学会写程序
首先确定适用的程序结构,保证写出的程序结构良好清晰、易于阅读和理解。还应
注意当有些条件或要求改变时,程序是否容易修改去满足新的要求。 (3)检查程序错误的能力
程序错误通常包括语言错误、逻辑错误和算法错误。要熟悉所用工具和编程环境帮
助迅速找到错误并改正。
实现过程
rgb2yuv
1.转换公式
考虑:
- 度信号的压缩系数;
- 色差零电平对应码电平128,色差信号总共占225个量化级,即将U、V值+128;
- 色度格式4:2:0时U、V的取样, 即每四个(2x2)像素点共用一个U、V信号,转换时,U、V取这四个像素点对应U、V数值的平均值。
综上,可得转换公式如下:
Y=0.2990R+0.5870G+0.1140B ;
U[i]=-0.1684*(R[i]+R[i+1]+R[w+i]+R[w+i+1])/4-0.3316*(G[i]+G[i+1]+G[w+i]+G[w+i+1])/4+0.5*(B[i]+B[i+1]+B[w+i]+B[w+i+1])/4 +128;
V[i]=0.5*(R[i]+R[i+1]+R[w+i]+R[w+i+1])/4-0.4187*(G[i]+G[i+1]+G[w+i]+G[w+i+1])/4-0.0813*(B[i]+B[i+1]+B[w+i]+B[w+i+1])/4 +128;
(其中w为图像宽度)
rgb2yuv中U、V具体赋值语句如下:
int temp;
for (j = 0; j < h/2; j++)
{
for (i = 0; i < w/2; i ++)
{
temp= -0.1687 * ((R[2 * i + j * 2 *w] + R[2 * i + 1 + j * 2 * w] + R[2 * i + (j * 2 + 1) * w] + R[2 * i + 1 + (j * 2 + 1) * w]) / 4)
- 0.3313 * ((G[2 * i + j * 2 * w] + G[2 * i + 1 + j * 2 * w] + G[2 * i + (j * 2 + 1) * w] + G[2 * i + 1 + (j * 2 + 1) * w]) / 4)
+ 0.5 * ((B[2 * i + j * 2 * w] + B[2 * i + 1 + j * 2 * w] + B[2 * i + (j * 2 + 1) * w] + B[2 * i + 1 + (j * 2 + 1) * w]) / 4) + 128;
if (temp < 16)
temp = 16;
else if (temp > 240)
temp = 240;
U[j*w / 2 + i] = temp;
temp= 0.5 * ((R[2 * i + j * 2 * w] + R[2 * i + 1 + j * 2 * w] + R[2 * i + (j * 2 + 1) * w] + R[2 * i + 1 + (j * 2 + 1) * w]) / 4)
- 0.4187 * ((G[2 * i + j * 2 * w] + G[2 * i + 1 + j * 2 * w] + G[2 * i + (j * 2 + 1) * w] + G[2 * i + 1 + (j * 2 + 1) * w]) / 4)
- - 0.0813 * ((B[2 * i + j * 2 * w] + B[2 * i + 1 + j * 2 * w] + B[2 * i + (j * 2 + 1) * w] + B[2 * i + 1 + (j * 2 + 1) * w]) / 4) + 128;
if (temp < 16)
temp = 16;
else if (temp > 240)
temp = 240;
V[j*w / 2 + i] =temp;
}
}
注意:
- R、G、B大小为w * h,U、V大小为(w /2) * (h / 2),注意转换公式中的对应;
- 不能直接用U、V数组判断溢出,因为U、V是unsigned char型,数值范围在0~255之间,对<0 和>255的情况无法判断并修正,可能会出现失真。
2.输出:
YUV格式数据存储在这里采用Y、U、V的简单堆放,即先存储Y数据(数据量为wh),再存U(wh/4),再存V(w*h/4);
具体程序如下:
for (i = 0; i < w*h; i++)
{
fp_yuv.write((char*)(Y+i), sizeof(unsigned char));
}
for (i = 0; i < w*h / 4; i++)
{
fp_yuv.write((char*)(U+i), sizeof(unsigned char));
}
for (i = 0; i < w*h / 4; i++)
{
fp_yuv.write((char*)(V+i), sizeof(unsigned char));
}
yuv2rgb
- 转换公式:
R=Y+1.402*(V-128)
G=Y-0.3441*(U-128)-0.7139*(V-128)
B=Y+1.7718*(U-128)-0.0013*(V-128)
思路与rgb2yuv类似,值得注意的是:
rgb转换为yuv图像的过程中丢失了一部分数据(每四个像素点只得到一组U、V值),故再从此yuv还原出的rgb图像与原始图像相比,相邻四个(即一个2x2方块中)像素点U、V值相等。
实现程序如下:
int m, n;
int temp;
unsigned char B[h*w] = { 0 }, G[h*w] = { 0 }, R[h*w] = { 0 };
for (j = 0; j < h; j++)
{
n = j / 2;
for (i = 0; i < w; i++)
{
m = i / 2;
temp= Y[i + j * w] + 1.402*(V[m + n * w / 2] - 128);
if (temp < 0)
temp = 0;
else if (temp > 255)
temp = 255;
R[i + j * w] = temp;
temp= Y[i + j * w] - 0.3441*(U[m + n * w / 2] - 128) - 0.7139*(V[m + n * w / 2] - 128);
if (temp < 0)
temp = 0;
else if (temp > 255)
temp = 255;
G[i + j * w] = temp;
temp= Y[i + j * w] + 1.7718*(U[m + n * w / 2] - 128) - 0.0013*(V[m + n * w / 2] - 128);
if (temp < 0)
temp = 0;
else if (temp > 255)
temp = 255;
B[i + j * w] = temp;
}
}
- 注意:和rgb2yuv一样,此处需要使用一个中间量temp来判断溢出,不同的是,在本次实验中,rgb2yuv中temp的使用与否对最后图像结果影响不大,但在yuv2rgb中,如果不使用temp判断溢出,则最后的rgb图像中将会出现大量红、蓝点,如下图:
- 将求出的R、G、B数据写入rgb文件时注意:
- bmp格式中数据排放在行数上倒序,即rgb文件中第一行应写入图像最后一行的数据
- bmp格式中每个像素点中的数据存储顺序为B、G、R
实现程序如下:
for (j = h - 1; j >= 0; j--)
{
for (i = 0; i < w; i++)
{
fp_rgb.write((char*)(B + j * w + i), sizeof(unsigned char));
fp_rgb.write((char*)(G + j * w + i), sizeof(unsigned char));
fp_rgb.write((char*)(R + j * w + i), sizeof(unsigned char));
}
}
完整代码
rgb2yuv.cpp
#include "pch.h"
#include<iostream>
#include<fstream>
#define h 256
#define w 256
using namespace std;
int main()
{
ifstream fp_rgb;
ofstream fp_yuv;
fp_rgb.open("down.rgb", ios::in | ios::binary);
//打开down.rgb文件,模式选用二进制读取操作
if (!fp_rgb)
{
cout << "down.rgb open failed" << endl;
return 0;
}
fp_yuv.open("down.yuv", ios::out | ios::trunc);
//预先打开down.yuv文件,模式选用写入操作,覆盖原有信息
if (!fp_yuv)
{
cout << "open failed" << endl;
return 0;
}
unsigned char B[h*w] = { 0 }, G[h*w] = {0}, R[h*w] = { 0 };
for (i = 0; i < w * h; i++)
{
fp_rgb.read((char*)(B + i), sizeof(unsigned char));
//直接从rgb数值中读取r、g、b
fp_rgb.read((char*)(G + i), sizeof(unsigned char));
fp_rgb.read((char*)(R + i), sizeof(unsigned char));
}
unsigned char Y[h*w] = { 0 }, U[h*w / 4] = { 0 }, V[h*w / 4] = { 0 };
for (i = 0; i < w*h; i++)
{
Y[i] = 0.299*R[i] + 0.587*G[i] + 0.114*B[i];
if (Y[i] < 16)
Y[i] = 16;
else if (Y[i] > 235)
Y[i] = 235;
}
int temp;
for (j = 0; j < h/2; j++)
{
for (i = 0; i < w/2; i ++)
{
temp= -0.1687 * ((R[2 * i + j * 2 *w] + R[2 * i + 1 + j * 2 * w] + R[2 * i + (j * 2 + 1) * w] + R[2 * i + 1 + (j * 2 + 1) * w]) / 4)
- 0.3313 * ((G[2 * i + j * 2 * w] + G[2 * i + 1 + j * 2 * w] + G[2 * i + (j * 2 + 1) * w] + G[2 * i + 1 + (j * 2 + 1) * w]) / 4)
+ 0.5 * ((B[2 * i + j * 2 * w] + B[2 * i + 1 + j * 2 * w] + B[2 * i + (j * 2 + 1) * w] + B[2 * i + 1 + (j * 2 + 1) * w]) / 4) + 128;
if (temp < 16)
temp = 16;
else if (temp > 240)
temp = 240;
U[j*w / 2 + i] = temp;
temp= 0.5 * ((R[2 * i + j * 2 * w] + R[2 * i + 1 + j * 2 * w] + R[2 * i + (j * 2 + 1) * w] + R[2 * i + 1 + (j * 2 + 1) * w]) / 4)
- 0.4187 * ((G[2 * i + j * 2 * w] + G[2 * i + 1 + j * 2 * w] + G[2 * i + (j * 2 + 1) * w] + G[2 * i + 1 + (j * 2 + 1) * w]) / 4)
- 0.0813 * ((B[2 * i + j * 2 * w] + B[2 * i + 1 + j * 2 * w] + B[2 * i + (j * 2 + 1) * w] + B[2 * i + 1 + (j * 2 + 1) * w]) / 4) + 128;
if (temp < 16)
temp = 16;
else if (temp > 240)
temp = 240;
V[j*w / 2 + i] =temp;
}
}
for (i = 0; i < w*h; i++)
{
fp_yuv.write((char*)(Y+i), sizeof(unsigned char));
}
for (i = 0; i < w*h / 4; i++)
{
fp_yuv.write((char*)(U+i), sizeof(unsigned char));
}
for (i = 0; i < w*h / 4; i++)
{
fp_yuv.write((char*)(V+i), sizeof(unsigned char));
}
fp_rgb.close();
fp_yuv.close();
//释放缓冲区
}
yuv2rgb
#include "pch.h"
#include<iostream>
#include<fstream>
#define h 256
#define w 256
using namespace std;
int main()
{
ifstream fp_yuv;
ofstream fp_rgb;
fp_yuv.open("down.yuv", ios::in | ios::binary);
//打开down.rgb文件,模式选用二进制读取操作
if (!fp_yuv)
{
cout << "down.yuv open failed" << endl;
return 0;
}
fp_rgb.open("down.rgb", ios::out | ios::binary);
//预先打开down.yuv文件,模式选用二进制写入操作
if (!fp_rgb)
{
cout << "open failed" << endl;
return 0;
}
int i, j;
unsigned char Y[h*w] = { 0 }, U[h*w / 4] = { 0 }, V[h*w / 4] = { 0 };
for (i = 0; i < w*h; i++)
{
fp_yuv.read((char*)(Y + i), sizeof(unsigned char));
}
for (i = 0; i < w*h / 4; i++)
{
fp_yuv.read((char*)(U + i), sizeof(unsigned char));
}
for (i = 0; i < w*h / 4; i++)
{
fp_yuv.read((char*)(V + i), sizeof(unsigned char));
}
int m, n;
int temp;
unsigned char B[h*w] = { 0 }, G[h*w] = { 0 }, R[h*w] = { 0 };
for (j = 0; j < h; j++)
{
n = j / 2;
for (i = 0; i < w; i++)
{
m = i / 2;
temp= Y[i + j * w] + 1.402*(V[m + n * w / 2] - 128);
if (temp < 0)
temp = 0;
else if (temp > 255)
temp = 255;
R[i + j * w] = temp;
temp= Y[i + j * w] - 0.3441*(U[m + n * w / 2] - 128) - 0.7139*(V[m + n * w / 2] - 128);
if (temp < 0)
temp = 0;
else if (temp > 255)
temp = 255;
G[i + j * w] = temp;
temp= Y[i + j * w] + 1.7718*(U[m + n * w / 2] - 128) - 0.0013*(V[m + n * w / 2] - 128);
if (temp < 0)
temp = 0;
else if (temp > 255)
temp = 255;
B[i + j * w] = temp;
}
}
for (j = h - 1; j >= 0; j--)
{
for (i = 0; i < w; i++)
{
fp_rgb.write((char*)(B + j * w + i), sizeof(unsigned char));
fp_rgb.write((char*)(G + j * w + i), sizeof(unsigned char));
fp_rgb.write((char*)(R + j * w + i), sizeof(unsigned char));
}
}
fp_rgb.close();
fp_yuv.close();
//释放缓冲区
}