音视频入门(三) - RGB和YUV之间的转换

一、YUV420p转RGB24

在嵌入式设备上进行神经网络推理时,经常会涉及到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;
}

二、RGB24转YUV420p

刚刚我们演示了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;
}

你可能感兴趣的:(音视频,视频处理,linux,嵌入式)