首页 > 其他分享 >【YUV】YUV图像基本处理

【YUV】YUV图像基本处理

时间:2024-02-29 23:55:24浏览次数:12  
标签:pDstImg yPlane 处理 YUV pSrcImg width uvPlane 图像 height

YUV简介

YUV是一种色彩编码模型,也叫YCbCr,Y:明亮度 U(Chromosnnance):色度 V:浓度(Chroma)

YUV编码模型的图像一般不能直接用于显示,需要转换为RGB(RGBA)编码模型,才能够正常显示图像

2.YUV的几种采样方式

image

YUV图像主流的采样方式:

  • YUV 4:4:4: 每一个 Y 分量对于一对 UV 分量,每像素占用 (Y + U + V = 8 + 8 + 8 = 24bits)3 字节;
  • YUV 4:2:2: 每两个 Y 分量共用一对 UV 分量,每像素占用 (Y + 0.5U + 0.5V = 8 + 4 + 4 = 16bits)2 字节;
  • YUV 4:2:0: 每四个 Y 分量共用一对 UV 分量,每像素占用 (Y + 0.25U + 0.25V = 8 + 2 + 2 = 12bits)1.5 字节。

其中最常用的采样方式是 YUV422 和 YUV420 。

YUV 格式也可按照 YUV 三个分量的组织方式分为打包(Packed)格式和平面格式(Planar)

  • 打包(Packed)格式:每个像素点的 YUV 分量是连续交叉存储的,如 YUYV 、NV21 格式;
  • 平面格式(Planar):YUV 图像数据的三个分量分别存放在不同的矩阵中,这种格式适用于采样,如 YV12、YU12 格式。

2.1YUV的几种常见的格式

对 YUV 图像处理中,YUYV 、YU12(I420)、NV21 和 NV12 最为常用,下面介绍下这几种格式的存储方式。

以一幅分辨率为 4x4 的 YUV 图为例,说明在不同 YUV 格式下的存储方式(括号内范围表示内存地址索引范围,默认以下不同格式图片存储使用的都是连续内存)。

2.1.1YUYV(YUV422采样方式)

YUYV 是 2 个Y 分量共用一对 UV 分量,YUYV 格式的存储格式:

(0 ~ 7) Y00 U00 Y01 V00 Y02 U01 Y03 V01
(8 ~ 15) Y10 U10 Y11 V10 Y12 U11 Y13 V11
(16 ~ 23) Y20 U20 Y21 V20 Y22 U21 Y23 V21
(24 ~ 31) Y30 U30 Y31 V30 Y32 U31 Y33 V31

一幅 720P (1280x720分辨率) 的图片,使用 YUV422 采样时占用存储大小为:

Y 分量:1280 * 720 = 921600 字节
U 分量:1280 * 720 * 0.5 = 460800 字节
V 分量:1280 * 720 * 0.5 = 460800 字节
总大小:Y 分量 + U 分量 + V 分量 = (1280 * 720 + 1280 * 720 * 0.5 * 2) / 1024 / 1024 = 1.76 MB

由上面计算可以看出 YUV422 采样的图像比 RGB 模型图像节省了 1/3 的存储空间。,在传输时占用的带宽也会随之减小。

2.1.2 YV12/YU12(YUV420采样方式)

YV12/YU12 也属于 YUV420P ,即 YUV420 采样方式的平面模式,YUV 三个分量分别存储于 3 个不同的矩阵(平面)。

image

YV12格式的存储方式

(0 ~ 3) Y00 Y01 Y02 Y03
(4 ~ 7) Y10 Y11 Y12 Y13
(8 ~ 11) Y20 Y21 Y22 Y23
(12 ~ 15) Y30 Y31 Y32 Y33

(16 ~ 17) V00 V01
(18 ~ 19) V10 V11

(20 ~ 21) U00 U01
(22 ~ 23) U10 U11

YU12(也称 I420) 格式的存储方式

(0 ~ 3) Y00 Y01 Y02 Y03
(4 ~ 7) Y10 Y11 Y12 Y13
(8 ~ 11) Y20 Y21 Y22 Y23
(12 ~ 15) Y30 Y31 Y32 Y33

(16 ~ 17) U00 U01
(18 ~ 19) U10 U11

(20 ~ 21) V00 V01
(22 ~ 23) V10 V11

一幅 720P (1280x720分辨率) 的图片,使用 YUV420 采样时(格式 YV12/YU12 )占用存储大小为:

Y 分量:1280 * 720 = 921600 字节
U 分量:1280 * 720 * (1/4) = 230400 字节
V 分量:1280 * 720 * (1/4) = 230400 字节
总大小:Y 分量 + U 分量 + V 分量 = (1280 * 720 + 1280 * 720 * (1/4)* 2) / 1024 / 1024 = 1.32 MB

YUV420 采样(格式 YV12/YU12 )的图像比 RGB 模型图像节省了 1/2 的存储空间。

2.1.3 NV12、NV21(YUV420采样方式)

NV21/NV12 属于 YUV420SP ,YUV420SP 格式有 2 个平面,Y 分量存储于一个平面,UV 分量交错存储于另一个平面。

1709221411889

NV21格式的存储方式

(0 ~ 3) Y00 Y01 Y02 Y03
(4 ~ 7) Y10 Y11 Y12 Y13
(8 ~ 11) Y20 Y21 Y22 Y23
(12 ~ 15) Y30 Y31 Y32 Y33

(16 ~ 19) V00 U00 V01 U01
(20 ~ 23) V10 U10 V11 U11

NV12格式的存储方式

(0 ~ 3) Y00 Y01 Y02 Y03
(4 ~ 7) Y10 Y11 Y12 Y13
(8 ~ 11) Y20 Y21 Y22 Y23
(12 ~ 15) Y30 Y31 Y32 Y33

(16 ~ 19) U00 V00 U01 V01
(20 ~ 23) U10 V10 U11 V11

NV21 与 NV12 格式的区别仅在于 UV 分量排列的先后顺序不同。

3.YUV图像的基本操作

下面以最常用的 NV21 图为例介绍其旋转、缩放和剪切的基本方法。

3.1 YUV 图片的定义、加载、保存及内存释放:

//YUV420SP  NV21 or NV12

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

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

void SaveYUVImage(const char *filePath, YUVImage *pImage)
{
    FILE *fp = fopen(filePath, "wb+");
    if (fp)
    {
        fwrite(pImage->yPlane, pImage->width * pImage->height, 1, fp);
        fwrite(pImage->uvPlane, pImage->width * (pImage->height >> 1), 1, fp);
    }
}

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

3.2 NV12图片旋转

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

以此类比,顺时针旋转 180 度时从平面右下角进行横向拷贝,顺时针旋转 270 度时从平面右上角进行纵向拷贝。

1709221531781

1709221536178

存储空间表示:

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

代码实现:

//angle 90,  270, 180
void RotateYUVImage(YUVImage *pSrcImg, YUVImage *pDstImg, int angle)
{
    int yIndex = 0;
    int uvIndex = 0;
    switch (angle)
    {
    case 90:
    {
        // y plane
        for (int i = 0; i < pSrcImg->width; i++) {
            for (int j = 0; j < pSrcImg->height; j++) {
                *(pDstImg->yPlane + yIndex) = *(pSrcImg->yPlane + (pSrcImg->height - j - 1) * pSrcImg->width + i);
                yIndex++;
            }
        }

        //uv plane
        for (int i = 0; i < pSrcImg->width; i += 2) {
            for (int j = 0; j < pSrcImg->height / 2; j++) {
                *(pDstImg->uvPlane + uvIndex) = *(pSrcImg->uvPlane + (pSrcImg->height / 2 - j - 1) * pSrcImg->width + i);
                *(pDstImg->uvPlane + uvIndex + 1) = *(pSrcImg->uvPlane + (pSrcImg->height / 2 - j - 1) * pSrcImg->width + i + 1);
                uvIndex += 2;
            }
        }
    }
    break;
    case 180:
    {
        // y plane
        for (int i = 0; i < pSrcImg->height; i++) {
            for (int j = 0; j < pSrcImg->width; j++) {
                *(pDstImg->yPlane + yIndex) = *(pSrcImg->yPlane + (pSrcImg->height - 1 - i) * pSrcImg->width + pSrcImg->width - 1 - j);
                yIndex++;
            }
        }


        //uv plane
        for (int i = 0; i < pSrcImg->height / 2; i++) {
            for (int j = 0; j < pSrcImg->width; j += 2) {
                *(pDstImg->uvPlane + uvIndex) = *(pSrcImg->uvPlane + (pSrcImg->height / 2 - 1 - i) * pSrcImg->width + pSrcImg->width - 2 - j);
                *(pDstImg->uvPlane + uvIndex + 1) = *(pSrcImg->uvPlane + (pSrcImg->height / 2 - 1 - i) * pSrcImg->width + pSrcImg->width - 1 - j);
                uvIndex += 2;
            }
        }
    }
    break;
    case 270:
    {
        // y plane
        for (int i = 0; i < pSrcImg->width; i++) {
            for (int j = 0; j < pSrcImg->height; j++) {
                *(pDstImg->yPlane + yIndex) = *(pSrcImg->yPlane + j * pSrcImg->width + (pSrcImg->width - i - 1));
                yIndex++;
            }
        }

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

3.3 NV12图片缩放

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

1709221597357

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

1709221607411

代码实现:

void ResizeYUVImage(YUVImage *pSrcImg, YUVImage *pDstImg)
{
    if (pSrcImg->width > pDstImg->width)
    {
        //缩小
        int x_scale = pSrcImg->width / pDstImg->width;
        int y_scale = pSrcImg->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) = *(pSrcImg->yPlane + i * y_scale *pSrcImg->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) = *(pSrcImg->uvPlane + i * y_scale *pSrcImg->width + j * x_scale);
                *(pDstImg->uvPlane + i*pDstImg->width + j + 1) = *(pSrcImg->uvPlane + i * y_scale *pSrcImg->width + j * x_scale + 1);
            }
        }
    }
    else
    {
        // 放大
        int x_scale = pDstImg->width / pSrcImg->width;
        int y_scale = pDstImg->height / pSrcImg->height;

        for (size_t i = 0; i < pSrcImg->height; i++)
        {
            for (size_t j = 0; j < pSrcImg->width; j++)
            {
                int yValue = *(pSrcImg->yPlane + i *pSrcImg->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  *pSrcRow = 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, pSrcRow, pDstImg->width * sizeof(unsigned char ));
            }
        }

        for (size_t i = 0; i < pSrcImg->height / 2; i++)
        {
            for (size_t j = 0; j < pSrcImg->width; j += 2)
            {
                int vValue = *(pSrcImg->uvPlane + i *pSrcImg->width + j);
                int uValue = *(pSrcImg->uvPlane + i *pSrcImg->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  *pSrcRow = 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, pSrcRow, pDstImg->width * sizeof(unsigned char ));
            }
        }
    }
}

3.4NV12图片裁剪

图例中将 6x6 的 NV21 图按照横纵坐标偏移量为(2,2)裁剪成 4x4 的 NV21 图。

1709221646534

1709221650733

代码实现:

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


    unsigned char  *pSrcCursor = NULL;
    unsigned char  *pDstCursor = NULL;


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


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

3.5 测试代码

void main()
{
    YUVImage srcImg = { 0 };
    srcImg.width = 840;
    srcImg.height = 1074;
    LoadYUVImage("IMG_840x1074.NV21", &srcImg);

    YUVImage rotateDstImg = { 0 };
    rotateDstImg.width = 1074;
    rotateDstImg.height = 840;
    rotateDstImg.yPlane = malloc(rotateDstImg.width * rotateDstImg.height*1.5);
    rotateDstImg.uvPlane = rotateDstImg.yPlane + rotateDstImg.width * rotateDstImg.height;

    RotateYUVImage(&srcImg, &rotateDstImg, 270);

    SaveYUVImage("D:\\material\\IMG_1074x840_270.NV21", &rotateDstImg);

    RotateYUVImage(&srcImg, &rotateDstImg, 90);

    SaveYUVImage("D:\\material\\IMG_1074x840_90.NV21", &rotateDstImg);

    rotateDstImg.width = 840;
    rotateDstImg.height = 1074;
    RotateYUVImage(&srcImg, &rotateDstImg, 180);

    SaveYUVImage("D:\\material\\IMG_840x1074_180.NV21", &rotateDstImg);

    YUVImage resizeDstImg = { 0 };
    resizeDstImg.width = 420;
    resizeDstImg.height = 536;
    resizeDstImg.yPlane = malloc(resizeDstImg.width * resizeDstImg.height*1.5);
    resizeDstImg.uvPlane = resizeDstImg.yPlane + resizeDstImg.width * resizeDstImg.height;

    ResizeYUVImage(&srcImg, &resizeDstImg);

    SaveYUVImage("D:\\material\\IMG_420x536_Resize.NV21", &resizeDstImg);

    YUVImage cropDstImg = { 0 };
    cropDstImg.width = 300;
    cropDstImg.height = 300;
    cropDstImg.yPlane = malloc(cropDstImg.width * cropDstImg.height*1.5);
    cropDstImg.uvPlane = cropDstImg.yPlane + cropDstImg.width * cropDstImg.height;

    CropYUVImage(&srcImg, 100, 500, &cropDstImg);

    SaveYUVImage("D:\\material\\IMG_300x300_crop.NV21", &cropDstImg);

    ReleaseYUVImage(&srcImg);
    ReleaseYUVImage(&rotateDstImg);
    ReleaseYUVImage(&resizeDstImg);
    ReleaseYUVImage(&cropDstImg);
}

转载文章:

https://zhuanlan.zhihu.com/p/683452602?utm_campaign=shareopn&utm_medium=social&utm_oi=909142489515040768&utm_psn=1744642023649378304&utm_source=wechat_session

标签:pDstImg,yPlane,处理,YUV,pSrcImg,width,uvPlane,图像,height
From: https://www.cnblogs.com/Wangzx000/p/18045719

相关文章

  • selenium处理iframe
    -如果定位的标签在iframe中,需要先切换到iframe中-切换到iframe:driver.switch_to.frame('iframe的id属性值')-动作链(拖动):fromselenium.webdriverimportActionChains-实例化一个动作链对象-执行一系列的动作链操作-调用perform()方法执行链中的所......
  • 【学习笔记】《综述图论中连通性及相关问题的一些处理方法》
    2023集训队论文第一篇。发现好像存在很多我不会/见过但是从来没记住过的结论之类的,所以这篇主要是背结论用的。目录无向图双连通性点双连通分量的性质耳分解割空间与环空间有向图可达性问题强连通性有向环竞赛图记\(u\rightsquigarrowv\)为\(u\)到\(v\)的路径,\(u\t......
  • 【JAVA】百度AI接入api使用流程-【黑图像上色】【步骤2】
    前言:根据API文档中java代码,使用idea编辑代码22.1进入网页,找到java代码https://cloud.baidu.com/doc/IMAGEPROCESS/s/Bk3bclns3 2.2新建java项目     2.3创建java类命名为 Colourize(就是刚才在网页里看到的Java代码的类名)  复制java代码  ......
  • 杜教筛——亚线性处理数论函数求和
    问题引入给定一个数论函数\(f(x)\),求\(\sum\limits_{i=1}^nf(i)\)。对\(n\le10^7\)甚至\(n\le10^8\)都是好做的,\(\mathcalO(n)\)解决即可,但如果\(n<2^{31}\)呢?这就需要亚线性时间复杂度的算法,杜教筛就是其一。杜教筛杜教筛是一种能在幂时间\(\mathcalO(......
  • servlet表单处理
    1.表单的语法知识各种组件的标签以及标签中的属性其中name属性很重要,以后用Servlet来处理表单数据就是通过这个属性来获取表单组件的数据的 密码是不能显示出来的name相同的radio就是一组,只有一个能被选中select中加上multiple就是多选了,可以结合键盘使用来选择2.......
  • 【JAVA】百度AI接入api使用流程-【黑图像上色】【步骤1】
    前言以【黑白图像上色】为例讲解百度AI接口使用,方便新手小白接入,以超级简单的方式操作百度AI库使用步骤1.创建应用获取AK(APIKey),SK(SecretKey)1.1进入: 百度AI官网,在开放能力下面找到:黑白图像上色 1.2选择:立即使用 1.3在创建新应用下,填写相应的信息。注意:接口选择......
  • 17种无量纲化处理的方式
    在进行数据分析时,数据无量纲化处理是一个关键步骤,通过合理地选择和应用无量纲化方法,可以使数据更加规范化和标准化,从而提高数据分析的准确性和可靠性。本文将介绍数据无量纲化的基本概念、常用方法的无量纲化处理方式、软件操作方法以及17种无量纲处理方法的简单说明。一、无量......
  • kubelet 证书过期处理
    现象:执行opensslx509-in/var/lib/kubelet/pki/kubelet-client-current.pem-noout-text|grep'Not' 提示时间已经过期了,节点Notready处理方案:#master节点kubelet证书生成1、移除 .conf文件和kubelet文件rm-rf/etc/kubernetes/*.confrm/var/lib/kubelet/pki/kubel......
  • Python 机器学习 决策树 文本特征的处理
    ​Python机器学习中,决策树是一种常用的分类和回归模型。决策树可以处理数值型特征和类别型特征。对于文本特征,决策树通常使用词袋模型(BOW)或TF-IDF模型进行处理。在处理文本特征时,决策树(和机器学习算法通常)不能直接处理原始文本。文本必须首先转换成算法能理解的数值形式。......
  • 视觉处理程序
    视觉处理程序图像的通道OpenCV中图像的通道可以是1、2、3和4。其中常见的是1通道和3通道,2通道和4通道不常见。1通道的是灰度图。2通道的图像是RGB555和RGB565。2通道图在程序处理中会用到,如傅里叶变换,可能会用到,一个通道为实数,一个通道为虚数,主要是编程方便。RGB555是16位的......