#ifndef/#define/#endif 使用详解:http://blog.csdn.net/abc5382334/article/details/18052757
Debug与Release版本的区别详解:http://blog.csdn.net/ithzhang/article/details/7575483#comments
VS中Release和Debug模式的区别:http://blog.csdn.net/eric491179912/article/details/6154375
YUV格式分析:http://www.cnblogs.com/armlinux/archive/2012/02/15/2396763.html
一、实验基本原理
1. 彩色空间转换的基本思想及转换公式
(1) YUV与RGB空间的相互转换
RGB格式转YUV格式:
由电视原理知识,
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.8860 B
为了使色差信号的动态范围控制在-0.5-0.5之间,需要进行归一化,对色差信号引入压缩系数,归一化后的色差信号为:
U = - 0.1684 R - 0.3316 G + 0.5 B
V = 0.5 R - 0.4187 G - 0.0813 B
(2) 码电平分配及数字表达式
亮电平信号:在对分量信号进行8比特均匀量化时,共分为256个等间隔的量化级。为了防止信号变动造成过载,在256级上端留20级,下端留16级作为信号超越动态范围的保护带。
色差信号:量化后码电平分配色差信号经过归一化处理后,动态范围为-0.5-0.5,让色差零电平对应码电平128,色差信号总共占225个量化级。在256级上端留15级,下端留16级作为信号超越动态范围的保护带。
由此可知,YUV转RGB公式为:
R = Y + 1.4075 *(V-128)
G = Y– 0.3455 *(U–128) – 0.7169 *(V–128)
B = Y + 1.779 *(U–128)
(3) 色度格式
4:2:0格式:色差信号U,V的取样频率为亮度信号取样频率的四分之一,在水平方向和垂直方向上的取样点数均为Y的一半。
所以:
RGB转换YUV格式时,根据公式计算得到一帧Y、U、V数据后,要对色差信号U、V进行下采样。
YUV转换RGB格式时,在利用公式进行计算之前首先要对色差信号U、V进行上采样。(下面是两张丑丑的图示~)
二、实验流程
1. 建立工程,C相关知识回顾(养成良好代码书写习惯)
1) 程序文件一般分为三部分:
(1)头文件:包括与结构(类)的声明和使用这些结构(类)的函数的原型,不要将函数的定义或变量的声明放在头文件中,#ifndef/#define/#endif 防止头文件被多次引用,详解见文章头部链接。
(2)源代码文件:包含与结构(类)有关的函数的代码,即函数的定义
(3)源代码文件(main.c/cpp):包含调用这些函数的代码
2) 学会使用带参主函数定义以及命令参数、可执行文件工作路径的设置
VS2013步骤为:项目→项目属性→配置属性→调试→命令参数(编辑)/工作目录(编辑:绝对路径/浏览:找到具体文件夹目录)如下图:
关于参数设置过程中Debug与Release版本的区别详解链接见文章头部链接。
2. 调试RGB转换YUV程序
理解课上所给例程,尤其对色差信号进行4:2:0下采样部分,妙用指针可以很容易实现,在课程作业编写YUV转换RGB程序中对U、V数据进行上采样也是借鉴了例程的指针用法。
3. 编写YUV转换RGB程序,具体见下一部分。
三、关键代码
#include "stdlib.h"
#include "yuv2rgb.h"
/*YUV->RGB 转换公式
R = Y + 1.4075 *(V-128)
G = Y – 0.3455 *(U –128) – 0.7169 *(V –128)
B = Y + 1.779 *(U – 128)
*/
static float YUV2RGB14075[256];
static float YUV2RGB03455[256], YUV2RGB07169[256];
static float YUV2RGB1779[256];
/************************************************************************
*
* int YUV2RGB (int x_dim, int y_dim, void *ychunk, void *uchunk, void *vchunk, void *rgb_out, int flip)
*
* Purpose : It takes a YUV (4:2:0) format and convert it into
* a 24-bit RGB bitmap.
*
* Input : x_dim the x dimension of the bitmap
* y_dim the y dimension of the bitmap*
* yuv chunk pointer to the YUV structure
rgb_out pointer to the buffer of the bitmap
*
* Output : 0 OK
* 1 wrong dimension
* 2 memory allocation error
*
* Side Effect :
* None
*
* Date : 03/11/2017
*
************************************************************************/
int YUV2RGB(int x_dim, int y_dim, void *ychunk, void *uchunk, void *vchunk, void *rgb_out, int flip)
{
static int init_done = 0;
long i, j, size;
unsigned char *r, *g, *b;
unsigned char *y, *u, *v;
unsigned char *pu1, *pu2, *pv1, *pv2, *psu, *psv;
unsigned char *addu_buffer, *addv_buffer;
unsigned char *addu_buf, *addv_buf;
if (init_done == 0)
{
InitLookupTable();
init_done = 1;
}
// check to see if x_dim and y_dim are divisible by 2
if ((x_dim % 2) || (y_dim % 2)) return 1;
size = x_dim * y_dim;
// allocate memory
addu_buffer = (unsigned char *)malloc(size * sizeof(unsigned char));
addv_buffer = (unsigned char *)malloc(size * sizeof(unsigned char));
b = (unsigned char *)rgb_out;
y = (unsigned char *)ychunk;
u = (unsigned char*)uchunk;
v = (unsigned char*)vchunk;
addu_buf = (unsigned char *)addu_buffer;
addv_buf = (unsigned char *)addv_buffer;
// 对UV块进行上采样
for (j = 0; j < y_dim/2; j++)
{
psu = u + j * x_dim / 2;//原始数据U块指针
psv = v + j * x_dim / 2;//原始数据V块指针
pu1 = addu_buf + 2 * j * x_dim; //U块奇数行首地址
pu2 = addu_buf + (2 * j + 1) * x_dim;//U块偶数行首地址
pv1 = addv_buf + 2 * j * x_dim; //V块奇数行首地址
pv2 = addv_buf + (2 * j + 1) * x_dim;//V块偶数行首地址
for (i = 0; i < x_dim/2; i++)
{
*pu1 = *psu;
*pu2 = *psu;
pu1++;
pu2++;
*pu1 = *psu;
*pu2 = *psu; //U块上采样:“以1作4”
*pv1 = *psv;
*pv2 = *psv;
pv1++;
pv2++;
*pv1 = *psv;
*pv2 = *psv; //V块上采样:“以1作4”
pu1++;
pu2++;
pv1++;
pv2++;
psu++;
psv++;
}
}
// convert YUV to RGB
if (!flip) {
for (j = 0; j < y_dim; j++)
{
y = y + (y_dim - j - 1) * x_dim;
addu_buf = addu_buf + (y_dim - j - 1) * x_dim;
addv_buf = addv_buf + (y_dim - j - 1) * x_dim;
for (i = 0; i < x_dim; i++) {
g = b + 1;
r = b + 2;
adjust(b, g, r, y, addu_buf, addv_buf);
b += 3;
y++;
addu_buf++;
addv_buf++;
}
}
}
else {
for (i = 0; i < size; i++)
{
g = b + 1;
r = b + 2;
adjust(b, g, r, y, addu_buf, addv_buf);//对变量范围作适应性调整
b += 3;
y++;
addu_buf++;
addv_buf++;
}
}
free(addu_buffer);
free(addv_buffer);
return 0;
}
void InitLookupTable()
{
int i;
for (i = 16; i < 240; i++) YUV2RGB14075[i] = (float)1.4075 * (i-128);
for (i = 16; i < 240; i++) YUV2RGB03455[i] = (float)0.3455 * (i - 128);
for (i = 16; i < 240; i++) YUV2RGB07169[i] = (float)0.7169 * (i - 128);//
for (i = 16; i < 240; i++) YUV2RGB1779[i] = (float)1.779 * (i-128);
}
void adjust(unsigned char *b, unsigned char *g, unsigned char *r, unsigned char *y, unsigned char *u, unsigned char *v)
{
float temp = 0;
temp = (float)(*y + YUV2RGB1779[*u]);
temp = temp>255 ? 255 : temp;
temp = temp<0 ? 0 : temp;
*b = (unsigned char)temp;
temp = (float)(*y - YUV2RGB03455[*u] - YUV2RGB07169[*v]);
temp = temp>255 ? 255 : temp;
temp = temp<0 ? 0 : temp;
*g = (unsigned char)temp;
temp = (float)(*y + YUV2RGB14075[*v]);
temp = temp>255 ? 255 : temp;
temp = temp<0 ? 0 : temp;
*r = (unsigned char)temp;
}
四、程序分析
①RGB格式裸数据部分,三通道的排列顺序依次是B\G\R,所以一个像素点有三个分别表示颜色的数据。
② YUV格式通常有两大类:
打包(packed)格式:将YUV分量存放在同一个数组中,通常是几个相邻的像素组成一个宏像素(macro-pixel);
平面(planar)格式:使用三个数组分开存放YUV三个分量,就像是一个三维平面一样。
下面是我们测试所用RGB与YUV文件的两张丑丑的排列简示图~
依次是B G R B G R.........
每帧数据Y U V分块排列......
程序实现过程:
1. 主程序实现对YUV视频逐帧读取数据分别处理,根据格式特点,开辟三个缓存分别存放YUV三个分量各自的数据。
2. 关键函数首先对U\V色差信号作上采样,下采样过程中相邻四个数据取平均得到一个U/V,所以上采样我们这里相应地就可以->以1代4。
3. 接着就是带入公式计算,依然使用查找表的方法,重点注意强制类型转换过程,在void adjust(~~~)函数中实现,超出0~255范围内的rgb值, 负数取0,大于255的取255。
4. 最后按照存储格式写入文件。
五、实验结果
(所有YUV测试文件均来自网站:http://trace.eas.asu.edu/yuv/index.html)
1. 如何检验程序结果是否正确:
由于RGB的裸数据并不构成文件格式,所以我们的YUV视频通过程序转换成RGB文件以后,再用RGB2YUV程序转换成YUV格式文件,用YUV播放 器查看,对比前后两个视频图像画面,若有明显色调失真或不明色块之类则表明程序出错。
2. 下面是这次实验程序编写过程中出现的错误整理:
①程序结果出错:
测试得到上图结果时,首先注意到明显的蓝红色块,猜测是公式计算过程中强制类型转换错误,老师上课也提醒过,查阅相关书籍得到C语言各种数据类型大小范围如下图:
改正: 将公式计算和强制类型转换过程另写函数,将计算所得结果在unsignedchar类型 0~255范围之外的再次处理。
②程序运行报错:program received signal SIGSEGV,Segmentationfault.
通过相关搜索以及不断改正调试发现,出现这种错误的原因是因为声明指针后没有初始化,指针指向地址为未知,未初始化指针直接执行运算就会出错。
改正:程序中声明的指针初始化。
③程序本身错误:对比差错图像与原图像具体差异:
下面是一组结果图:
蓝红色块错误处理完之后,得到的图像还是不对,但对于错误原因不是很清楚,于是再次进行各种测试。
看上去好像是色调改变了
继续测试
。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
还好找到了这个颜色丰富亮丽的YUV,帮了大忙
似曾相识的竖条色彩分界
。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
所以这应该是发生了某部分数据的位置偏移,问题应该出现在有关数据计算部分。
于是重新查看程序,在直接得到数据的计算过程和U/V色差信号的上采样两部分仔细排查,最终发现错误出现采样程序部分的指针使用。
错误:程序中指针psu\psv多进行了一次偏移超出数据范围。
上采样部分正确代码已经在上面程序部分给出,指针使用图示如下:
指针简图(V同理):
以上错误改正以后再次进行多次测试,结果表明程序运行结果正确。
test 1----
test 2----
test 3----
五、结论
掌握指针运算很重要,会使程序变的简单。
tips/2017/04/30:对于人眼来说,亮度信号是最敏感的,如果将彩色图像转换为灰度图像,仅仅需要转换保存亮度信号就可以。
YUV官方播放器的注册方法:
官方提供注册码:
REGEDIT4
[HKEY_CURRENT_USER\Software\tihohod.com\YUVPlayer\RegistrationInfo]
"RegistrationName"="Peace on Earth -Goodwill to Men"
"RegistrationKey"="JxsQHEseR43VXvp6kCV8w5ACtqNVJ11LhOAE4Pztk+Ksw211wohNjsR78XHoGajwlqIOU6gVz0zipwkJCfdvIqyAJrEgJLYB+NKwPGaD+OqbJD+oe+5xixyIKc7tT0ecQjNER47sA3HsUUhRl3LmKozxS2nH233WNPpJwoF4rL1Bjm5OfcM/jiixF/By85wQTdzSwAehjfvB7iO9tCaI9A=="
把上面key的内容复制到一个编辑器,另存为key.reg,点击运行这个文件会自动注册,就OK了