龙空技术网

YUV 420P 视频裁剪、拼接

音视频开发老舅 306

前言:

此刻姐妹们对“yuv叠加算法”可能比较讲究,同学们都想要学习一些“yuv叠加算法”的相关资讯。那么小编在网络上收集了一些有关“yuv叠加算法””的相关文章,希望同学们能喜欢,姐妹们快快来了解一下吧!

一、YUV简介

YUV(也称YCbCr)是电视系统所采用的一种颜色编码方法。其中 Y 表示亮度,也就是灰阶值,它是基础信号;U 和 V 表示的则是色度,UV 的作用是描述影像色彩及饱和度,它们用于指定像素的颜色。U和V不是基础信号,它俩都是被正交调制的.

YUV与RGB视频信号相比,最大的优点在于只需占用极少的带宽,YUV只需要占用RGB一半的带宽。

YUV就是利用一个亮度(Y)和两个色差(U,V)来代替传统的RGB三原色,以压缩图像或者视频的数据占有率。比如说,传统的RGB三原色,使用红绿红三原色表示一个像素,每种色占用一个字节(8bit),则RGB表示一个像素就需要83=24bit。但如果使用YUV表示这个像素,假设YUV的采样率为:4:2:0,则对每一个像素来说,亮度Y的采样频率为1,而对于色差U和V,则是每相邻的两个像素各取一个U和V。即对于单个的像素来说,色差U和V的采样频率为亮度的一半,为0.5。举例说,有三个相邻的像素,如果用RGB三原色表示,则共需要占用:833=72bits;但是如果采用YUV(4:2:0)表示,则只需要占用:83(Y)+ 830.5(U)+ 830.5(V)= 36bits。这样,只需要用原来一半的空间,即可表示原来的图像或视频数据,数据率压缩了一倍,而图像的效果基本没发生变化。

YUV 主要的采样格式有YCbCr 4:2:0、YCbCr 4:2:2 和 YCbCr 4:4:4 ,其中 YCbCr 4:2:0是最常用的采样格式。YUV420P 是平面存储格式,YUV420P又分为 I420 和 YV12 两种格式。I420格式和YV12格式的不同处在U平面和V平面的位置不同。在I420格式中,U平面紧跟在Y平面之后,然后才是V平面(即:YUV);但YV12则是相反(即:YVU)。

看上图是一帧画面的顺序排列的6个像素点使用YUV402P采样格式,我们本篇主要讲解I420格式,YV12原理相同。浅蓝色区域是一个像素,绿色、红色,紫色、黄色都分别代表一个像素,在数组中的存储方式是从左到右,从上到下,1080P的图像或者视频则共有1920*1080个像素。

C++音视频开发学习资料点击领取→音视频开发(资料文档+视频教程+面试题)(FFmpeg+WebRTC+RTMP+RTSP+HLS+RTP)

二、YUV420P视频帧裁剪(支持横向和竖向)

横向裁剪会存在X偏移,竖向裁剪会存在Y偏移。(下面提到的帧数据都是存放在数组中)

//假设将一帧图像从中间竖向分割// 参数详解:Src 即为源帧数据,x为裁剪的x偏移,y为裁剪的y偏移,srcWidth 为源视频帧的宽度,desWidth 为裁剪后目标视频帧的宽度,Dst为接收裁剪后视频帧数据的数组void VlcPlayerWidget:: Cut_I420(uint8_t* Src, int x, int y, int srcWidth, int srcHeight, uint8_t* Dst, int desWidth, int desHeight)//图片按位置裁剪  {    int nIndex = 0;    int BPosX = x;     int BPosY = y;      for (int i = 0; i < desHeight; i++)    {        // Dst + desWidth * i  一行一行往下挪        //Src + (srcWidth*BPosY) + BPosX + nIndex   (BPosY 是Y轴偏移量,等于0时,即为高度上无裁剪,因此就是  Src + BPosX + nIndex。加上 nIndex是因为每次挪一行        memcpy(Dst + desWidth * i, Src + (srcWidth*BPosY) + BPosX + nIndex, desWidth);        nIndex += srcWidth;    }    nIndex = 0;    uint8_t *pUSour = Src + srcWidth * srcHeight;    uint8_t *pUDest = Dst + desWidth * desHeight;    // 此处遍历条件为 i < desHeight / 2 ,因为src总大小为3/2,而srcWidth * srcHeight只占2/2是存储Y分量的,所有还有1/2的高度是存储UV的    //YUV420P UV连续存储,先U后V,各占1/4    for (int i = 0; i < (desHeight >> 1); i++)//      {        memcpy(pUDest + (desWidth >> 1) * i, pUSour + ((srcWidth >> 1) * (BPosY >> 1)) + (BPosX >> 1) + nIndex,desWidth >> 1);        nIndex += (srcWidth >> 1);    }    // Y占4/4,U占1/4,V占1/4    nIndex = 0;    uint8_t *pVSour = Src + srcWidth * srcHeight * 5 / 4;    uint8_t *pVDest = Dst + desWidth * desHeight * 5 / 4;    for (int i = 0; i < desHeight >> 1; i++)//      {        memcpy(pVDest + (desWidth >> 1) * i, pVSour + ((srcWidth >> 1) * (BPosY >> 1)) + (BPosX >> 1) + nIndex, desWidth >> 1);        nIndex += (srcWidth >> 1);    }}

如果是YV12格式存储,则交换上面的UV分量即可。

二、YUV420P视频帧竖向拼接

下图比较直观的展示了竖向拼接:

//参数详解:disList存放的是一系列要拼接的帧数据,Dst是拼接好的帧数据void VlcPlayerWidget::connectI420Ver(std::vector<DstData> disList, uint8_t* Dst){    // 依次拷贝N块数据的Y分量    int increaseY = 0;  // Y分量每次递增    for (auto dstData : disList)    {        memcpy(Dst + increaseY, dstData.data, (dstData.dstW * dstData.dstH));        increaseY += (dstData.dstW * dstData.dstH);    }    // 依次拷贝N块数据的U分量    int increaseU = 0;    for (auto dstData : disList)    {       int nIndex = 0;        uint8_t *pUSour = dstData.data + dstData.dstW * dstData.dstH;        uint8_t *pUDest = Dst + increaseY + increaseU;        for (int i = 0; i < dstData.dstH >> 1; i++)        {            memcpy(pUDest + (dstData.dstW >> 1) * i, pUSour + nIndex, dstData.dstW >> 1);            nIndex += (dstData.dstW >> 1);        }        increaseU += (dstData.dstW >> 1) * (dstData.dstH >> 1);    }    // 依次拷贝N块数据的V分量    int increaseV = 0;    for (auto dstData : disList)    {        int nIndex = 0;        uint8_t *pVSour = dstData.data + dstData.dstW * dstData.dstH * 5 / 4;        uint8_t *pVDest = Dst + increaseY + increaseU + increaseV;        for (int i = 0; i < dstData.dstH >> 1; i++)        {            memcpy(pVDest + (dstData.dstW >> 1) * i, pVSour + nIndex, dstData.dstW >> 1);            nIndex += (dstData.dstW >> 1);        }		increaseV += (dstData.dstW >> 1) * (dstData.dstH >> 1);    }}
三、YUV420P视频帧横向拼接

C++音视频开发学习资料点击领取→音视频开发(资料文档+视频教程+面试题)(FFmpeg+WebRTC+RTMP+RTSP+HLS+RTP)

下图比较直观的展示了横向拼接:

//参数详解:disList存放的是一系列要拼接的帧数据,Dst是拼接好的帧数据int nOffY = 0;		// 当前要拼接图像的Y偏移	int nOffX = 0;		// 当前要拼接图像的X偏移	UINT nOff = 0;	int allWidth = 0;	for (auto dstData : disList)	{		allWidth += dstData.dstW;	}	// 拷贝 Y 分量	for (auto dstData : disList)	{		for (int i = 0; i < dstData.dstH; i++)		{			// Y轴偏移(体积),移动到新的点,再弥补上后面几份的宽度, 再加上X 偏移(长度) 可以定位当前要拼接图像的起始点(每段等宽)			nOff = dstData.dstW * nOffY + allWidth * i  + nOffX;			//逐行拷贝				memcpy(Dst + nOff, dstData.data + dstData.dstW * i, dstData.dstW);		}		nOffX += dstData.dstW;	}	// 拷贝 U 分量	int YTotal = 0;	nOffX = 0;	nOffY = 0;	nOff = 0;	int nIndex = 0;	for (auto dstData : disList)	{		YTotal += dstData.dstW * dstData.dstH;	}	for (auto dstData : disList)	{		int nIndex = 0;		for (int i = 0; i < (dstData.dstH) >> 1; i++)		{			nOff = (dstData.dstW >> 1) * (nOffY >> 1) + (allWidth >> 1) * i + (nOffX);			//逐行拷贝			 memcpy(Dst + nOff + YTotal, dstData.data + (dstData.dstW * dstData.dstH) + nIndex, dstData.dstW >> 1);			// 拷贝 V 分量			memcpy(Dst + nOff + YTotal * 5 / 4, dstData.data + (dstData.dstW * dstData.dstH * 5 / 4) + nIndex, dstData.dstW >> 1);			nIndex += (dstData.dstW >> 1);		}		nOffX += dstData.dstW >> 1;	}
四、总结

C++音视频开发学习资料点击领取→音视频开发(资料文档+视频教程+面试题)(FFmpeg+WebRTC+RTMP+RTSP+HLS+RTP)

上面三段代码为核心代码,已经实现了YUV420P的裁剪和拼接,可以实现诸如摄像头流媒体数据的拼接,多视频播放场景的拼接,多人视频聊天实时流的拼接等等。如要实现其他效果,比如缩放,旋转等操作,可以使用 google 提供的第三方库 libyuv去做。

标签: #yuv叠加算法