在嵌入式设备上进行神经网络推理时,经常会涉及到YUV420p到RGB之间的转换
在之前的文章中简单描述过YUV420p和RGB24的存储格式,为了方便理解,这里再次列出其存储格式。
YUV420p RGB24
Y Y Y Y Y Y Y Y R R R R R R R R
Y Y Y Y Y Y Y Y G G G G G G G G
Y Y Y Y Y Y Y Y B B B B B B B B
Y Y Y Y Y Y Y Y R R R R R R R R
U U U U U U U U G G G G G G G G
V V V V V V V V B B B B B B B B
上述信息中简单回顾了YUV420p和RGB24的存储格式,它们之间的区别在于:YUV420p属于分开存储(Y存储完再存储U最后再存储V),RGB24属于连续存储(一个像素点由8位的R元素、8位的G元素、8位的B元素构成)。其中,类似于YUV420p的存储方式称为Planar方式,RGB24的存储方式称为Packed方式。
知道了内部存储方式之后,我们还需要知道如何从YUV数据转换成RGB数据,所以我们还需要知道YUV420p到RGB24的数据转换的公式。
B = Y + 1.779 * (U-128)
G = Y - 0.3455 * (U-128) - 0.7169 * (V-128)
R = Y + 1.4075 * (V-128)
下面是代码段
注:这里为了转换后的RGB数据可能不在0~255这个区间内,所以多设置了一个函数避免越过这个区间。
#include
#include
using namespace std;
#define rgbFileName "rgbTest.rgb"
#define yuvFileName "lena_256x256_yuv420p.yuv"
/* 防止计算后的数据过大或者过小 */
unsigned char clipValue(unsigned char x, unsigned char minVal, unsigned char maxVal)
{
if (x > maxVal)
{
return maxVal;
}
else if (x < minVal)
{
return minVal;
}
else
{
return x;
}
}
bool yuv420ToRGB24(unsigned char *yuvData, int width, int height, unsigned char *rgbData)
{
int indexY = 0;
int indexU = 0;
int indexV = 0;
unsigned char rData;
unsigned char gData;
unsigned char bData;
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
indexY = i * width + j;
indexU = width * height + i / 4 * width + j / 2;
indexV = width * height * 5 / 4 + i / 4 * width + j / 2;
rData = yuvData[indexY] + 1.402 * (yuvData[indexV] - 128); //R = Y+1.4075*(V-128)
gData = yuvData[indexY] - 0.34413 * (yuvData[indexU] - 128) - 0.71414 * (yuvData[indexV] - 128); //G = Y-0.3455*(U-128)-0.7169*(V-128)
bData = yuvData[indexY] + 1.772 * (yuvData[indexU] - 128); //B = Y +1.779*(U-128)
*(rgbData++) = clipValue(rData, 0, 255);
*(rgbData++) = clipValue(gData, 0, 255);
*(rgbData++) = clipValue(bData, 0, 255);
}
}
return true;
}
int main()
{
int width = 256;
int height = 256;
unsigned char *yuvData = (unsigned char *)malloc(width * height * 3 / 2);
unsigned char *rgbData = (unsigned char *)malloc(width * height * 3);
FILE *fp1 = NULL;
FILE *fp2 = NULL;
fp1 = fopen(yuvFileName, "r+");
fread(yuvData, 1, width * height * 3 / 2, fp1);
yuv420ToRGB24(yuvData, width, height, rgbData);
fp2 = fopen(rgbFileName, "w+");
fwrite(rgbData, 1, width * height * 3, fp2);
fclose(fp1);
fclose(fp2);
free(yuvData);
free(rgbData);
return 0;
}
刚刚我们演示了YUV420p转RGB24。RGB24转YUV420p就只不过是一个逆过程而已,同样的我们贴出公式
Y = 0.299 * R + 0.587 * G + 0.114 * B
U = -0.147 * R - 0.289 * G + 0.463 * B
V = 0.615 * R - 0.515 * G - 0.100 * B
注意: 在YUV420p中,U和V占有的字节数之和应该是Y的一半,所以U,V在水平和垂直方向的取样数是Y的一半
#include
#include
using namespace std;
#define rgbFileName "rgbTest.rgb"
#define yuvFileName "yuvTest.yuv"
/* 防止计算后的数据过大或者过小 */
unsigned char clipValue(unsigned char x, unsigned char minVal, unsigned char maxVal)
{
if (x > maxVal)
{
return maxVal;
}
else if (x < minVal)
{
return minVal;
}
else
{
return x;
}
}
bool RGB24ToYuv420(unsigned char *RgbData, int width, int height, unsigned char *YuvData)
{
unsigned char *ptrY;
unsigned char *ptrU;
unsigned char *ptrV;
unsigned char *ptrRGB;
memset(YuvData, 0, width * height * 3 / 2);
ptrY = YuvData;
ptrU = YuvData + width * height;
ptrV = ptrU + (width * height * 1 / 4);
unsigned char yData;
unsigned char uData;
unsigned char vData;
unsigned char rData;
unsigned char gData;
unsigned char bData;
for (int j = 0; j < height; j++)
{
ptrRGB = RgbData + width * j * 3;
for (int i = 0; i < width; i++)
{
rData = *(ptrRGB++);
gData = *(ptrRGB++);
bData = *(ptrRGB++);
/*
原公式
Y = 0.299 * R + 0.587 * G + 0.114 * B
U = -0.147 * R - 0.289 * G + 0.463 * B
V = 0.615 * R - 0.515 * G - 0.100 * B
这里转换成整数运算
*/
yData = (unsigned char)((66 * rData + 129 * gData + 25 * bData + 128) >> 8) + 16;
uData = (unsigned char)((-38 * rData - 74 * gData + 112 * bData + 128) >> 8) + 128;
vData = (unsigned char)((112 * rData - 94 * gData - 18 * bData + 128) >> 8) + 128;
/* Y取全部 */
*(ptrY++) = clipValue(yData, 0, 255);
if (j % 2 == 0 && i % 2 == 0)
{
/* U取偶数行的偶数列 */
*(ptrU++) = clipValue(uData, 0, 255);
}
else
{
if (i % 2 == 0)
{
/* V取奇数行的偶数列 */
*(ptrV++) = clipValue(vData, 0, 255);
}
}
}
}
return true;
}
int main()
{
int width = 256;
int height = 256;
unsigned char *yuvData = (unsigned char *)malloc(width * height * 3 / 2);
unsigned char *rgbData = (unsigned char *)malloc(width * height * 3);
FILE *fp1 = NULL;
FILE *fp2 = NULL;
fp2 = fopen(rgbFileName, "r+");
fread(rgbData, 1, width * height * 3, fp2);
RGB24ToYuv420(rgbData, width, height, yuvData);
fp1 = fopen(yuvFileName, "w+");
fwrite(yuvData, 1, width * height * 3 / 2, fp1);
fclose(fp1);
fclose(fp2);
free(yuvData);
free(rgbData);
return 0;
}