YUV图像的处理

300 阅读4分钟

YUV你究竟了解多少YUV格式大全两篇文章中,我们已经对YUV图像格式的数据排列方式,格式类型等有了个大概的认识,这篇文章中我们会给大家介绍下YUV图像的处理,下面还是以较常见的 NV21格式为例。

YUV 图像的定义、加载、保存及内存释放。

// 定义结构体
typedef struct
{
    int width;                 // 图片宽
    int height;                // 图片高 
    unsigned char  *yPlane;    // Y 平面指针
    unsigned char  *uvPlane;   // UV 平面指针
} YUVImage;

// 加载YUV图像
void LoadYUVImage(const char *filePath, YUVImage *yUV)
{
    FILE *fpData = fopen(filePath, "rb+");
    if (fpData)
    {
        fseek(fpData, 0, SEEK_END);
        int len = ftell(fpData);
        yUV->yPlane = malloc(len);
        fseek(fpData, 0, SEEK_SET);
        fread(yUV->yPlane, 1, len, fpData);
        fclose(fpData);
        fpData = NULL;
    }
    yUV->uvPlane = yUV->yPlane + yUV->width * yUV->height;
}

// 保存YUV图像
void SaveYUVImage(const char *filePath, YUVImage *yUV)
{
    FILE *fp = fopen(filePath, "wb+");
    if (fp)
    {
        fwrite(yUV->yPlane, yUV->width * yUV->height, 1, fp);
        fwrite(yUV->uvPlane, yUV->width * (yUV->height >> 1), 1, fp);
    }
}

void ReleaseYUVImage(YUVImage *yUV)
{
    if (yUV->yPlane)
    {
        free(yUV->yPlane);
        yUV->yPlane = NULL;
        yUV->uvPlane = NULL;
    }
}

NV21 图像旋转

以顺时针旋转 90 度为例,Y 和 UV 两个平面分别从平面左下角进行旋转拷贝,需要注意的是一对 UV 分量作为一个整体进行拷贝,如下所示:

Y00  Y01  Y02  Y03              Y30  Y20  Y10  Y00
Y10  Y11  Y12  Y13    旋转90度   Y31  Y21  Y11  Y01
Y20  Y21  Y22  Y23              Y32  Y22  Y12  Y02
Y30  Y31  Y32  Y33              Y33  Y23  Y13  Y03
                      旋转90度
V00  U00  V01  U01              V10  U10  V00  U00
V10  U10  V11  U11              V11  U11  V01  U01

代码如下:

void RotateYUV(YUVImage *pOriImg, YUVImage *pDstImg, int angle)
{
    int yIndex = 0;
    int uvIndex = 0;
    switch (angle)
    {
    case 90://Y 和 UV 两个平面分别从平面左下角进行纵向拷贝,UV一起转
    {
        // y plane
        for (int i = 0; i < pOriImg->width; i++) {
            for (int j = 0; j < pOriImg->height; j++) {
                *(pDstImg->yPlane + yIndex) = *(pOriImg->yPlane + (pOriImg->height - j - 1) * pOriImg->width + i);
                yIndex++;
            }
        }

        //uv plane
        for (int i = 0; i < pOriImg->width; i += 2) {
            for (int j = 0; j < pOriImg->height / 2; j++) {
                *(pDstImg->uvPlane + uvIndex) = *(pOriImg->uvPlane + (pOriImg->height / 2 - j - 1) * pOriImg->width + i);
                *(pDstImg->uvPlane + uvIndex + 1) = *(pOriImg->uvPlane + (pOriImg->height / 2 - j - 1) * pOriImg->width + i + 1);
                uvIndex += 2;
            }
        }
    }
    break;
    case 180: // 顺时针旋转 180 度时从平面右下角进行横向拷贝,UV一起转
    {
        // y plane
        for (int i = 0; i < pOriImg->height; i++) {
            for (int j = 0; j < pOriImg->width; j++) {
                *(pDstImg->yPlane + yIndex) = *(pOriImg->yPlane + (pOriImg->height - 1 - i) * pOriImg->width + pOriImg->width - 1 - j);
                yIndex++;
            }
        }

        //uv plane
        for (int i = 0; i < pOriImg->height / 2; i++) {
            for (int j = 0; j < pOriImg->width; j += 2) {
                *(pDstImg->uvPlane + uvIndex) = *(pOriImg->uvPlane + (pOriImg->height / 2 - 1 - i) * pOriImg->width + pOriImg->width - 2 - j);
                *(pDstImg->uvPlane + uvIndex + 1) = *(pOriImg->uvPlane + (pOriImg->height / 2 - 1 - i) * pOriImg->width + pOriImg->width - 1 - j);
                uvIndex += 2;
            }
        }
    }
    break;
    case 270:// 顺时针旋转 270 度时从平面右上角进行纵向拷贝
    {
        // y plane
        for (int i = 0; i < pOriImg->width; i++) {
            for (int j = 0; j < pOriImg->height; j++) {
                *(pDstImg->yPlane + yIndex) = *(pOriImg->yPlane + j * pOriImg->width + (pOriImg->width - i - 1));
                yIndex++;
            }
        }

        //uv plane
        for (int i = 0; i < pOriImg->width; i += 2) {
            for (int j = 0; j < pOriImg->height / 2; j++) {
                *(pDstImg->uvPlane + uvIndex + 1) = *(pOriImg->uvPlane + j * pOriImg->width + (pOriImg->width - i - 1));
                *(pDstImg->uvPlane + uvIndex) = *(pOriImg->uvPlane + j * pOriImg->width + (pOriImg->width - i - 2));
                uvIndex += 2;
            }
        }
    }
    break;
    default:
        break;
    }

}

NV21 图片缩放

将 2x2 的 NV21 图缩放成 4x4 的 NV21 ,原数据横向每个像素的 Y 分量向右拷贝 1(放大倍数-1)次,纵向每列元素以列为单位向下拷贝 1(放大倍数-1)次.

Y00 Y01                  Y00 Y00 Y01 Y01
Y10 Y11    缩放4*4        Y00 Y00 Y01 Y01
                         Y10 Y10 Y11 Y11
                         Y10 Y10 Y11 Y11

V00 U00                  V00 U00 V00 U00
                         V00 U00 V00 U00

将 4x4 的 NV21 图缩放成 2x2 的 NV21 ,实际上就是进行采样。

Y00 Y01 Y02 Y03                    
Y10 Y11 Y12 Y13                     Y00 Y02
Y20 Y21 Y22 Y23                     Y20 Y22
Y30 Y31 Y32 Y33
                   缩放为2*2
V00 U00 V01 U01                     V00 U00
V10 U10 V11 U11

代码如下:

void ResizeYUV(YUVImage *pOriImg, YUVImage *pDstImg)
{
    if (pOriImg->width > pDstImg->width)
    {
        //缩小
        int x_scale = pOriImg->width / pDstImg->width;
        int y_scale = pOriImg->height / pDstImg->height;

        for (size_t i = 0; i < pDstImg->height; i++)
        {
            for (size_t j = 0; j < pDstImg->width; j++)
            {
                *(pDstImg->yPlane + i*pDstImg->width + j) = *(pOriImg->yPlane + i * y_scale *pOriImg->width + j * x_scale);
            }
        }

        for (size_t i = 0; i < pDstImg->height / 2; i++)
        {
            for (size_t j = 0; j < pDstImg->width; j += 2)
            {
                *(pDstImg->uvPlane + i*pDstImg->width + j) = *(pOriImg->uvPlane + i * y_scale *pOriImg->width + j * x_scale);
                *(pDstImg->uvPlane + i*pDstImg->width + j + 1) = *(pOriImg->uvPlane + i * y_scale *pOriImg->width + j * x_scale + 1);
            }
        }
    }
    else
    {
        // 放大
        int x_scale = pDstImg->width / pOriImg->width;
        int y_scale = pDstImg->height / pOriImg->height;

        for (size_t i = 0; i < pOriImg->height; i++)
        {
            for (size_t j = 0; j < pOriImg->width; j++)
            {
                int yValue = *(pOriImg->yPlane + i *pOriImg->width + j);
                for (size_t k = 0; k < x_scale; k++)
                {
                    *(pDstImg->yPlane + i * y_scale * pDstImg->width + j  * x_scale + k) = yValue;
                }
            }

            unsigned char  *pOriRow = pDstImg->yPlane + i * y_scale * pDstImg->width;
            unsigned char  *pDstRow = NULL;
            for (size_t l = 1; l < y_scale; l++)
            {
                pDstRow = (pDstImg->yPlane + (i * y_scale + l)* pDstImg->width);
                memcpy(pDstRow, pOriRow, pDstImg->width * sizeof(unsigned char ));
            }
        }

        for (size_t i = 0; i < pOriImg->height / 2; i++)
        {
            for (size_t j = 0; j < pOriImg->width; j += 2)
            {
                int vValue = *(pOriImg->uvPlane + i *pOriImg->width + j);
                int uValue = *(pOriImg->uvPlane + i *pOriImg->width + j + 1);
                for (size_t k = 0; k < x_scale * 2; k += 2)
                {
                    *(pDstImg->uvPlane + i * y_scale * pDstImg->width + j  * x_scale + k) = vValue;
                    *(pDstImg->uvPlane + i * y_scale * pDstImg->width + j  * x_scale + k + 1) = uValue;
                }
            }

            unsigned char  *pOriRow = pDstImg->uvPlane + i * y_scale * pDstImg->width;
            unsigned char  *pDstRow = NULL;
            for (size_t l = 1; l < y_scale; l++)
            {
                pDstRow = (pDstImg->uvPlane + (i * y_scale + l)* pDstImg->width);
                memcpy(pDstRow, pOriRow, pDstImg->width * sizeof(unsigned char ));
            }
        }
    }
}

NV21 图像裁剪

Y00 Y01 Y02 Y03
Y10 Y11 Y12 Y13                         Y22 Y23
Y20 Y21 Y22 Y23                         Y32 Y33
Y30 Y31 Y32 Y33 
                 按照横纵坐标(2,2)
V00 U00 V01 U01
V10 U10 V11 U11                         V11 U11

代码如示:

void CropYUV(YUVImage *pOriImg, int x_offSet, int y_offSet, YUVImage *pDstImg)
{
    // 确保裁剪区域不存在内存越界
    int cropWidth = pOriImg->width - x_offSet;
    cropWidth = cropWidth > pDstImg->width ? pDstImg->width : cropWidth;
    int cropHeight = pOriImg->height - y_offSet;
    cropHeight = cropHeight > pDstImg->height ? pDstImg->height : cropHeight;

    unsigned char  *pOriCursor = NULL;
    unsigned char  *pDstCursor = NULL;

    //crop yPlane
    for (size_t i = 0; i < cropHeight; i++)
    {
        pOriCursor = pOriImg->yPlane + (y_offSet + i) * pOriImg->width + x_offSet;
        pDstCursor = pDstImg->yPlane + i * pDstImg->width;
        memcpy(pDstCursor, pOriCursor, sizeof(unsigned char ) * cropWidth);
    }

    //crop uvPlane
    for (size_t i = 0; i < cropHeight / 2; i++)
    {
        pOriCursor = pOriImg->uvPlane + (y_offSet / 2 + i) * pOriImg->width + x_offSet;
        pDstCursor = pDstImg->uvPlane + i * pDstImg->width;
        memcpy(pDstCursor, pOriCursor, sizeof(unsigned char ) * cropWidth);
    }

}

微信公众号首发,欢迎关注微信公众号:江湖修行,感谢共同进步的你。