YUVI420 视频 裁剪 缩放 拷贝

YUV介绍

https://zh.wikipedia.org/wiki/YUV

YUV是一种颜色空间,基于YUV的颜色编码是流媒体的常用编码方式。Y表示流明,U、V表示色度、浓度,这种表达方式起初是为了彩色电视与黑白电视之间的信号兼容。 对于图像每一点,Y确定其亮度,UV确认其彩度。

YUV编码是image/video pipeline的重要组成。比如常用的I420相对于RGB24(RGB三个分量各8个字节)的编码格式,只需要一半的存储容量。在流数据传输时降低了带宽压力。

常用的YUV格式

为节省带宽起见,大多数YUV格式平均使用的每像素位数都少于24位。主要的抽样(subsample)格式有YCbCr 4:2:0、YCbCr 4:2:2、YCbCr 4:1:1和YCbCr 4:4:4。YUV的表示法称为A:B:C表示法:

  • 4:4:4表示完全取样。
  • 4:2:2表示2:1的水平取样,垂直完全采样。
  • 4:2:0表示2:1的水平取样,垂直2:1采样。
  • 4:1:1表示4:1的水平取样,垂直完全采样。

最常用Y:UV记录的比重通常1:1或2:1,DVD-Video是以YUV 4:2:0的方式记录,也就是我们俗称的I420,YUV4:2:0并不是说只有U(即Cb), V(即Cr)一定为0,而是指U:V互相援引,时见时隐,也就是说对于每一个行,只有一个U或者V分量,如果一行是4:2:0的话,下一行就是4:0:2,再下一行是4:2:0...以此类推。至于其他常见的YUV格式有YUY2、YUYV、YVYU、UYVY、AYUV、Y41P、Y411、Y211、IF09、IYUV、YV12、YVU9、YUV411、YUV420等。

I420 的单帧结构示意图如下(Planar 方式):

YUVI420 视频 裁剪 缩放 拷贝_第1张图片

 

这幅图的上面一幅可以看出 Y1、Y2、Y7、Y8 共用 U1 和 V1。后面的线性数组为其存储顺序,可以看出 Y、U 和 V 都是顺序存储的,往外写的时候,先按顺序将 Y 分量写出,然后再根据 U、V 分别将它们依次写出即可。

0.颜色赋值

 

	// 如果是设置灰色, 只需要 Y=灰度值比如54    U=V=128 也就是0x80 即可
	//
	//
	//  Y = 0.299R + 0.587G + 0.114B
	//	U = -0.147R - 0.289G + 0.436B+128
	//	V = 0.615R - 0.515G - 0.100B+128

	//	R = Y + 1.14V
	//	G = Y - 0.39U - 0.58V
	//	B = Y + 2.03U
	//
	//OR
    //	Y = 0.257R + 0.504G + 0.098B + 16
	//  U = 0.148R - 0.291G + 0.439B + 128
	//  V = 0.439R - 0.368G - 0.071B + 128
	//  B = 1.164(Y - 16) + 2.018(U - 128)
	//  G = 1.164(Y - 16) - 0.813(V - 128) - 0.391(U - 128)
	//  R = 1.164(Y - 16) + 1.596(V - 128)

	//
	//
	// 用桌面拾取工具测试时    设置的灰度值 54    拾取的是43   估计Y最后面那个参数是可变的 

详细参考前面文章

 

1 缩放

// Scales a YUV 4:2:0 image from the src width and height to the
// dst width and height.
// If filtering is kFilterNone, a simple nearest-neighbor algorithm is
// used. This produces basic (blocky) quality at the fastest speed.
// If filtering is kFilterBilinear, interpolation is used to produce a better
// quality image, at the expense of speed.
// If filtering is kFilterBox, averaging is used to produce ever better
// quality image, at further expense of speed.
// Returns 0 if successful.

int I420Scale(const uint8* src_y, int src_stride_y,
              const uint8* src_u, int src_stride_u,
              const uint8* src_v, int src_stride_v,
              int src_width, int src_height,
              uint8* dst_y, int dst_stride_y,
              uint8* dst_u, int dst_stride_u,
              uint8* dst_v, int dst_stride_v,
              int dst_width, int dst_height,
              FilterMode filtering);

YUV420  空间分布: Y占4 U 占1 V占1

 

1)示例1 

					ret_data.append(width * height * 3 / 2, (char)0);  //des目的空间 目的宽高  width  height
					uint8_t* src_y = (uint8_t*)pic_info->pdata_;        //soucre内存首地址
					uint8_t* src_u = src_y + src_w * src_h;            //U 首地址
					uint8_t* src_v = src_u + src_w * src_h / 4;        //V 首地址
					uint8_t* des_y = (uint8_t*)ret_data.c_str();       //des 内存首地址
					uint8_t* des_u = des_y + width * height;           
					uint8_t* des_v = des_u + width * height / 4;
					libyuv::FilterMode filter_mode = libyuv::kFilterNone;
					libyuv::I420Scale(src_y, src_w,
						src_u, src_w / 2,
						src_v, src_w / 2,
						src_w, src_h,
						des_y, width,
						des_u, width / 2,
						des_v, width / 2,
						width, height,
						filter_mode);

 

 

2)示例2  将画面缩放到画布右侧, 左侧1/4留白

				if (nim_comp::VideoManager::GetInstance()->video_frame_mng_.GetVideoFrame("", time, (char*)data_temp.c_str(), width, height, false, false))
				{

						//CopyI420Data(capture_type_ == kCaptureTypeCamera ? SIZEALL : SIZENE,
						//	data_ret, data_temp, width, height);
						frame_show = true;




						int src_w = width;
						int src_h = height;

						int desWidth = width_ - width_ / 4;

						std::string data_mid;  //要生成的视频帧数据

						int size_Y = desWidth * height;
						int size_U = desWidth * height / 4;
						int size_V = desWidth * height / 4;

						data_mid.append(size_Y, (char)0x36);
						data_mid.append(size_U, (char)clr_temp);
						data_mid.append(size_V, (char)clr_temp);


						bool bCut = false;
						if (!bCut) //缩放形式
						{


							uint8_t* src_y = (uint8_t*)data_temp.c_str();
							uint8_t* src_u = src_y + src_w * src_h;
							uint8_t* src_v = src_u + src_w * src_h / 4;

							uint8_t* des_y = (uint8_t*)data_mid.c_str();
							uint8_t* des_u = des_y + desWidth * height;
							uint8_t* des_v = des_u + desWidth * height / 4;
							libyuv::FilterMode filter_mode = libyuv::kFilterNone;
							libyuv::I420Scale(
								src_y, src_w,
								src_u, src_w / 2,
								src_v, src_w / 2,
								src_w, src_h,

								des_y, desWidth,
								des_u, desWidth / 2,
								des_v, desWidth / 2,
								desWidth, height,
								filter_mode);



							CopyI420Data(SIZENW,
								data_ret, data_mid, desWidth, height);


						}

				}

 

3) 等比例缩放

       原图等比例缩放内接于画布中

       假设:原图宽  src_w  原图高  src_h

                  画布宽  nCtrlW 画布高  nCtrlH

       假设    调整后的图宽width   高height 

       等比例内接于画面

       width==nCtrlW  或height==nCtrlH

       根据比例调整令一边,使得全部画面都在控件内,且顶满控件

       调整方式

        

			//等比
			if (src_h * nCtrlW > src_w * nCtrlH)
			{
				width = src_w * nCtrlH / src_h;  //高顶满 左右留白
			}
			else
			{
				height= src_h * nCtrlW / src_w;  //宽顶满  上下留白
			}

        

GetVideoFrame(char* out_data, int& width, int& height)
{
			int src_w = pic_info->width_;
			int src_h = pic_info->height_;
			//等比
			if (src_h * nCtrlW > src_w * nCtrlH)
			{
				width = src_w * nCtrlH / src_h; //高顶满 左右留白
			}
			else
			{
				height= src_h * nCtrlW / src_w; //宽顶满  上下留白
			}
			width -= width % 2;
			height -= height % 2;

			std::string ret_data;


			if (width != src_w || height != src_h)
			{


                   //缩放
					ret_data.append(width * height * 3 / 2, (char)0);  //des目的空间 目的宽高  width  height
					uint8_t* src_y = (uint8_t*)pic_info->pdata_;        //soucre内存首地址
					uint8_t* src_u = src_y + src_w * src_h;            //U 首地址
					uint8_t* src_v = src_u + src_w * src_h / 4;        //V 首地址
					uint8_t* des_y = (uint8_t*)ret_data.c_str();       //des 内存首地址
					uint8_t* des_u = des_y + width * height;           
					uint8_t* des_v = des_u + width * height / 4;
					libyuv::FilterMode filter_mode = libyuv::kFilterNone;
					libyuv::I420Scale(src_y, src_w,
						src_u, src_w / 2,
						src_v, src_w / 2,
						src_w, src_h,
						des_y, width,
						des_u, width / 2,
						des_v, width / 2,
						width, height,
						filter_mode);

			}else
            {

                ret_data.append(pic_info->pdata_, pic_info->size_);
            }

       //将数据拷贝到out_data中
        memcpy(out_data, ret_data.c_str(), ret_data.size());

}

 

如果要将数据传递出去

	memcpy(out_data, ret_data.c_str(), ret_data.size());


示例:

    static char clr_temp = 0x80;// ;
	std::string data_temp; //临时数据
	int size_Y = width_ * height_;
	int size_U = width_ * height_/4;
	int size_V = width_ * height_ / 4;

	data_temp.append(size_Y, (char)0x36);
	data_temp.append(size_U, (char)clr_temp);
	data_temp.append(size_V, (char)clr_temp);


    //将原图缩放 到data_temp中
    GetVideoFrame((char*)data_temp.c_str(), width, height)

缩放封装:

   图片缩放进一步封装下

ScaleYUV(const std::string& src, int src_width, int src_height,
	std::string& dest, int dst_width, int dst_height)
{
	uint8_t* src_i420_data = (uint8_t*)src.c_str();
	uint8_t* dst_i420_data = (uint8_t*)dest.c_str();




	int src_i420_y_size = src_width * src_height;
	int src_i420_u_size = (src_width >> 1) * (src_height >> 1);
	uint8 *src_i420_y_data = src_i420_data;
	uint8 *src_i420_u_data = src_i420_data + src_i420_y_size;
	uint8 *src_i420_v_data = src_i420_data + src_i420_y_size + src_i420_u_size;

	int dst_i420_y_size = dst_width * dst_height;
	int dst_i420_u_size = (dst_width >> 1) * (dst_height >> 1);
		uint8 *dst_i420_y_data = dst_i420_data;
		uint8 *dst_i420_u_data = dst_i420_data + dst_i420_y_size;
		uint8 *dst_i420_v_data = dst_i420_data + dst_i420_y_size + dst_i420_u_size;

		libyuv::I420Scale((const uint8 *)src_i420_y_data, src_width,
			(const uint8 *)src_i420_u_data, src_width >> 1,
			(const uint8 *)src_i420_v_data, src_width >> 1,
			src_width, src_height,
			(uint8 *)dst_i420_y_data, dst_width,
			(uint8 *)dst_i420_u_data, dst_width >> 1,
			(uint8 *)dst_i420_v_data, dst_width >> 1,
			dst_width, dst_height,
			(libyuv::FilterMode) libyuv::kFilterNone);
}

等比例缩放优化为:

					if (width==nMainScreenWidth&&height<=nMainScreenHeight||height==nMainScreenHeight&&width<=nMainScreenWidth)
					{
						//居中
						CopyI420Data(SIZECENTER_Main, data_ret, data_temp, width, height);
					}
					else
					{
						//等比例放缩
						float fMainRatio = (nMainScreenWidth*1.0) / nMainScreenHeight; //控件比例
						float fRadio = (width*1.0) / height;                           //实际视频图像比例

						if (fRadio>fMainRatio)
						{
							//实际视频比较宽, 则以宽度为准,求等比例后的高度

							std::string data_scale;
							int nScaleWidth = nMainScreenWidth;                     //宽度为控件宽度
							int nScaleHeight = nScaleWidth / fRadio;                //计算等比例缩放后的 高度

							nScaleWidth -= nScaleWidth % 2;
							nScaleHeight -= nScaleHeight % 2;

							int nScaleSize = nScaleWidth * nScaleHeight * 3 / 2;

							data_scale.append(nScaleSize, (char)clr_temp);        //保存目的图像数据

							ScaleYUV(data_temp, width, height,
								data_scale, nScaleWidth, nScaleHeight);
							CopyI420Data(SIZECENTER_Main, data_ret, data_scale, nScaleWidth, nScaleHeight);


						}
						else{
							//实际视频比较窄,以高度为准,求等比例后的宽度


							std::string data_scale;
							
							int nScaleHeight = nMainScreenHeight;             //高度为控件高度
							int nScaleWidth = nScaleHeight / fRadio;          //等比例调整后的宽度

							nScaleWidth -= nScaleWidth % 2;
							nScaleHeight -= nScaleHeight % 2;

							int nScaleSize = nScaleWidth * nScaleHeight * 3 / 2;

							data_scale.append(nScaleSize, (char)clr_temp);   //保存目的图像数据

							ScaleYUV(data_temp, width, height,
								data_scale, nScaleWidth, nScaleHeight);
							CopyI420Data(SIZECENTER_Main, data_ret, data_scale, nScaleWidth, nScaleHeight);
						}
					}

 

 

 

4) 裁剪充满

      充满控件, 使用的方法是: 最大化选择控件同比例视频内容, 然后,对这部分视频再做缩放操作,充满控件

 

      YUVI420 视频 裁剪 缩放 拷贝_第2张图片

假设画布的宽高比为2:1

那就是在图像上选择2:1的最大区域, 然后将其放缩在画布中,即可实现充满效果

 

YUVI420 视频 裁剪 缩放 拷贝_第3张图片

					int src_width_ = src_w;     //图像宽高
					int src_height_ = src_h;
					int dst_width_ = width;     //画布宽高
					int dst_height_ = height;

					// Making sure that destination frame is of sufficient size.

					// We want to preserve aspect ratio instead of stretching the frame.
					// Therefore, we need to crop the source frame. Calculate the largest center
					// aligned region of the source frame that can be used.
					const int cropped_src_width =
						min(src_width_, dst_width_ * src_height_ / dst_height_);
					const int cropped_src_height =
						min(src_height_, dst_height_ * src_width_ / dst_width_);
					// Make sure the offsets are even to avoid rounding errors for the U/V planes.
					const int src_offset_x = ((src_width_ - cropped_src_width) / 2) & ~1;
					const int src_offset_y = ((src_height_ - cropped_src_height) / 2) & ~1;

 

假设图像比例和画布比例相同

YUVI420 视频 裁剪 缩放 拷贝_第4张图片

 

实际上,大部分是不同的,但选择的区域比例是相同的

YUVI420 视频 裁剪 缩放 拷贝_第5张图片

 

基本值

					//YUV420 image size
					int I420_Y_Size = src_width_ * src_height_;
					int I420_U_Size = src_width_ * src_height_ / 4;
					//    int I420_V_Size = I420_U_Size;

					unsigned char *Y_data_src = (unsigned char *)pic_info->pdata_;
					unsigned char *U_data_src = Y_data_src + I420_Y_Size;
					unsigned char *V_data_src = Y_data_src + I420_Y_Size + I420_U_Size;
					int src_stride_Y = src_width_;
					int src_stride_U = (src_width_ + 1) >> 1;
					int src_stride_V = src_stride_U;

					//最终写入目标
					int dest_I420_Y_Size = dst_width_ * dst_height_;
					int dest_I420_U_Size = dst_width_ * dst_height_ / 4;
					unsigned char *Y_data_dest = (unsigned char *)ret_data.c_str();
					unsigned char *U_data_dest = Y_data_dest + dest_I420_Y_Size;
					unsigned char *V_data_dest = Y_data_dest + dest_I420_Y_Size + dest_I420_U_Size;
					int dest_stride_Y = dst_width_;
					int dest_stride_U = (dst_width_ + 1) >> 1;
					int dest_stride_V = dest_stride_U;

 

 

YUVI420 视频 裁剪 缩放 拷贝_第6张图片

因为我们选择的是原图像部分区域进行缩放,所以需要计算源图像选中区域的相关参数

 

					const uint8_t* y_ptr =
						Y_data_src +
						src_offset_y * src_stride_Y +
						src_offset_x;
					const uint8_t* u_ptr =
						U_data_src +
						src_offset_y / 2 * src_stride_U +
						src_offset_x / 2;
					const uint8_t* v_ptr =
						V_data_src +
						src_offset_y / 2 * src_stride_V +
						src_offset_x / 2;

					libyuv::I420Scale(
						y_ptr,
						src_stride_Y,
						u_ptr,
						src_stride_U,
						v_ptr,
						src_stride_V,
						cropped_src_width, cropped_src_height,
						Y_data_dest,
						dest_stride_Y,
						U_data_dest,
						dest_stride_U,
						V_data_dest,
						dest_stride_V,
						dst_width_, dst_height_,
						libyuv::kFilterNone);

 

YUVI420 视频 裁剪 缩放 拷贝_第7张图片

 

5)拷贝

// Copy I420 to I420.
int I420Copy(const uint8* src_y, int src_stride_y,
             const uint8* src_u, int src_stride_u,
             const uint8* src_v, int src_stride_v,
             uint8* dst_y, int dst_stride_y,
             uint8* dst_u, int dst_stride_u,
             uint8* dst_v, int dst_stride_v,
             int width, int height);
void I420CopyEx(const uint8* src_y, int src_stride_y, const uint8* src_ya, int src_stride_ya,
	const uint8* src_u, int src_stride_u, const uint8* src_ua, int src_stride_ua,
	const uint8* src_v, int src_stride_v, const uint8* src_va, int src_stride_va,
	uint8* dst_y, int dst_stride_y,
	uint8* dst_u, int dst_stride_u,
	uint8* dst_v, int dst_stride_v,
	int width, int height)
{
	if (src_stride_ya == 0 && src_stride_ua == 0 && src_stride_va == 0)
	{
		libyuv::I420Copy(src_y, src_stride_y,
			src_u, src_stride_u,
			src_v, src_stride_v,
			dst_y, dst_stride_y,
			dst_u, dst_stride_u,
			dst_v, dst_stride_v,
			width, height);
	}
	else
	{
       //ARGBTO420 自己做的转换
		for (int ih = 0; ih < height; ++ih)
		{
			bool uv = ih % 2 == 0;
			for (int iw = 0; iw < width / 2; ++iw)
			{
				MixClr(src_y++, dst_y++, *src_ya++);
				MixClr(src_y++, dst_y++, *src_ya++);
				if (uv)
				{
					MixClr(src_u++, dst_u++, *src_ua++);
					MixClr(src_v++, dst_v++, *src_va++);
				}
			}
			src_y += (src_stride_y - width);
			src_ya += (src_stride_ya - width);
			dst_y += (dst_stride_y - width);
			if (uv)
			{
				src_u += (src_stride_u - width / 2);
				src_v += (src_stride_v - width / 2);
				src_ua += (src_stride_ua - width / 2);
				src_va += (src_stride_va - width / 2);
				dst_u += (dst_stride_u - width / 2);
				dst_v += (dst_stride_v - width / 2);
			}
		}
	}
}

 

CopyI420Data(int type, std::string& dest, const std::string& src, int width, int height, ImageYuvDataType image_type, POINT pt)
{
	int nCtrlH = height_;  //目的控件画布的高度   
	int nCtrlW = width_;   //目的控件画布的宽度

	int left = (nCtrlW - width) / 2;
	left -= left % 2;
	int top = (nCtrlH - height) / 2;  
	top -= top % 2;

	if (left < 0)
		left = 0;
	if (top < 0)
		top = 0;
	int stride_y = width;
	int stride_u = width / 2;
	int stride_v = width / 2;
	int stride_ya = 0;
	int stride_ua = 0;
	int stride_va = 0;
	if (image_type == kYuvDataTypeImage || image_type == kYuvDataTypeImageAlpha)
	{
		stride_y *= 2;  //ARGBTO420转换的 跨度不一样
		stride_u *= 2;
		stride_v *= 2;
		if (image_type == kYuvDataTypeImageAlpha)
		{
			stride_ya = stride_y;
			stride_ua = stride_u;
			stride_va = stride_v;
		}
	}
	uint8_t* src_y = (uint8_t*)src.c_str();
	uint8_t* src_u = src_y + stride_y * height;
	uint8_t* src_v = src_u + stride_u * height / 2;
	uint8_t* des_y = (uint8_t*)dest.c_str();
	uint8_t* des_u = des_y + nCtrlW * nCtrlH;
	uint8_t* des_v = des_u + nCtrlW * nCtrlH / 4;
	des_y += left + top * nCtrlW;
	des_u += left / 2 + top * nCtrlW / 4;
	des_v += left / 2 + top * nCtrlW / 4;
	I420CopyEx(src_y, stride_y, src_y + width, stride_ya,
		src_u, stride_u, src_u + width / 2, stride_ua,
		src_v, stride_v, src_v + width / 2, stride_va,
		des_y, nCtrlW,
		des_u, nCtrlW / 2,
		des_v, nCtrlW / 2,
		width, height);
}

 

6) 区域拷贝

  由原图的某位置 拷贝到目标图指定位置 指定大小

void ScreenCaptureTool::CutCopyI420Data(std::string& dest, int dest_xPos, int dest_yPos, const std::string& src, int src_xPos, int src_yPos, int srcWidth, int srcHeight, int cutWidth, int cutHeight, ImageYuvDataType image_type/* = kYuvDataTypeNormal*/)
{
	int nCtrlH = height_;  //目的控件画布的高度   
	int nCtrlW = width_;   //目的控件画布的宽度



	int stride_y = srcWidth;
	int stride_u = srcWidth / 2;
	int stride_v = srcWidth / 2;
	int stride_ya = 0;
	int stride_ua = 0;
	int stride_va = 0;
	if (image_type == kYuvDataTypeImage || image_type == kYuvDataTypeImageAlpha)
	{
		stride_y *= 2;  //ARGBTO420转换的 跨度不一样
		stride_u *= 2;
		stride_v *= 2;
		if (image_type == kYuvDataTypeImageAlpha)
		{
			stride_ya = stride_y;
			stride_ua = stride_u;
			stride_va = stride_v;
		}
	}
	uint8_t* src_y = (uint8_t*)src.c_str();
	uint8_t* src_u = src_y + stride_y * srcHeight;
	uint8_t* src_v = src_u + stride_u * srcHeight / 2;
	uint8_t* des_y = (uint8_t*)dest.c_str();
	uint8_t* des_u = des_y + nCtrlW * nCtrlH;
	uint8_t* des_v = des_u + nCtrlW * nCtrlH / 4;







	src_y += src_xPos + src_yPos * srcWidth;
	src_u += src_xPos / 2 + src_yPos * srcWidth / 4;
	src_v += src_xPos / 2 + src_yPos * srcWidth / 4;


	des_y += dest_xPos + dest_yPos * nCtrlW;
	des_u += dest_xPos / 2 + dest_yPos * nCtrlW / 4;
	des_v += dest_xPos / 2 + dest_yPos * nCtrlW / 4;
	I420CopyEx(src_y, stride_y, src_y + srcWidth, stride_ya,
		src_u, stride_u, src_u + srcWidth / 2, stride_ua,
		src_v, stride_v, src_v + srcWidth / 2, stride_va,
		des_y, nCtrlW,
		des_u, nCtrlW / 2,
		des_v, nCtrlW / 2,
		cutWidth, cutHeight);
}

 

示例:

			if (bMain)
			{   
				//裁剪绘制
				CutCopyI420Data(data_ret, 0, 0, data_temp, 0, 0, 1280, 720, 1040, 720);
			}
			
			if (bSec)
			{
				//裁剪绘制
				CutCopyI420Data(data_ret, 1040, 0, data_temp, 1040, 0, 1280, 720, 240, 180);
			}

 

你可能感兴趣的:(YUVI420 视频 裁剪 缩放 拷贝)