本次实验在老师的带领下已经完成了一半(rgb转yuv),在后续yuv转rgb的代码中本次不再采用过去的一个main
函数走到底的方法,尝试把方法函数和主函数分块处理。
Y = 0.2990 R + 0.5870 G + 0.1140 B U − 128 = − 0.1684 R − 0.3316 G + 0.5 B V − 128 = 0.5 R − 0.4187 G − 0.0813 B Y=0.2990R+0.5870G+0.1140B\\ U-128=-0.1684R-0.3316G+0.5B\\ V-128=0.5R-0.4187G-0.0813B Y=0.2990R+0.5870G+0.1140BU−128=−0.1684R−0.3316G+0.5BV−128=0.5R−0.4187G−0.0813B
运行结果:
运用YUVviewersPlus打开输出文件down_new.yuv
查看:
起初按照所学要求更改一些配置之后一直无法正常运行,反反复复重做几遍后,偶然发现问题出在活动解决方案平台这里,将平台从x86
改为x64
后成功,不过不太明白具体原因,这里记录一下。
yuv转rgb公式:
将已知的rgb转yuv公式化为矩阵式:
A = [ 0.2990 0.5870 0.1140 − 0.1684 − 0.3316 0.5 0.5 − 0.4187 − 0.0813 ] A= \begin{bmatrix} 0.2990 & 0.5870 & 0.1140 \\ -0.1684 & −0.3316 & 0.5 \\ 0.5 & -0.4187 & -0.0813 \end{bmatrix} \quad A=⎣⎡0.2990−0.16840.50.5870−0.3316−0.41870.11400.5−0.0813⎦⎤
[ Y , U − 128 , V − 128 ] T = A ∗ [ R , G , B ] T [Y,U-128,V-128]^T =A*[R,G,B]^T [Y,U−128,V−128]T=A∗[R,G,B]T
等式左右两侧左乘A的逆矩阵得:
[ R , G , B ] T = A − 1 ∗ [ Y , U − 128 , V − 128 ] T [R,G,B]^T=A^{-1}*[Y,U-128,V-128]^T [R,G,B]T=A−1∗[Y,U−128,V−128]T
借助matlab进行运算得到:
A − 1 = [ 1 0 1.4020 1 − 0.3441 − 0.7139 1 1.7718 − 0.0013 ] A^{-1}= \begin{bmatrix} 1& 0& 1.4020\\ 1& -0.3441 & -0.7139\\ 1& 1.7718& -0.0013 \end{bmatrix} \quad A−1=⎣⎡1110−0.34411.77181.4020−0.7139−0.0013⎦⎤
故yuv转rgb公式如下:
R = Y + 1.4020 ( V − 128 ) G = Y − 0.3441 ( U − 128 ) − 0.7139 ( V − 128 ) B = Y + 1.7718 ( U − 128 ) − 0.0013 ( V − 128 ) R=Y+1.4020(V-128)\\ G=Y-0.3441(U-128)-0.7139(V-128)\\ B=Y+1.7718(U-128)-0.0013(V-128) R=Y+1.4020(V−128)G=Y−0.3441(U−128)−0.7139(V−128)B=Y+1.7718(U−128)−0.0013(V−128)
int YUV2RGB(unsigned char* yuv, unsigned char* rgb, int height, int width);
int YUV420TOYUV444(unsigned char* yuv420, unsigned char* yuv444, int height, int width);
void InitLookupTable();
float judge(float i);
#define _CRT_SECURE_NO_DEPRECATE
#include "yuv2rgb.h"
#include
using namespace std;
int main(int argc, char** argv)
{
//定义变量
int frameWidth;
int frameHeight;
char* rgbFileName = NULL;
char* yuvFileName = NULL;
FILE* rgbFile = NULL;
FILE* yuvFile = NULL;
unsigned char* yuv420Buf = NULL;
unsigned char* yuv444Buf = NULL;
unsigned char* rgbBuf = NULL;
//传入参数
yuvFileName = argv[1];
rgbFileName = argv[2];
frameWidth = atoi(argv[3]);
frameHeight = atoi(argv[4]);
//分配空间
yuv420Buf = (unsigned char*)malloc(frameWidth * frameHeight * 3 / 2 * sizeof(unsigned char));
yuv444Buf = (unsigned char*)malloc(frameWidth * frameHeight * 3 * sizeof(unsigned char));
rgbBuf = (unsigned char*)malloc(frameWidth * frameHeight * 3);
//打开文件
yuvFile = fopen(yuvFileName, "rb");
if (yuvFile == NULL)
{
printf("cannot find yuv file\n");
exit(1);
}
else
{
cout << "The input yuv file is " << yuvFileName << endl;
}
rgbFile = fopen(rgbFileName, "wb");
if (rgbFile == NULL)
{
printf("cannot find rgb file\n");
exit(1);
}
else
{
cout << "The input rgb file is " << rgbFileName << endl;
}
//读取数据
fread(yuv420Buf, 1, frameWidth * frameHeight * 3 / 2, yuvFile);
//采样格式转换
YUV420TOYUV444(yuv420Buf, yuv444Buf, frameHeight, frameWidth);
//debug检查格式转换成功没
//fwrite(yuv444Buf, 1, frameHeight * frameWidth * 3, rgbFile);
//yuv转rgb
YUV2RGB(yuv444Buf, rgbBuf, frameHeight, frameWidth);
//写入文件
fwrite(rgbBuf, 1, frameHeight * frameWidth * 3, rgbFile);
//关闭文件
fclose(yuvFile);
fclose(rgbFile);
return 0;
}
引头文件,定义用在查找表中的全局变量。
#include "yuv2rgb.h"
#include
using namespace std;
static float YUVRGB14020[256], YUVRGB03441[256], YUVRGB07139[256], YUVRGB17718[256], YUVRGB00013[256];
用来将原有的420格式的yuv文件转换为444格式的yuv文件,方便后续转rgb的处理。
int YUV420TOYUV444(unsigned char* yuv420, unsigned char* yuv444, int height, int width)
{
//定义变量,分配空间
unsigned char* y = NULL, * ou = NULL, * ov = NULL;
unsigned char* u = NULL, * v = NULL;
ou = (unsigned char*)malloc(height * width / 4 * sizeof(unsigned char));
ov = (unsigned char*)malloc(height * width / 4 * sizeof(unsigned char));
y = (unsigned char*)malloc(height * width * sizeof(unsigned char));
u = (unsigned char*)malloc(height * width * sizeof(unsigned char));
v = (unsigned char*)malloc(height * width * sizeof(unsigned char));
//分离yuv数据
for (int i = 0; i < height * width; i++)
{
*(y + i) = *yuv420;
yuv420++;
}
for (int i = 0; i < height * width * 1 / 4; i++)
{
*(ou + i) = *yuv420;
yuv420++;
}
for (int i = 0; i < height * width * 1 / 4; i++)
{
*(ov + i) = *yuv420;
yuv420++;
}
//采样格式转换
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
*(u + i * width + j) = *(ou + (i / 2) * (width / 2) + j / 2);
*(v + i * width + j) = *(ov + (i / 2) * (width / 2) + j / 2);
}
}
//整理到444格式的yuv中
for (int i = 0; i < height * width; i++)
{
*(yuv444 + i) = *y;
*(yuv444 + i + height * width) = *u;
*(yuv444 + i + height * width * 2) = *v;
y++;
u++;
v++;
}
return 0;
}
数字过多,做查找表方便函数里数学公式的运用。
void InitLookupTable()
{
int i;
for (i = 0; i < 256; i++) YUVRGB14020[i] = (float)1.4020 * (i - 128);
for (i = 0; i < 256; i++) YUVRGB03441[i] = (float)0.3441 * (i - 128);
for (i = 0; i < 256; i++) YUVRGB07139[i] = (float)0.7139 * (i - 128);
for (i = 0; i < 256; i++) YUVRGB17718[i] = (float)1.7718 * (i - 128);
for (i = 0; i < 256; i++) YUVRGB00013[i] = (float)0.0013 * (i - 128);
}
用来控制转换后的rgb结果溢出造成图像失真。
float judge(float i)
{
//控制在0~255内
if (i > 255) { i = 255; }
if (i < 0) { i = 0; }
return i;
}
实现444格式的yuv文件向rgb文件转换。
int YUV2RGB(unsigned char* yuv, unsigned char* rgb, int height, int width)
{
//引入查找表
InitLookupTable();
//定义变量,分配空间
unsigned char* y = NULL, * u = NULL, * v = NULL;
unsigned char* r = NULL, * g = NULL, * b = NULL;
y = (unsigned char*)malloc(height * width * sizeof(unsigned char));
u = (unsigned char*)malloc(height * width * sizeof(unsigned char));
v = (unsigned char*)malloc(height * width * sizeof(unsigned char));
r = (unsigned char*)malloc(height * width * sizeof(unsigned char));
g = (unsigned char*)malloc(height * width * sizeof(unsigned char));
b = (unsigned char*)malloc(height * width * sizeof(unsigned char));
//分离yuv变量
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
*(y + i * width + j) = *(yuv + i * width + j);
*(u + i * width + j) = *(yuv + i * width + j + height * width);
*(v + i * width + j) = *(yuv + i * width + j + height * width * 2);
}
}
//利用公式求rgb
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
*(r + i * width + j) = (unsigned char)judge((float)*y + YUVRGB14020[*v]);
*(g + i * width + j) = (unsigned char)judge((float)*y - YUVRGB03441[*u] - YUVRGB07139[*v]);
*(b + i * width + j) = (unsigned char)judge((float)*y + YUVRGB17718[*u] - YUVRGB00013[*v]);
y++;
u++;
v++;
}
}
//按照BGR顺序排列将分离的rgb分量放入整合的rgb中
for (int i = 0; i < height * width; i++)
{
*(rgb + i * 3) = *b;
*(rgb + i * 3 + 1) = *g;
*(rgb + i * 3 + 2) = *r;
b++;
g++;
r++;
}
return 0;
}
用YUVviewersPlus打开输出的图像并与原rgb图像做对比:
对比两图片可看到基本上没有什么差异,但是放大点看发现程序输出的图像和原rgb图在一些细节地方仍有些许色彩差异,思考后认为其原因为:
down_new.rgb
是由down.yuv
转换过来的,而down.yuv
是420格式的,在转化为444格式的yuv文件过程中,444格式的yuv文件uv分量存在信息的丢失,造成在色彩急剧变化的区域存在较为明显的差异。先按照自己的理解一口气把代码写完了,输出的文件可以说是全失真的,找又找不到问题,debug的过程中非常痛苦,一些稍大的问题如下:
用法1:
//分离yuv数据
for (int i = 0; i < height * width; i++)
{
*(y + i) = *yuv420;
yuv420++;
}
用法2:
//分离yuv数据
for (int i = 0; i < height * width; i++)
{
y = yuv420;
y++;
yuv420++;
}
方法2中*y
指针在循环结束时已经指向了结尾,在后续的使用中还要再用循环调回来才能重新用*y
的开头,为了避免多余操作,最后还是用了方法1来实现。
在输出图像频繁失真后,回过头来找问题发现真的大海捞针。。搁置了几天之后突然想到一个方法可以稍微定位一下问题所在位置:
//debug检查格式转换成功没
fwrite(yuv444Buf, 1, frameHeight * frameWidth * 3, rgbFile);
用YUVviewersPlus先打开看了看,发现在第一部分的实现中已经出错,又仔细查看修改了YUV420TOYUV444
函数的逻辑,最后成功。
本来没有设置judge
函数,输出的图片存在少量但极为明显的红色和蓝色点点,问了问同学,最后设置上了judge
函数,把溢出的部分设为定值。