10、YUV 简介及使用

10、YUV 简介及使用一 YUV 简介 YUV 是一种颜色编码方法 常使用在各个视频处理组件中 Y UV YCbCr YPbPr 等专有名词都可以称为 YUV 彼此有重叠 Y 表示明亮度 单取此通道即可得灰度图 U 和 V 则是色

大家好,欢迎来到IT知识分享网。


一、YUV 简介

在这里插入图片描述

  • YUV:是一种颜色编码方法,常使用在各个视频处理组件中
    • Y'UV(模拟), YCbCr(数字), YPbPr等专有名词都可以称为 YUV,彼此有重叠
    • Y表示明亮度(单取此通道即可得灰度图),UV则是描述图像的色彩饱和度,用于指定像素的颜色
    • 编解码:采集到的视频数据一般是 RGB24,为了节省带宽,一般需要经过 编码转换(RGB2YUV) 为 NV12 进行传输;应用时一般需要经过 解码转换(YUV2RGB) 为 RGB 用于显示或后续算法
  • YUV 采样方式及原理:根据人眼的特点,将人眼相对不敏感的色彩信息进行压缩采样(亮度保持不变),得到相对小的文件进行播放和传输
    • YUV4:2:0 数据,每四个 Y 共用一组 UV 分量,在内存中的长度是 h * w + h * w / 4 + h * w / 4 = h * w *1.5,是 RGB(h * w * 3) 格式视频数据内存的一半,每个像素的 Y 数据保留, 两个像素数据只保留一个 U 或者 V 数据
    • YUV4:2:2 数据,每两个 Y 共用一组 UV 分量,在内存中的长度是 h * w + h * w / 2 + h * w / 2 = h * w *2,是 RGB(h * w * 3) 格式视频数据内存 2/3,每两个相邻的像素,一个丢弃 V 数据,一个丢弃 U 数据
    • YUV4:4:4 数据,每一个 Y 共用一组 UV 分量,在内存中的长度是 h * w + h * w + h * w = h * w *3,与 RGB(h * w * 3) 格式视频数据内存一样
  • YUV 存储格式:
    • packed(打包格式):每个像素点的 Y,U,V 是连续交叉存储的(YUVYUVYUVYUV
    • planar(平面格式):先连续存储所有像素点的 Y,紧接着存储所有像素点的 U,随后是所有像素点的V(YYYYUUVV
    • semi-planar(半平面格式):先连续存储所有像素点的 Y,紧接着连续交叉存储所有像素点的U,V(YYYYUV
  • YUV444 & RGB 相互转换:
    • 图形显示时常用 RGB 模型,而 YUV 常用在数据传输场景,所以这两种颜色模型之间经常需要进行转换
    • 可以根据其采样格式来从码流中还原每个像素点的 YUV 值,进而通过 YUVRGB 的转换公式提取出每个像素点的 RGB 值,然后显示出来(可参考博客:YUV 采样与恢复)
  • YUV 转 RGB:输入图像的宽度和高度必须为偶数;输入图像的 y 和 uv planestride 必须相同
// BT601(SDTV)/BT709(HDTV)/BT2020(UHDTV) 定义了 RGB 和 YUV 互转的色域标准规范 // BT2020 的 YUV 转换公式和 BT601 BT709 都是不同的,但是两者的取值范围是相同的,一般 HD 视频(1080p)使用 BT601  // 转换, UHDTV(4k或8K)使用 BT2020 协议转换, 若视频信号中无色域标志或者色彩转换矩阵, 默认使用 BT601 即可,  // ffmpeg 转 rgb 也是这样操作的。 // 参考:https://www.zhihu.com/question//answer/ #define ROUND(f) static_cast<int>(f + 0.5f) static int RoundToByte(float f) { 
    int i = ROUND(f); return (i < 0) ? 0 : ((i > 255) ? 255 : i); } // BT.601 limited range YUV to RGB reference // RGB 范围 [0,255],Y 范围 [16,235] ,UV 范围 [16,239]。如果计算结果超过了取值范围要进行截取 R = RoundToByte(1.164 * (Y - 16) + 1.596 * (V - 128)); G = RoundToByte(1.164 * (Y - 16) - 0.391 * (U - 128) - 0.813 * (V - 128)); B = RoundToByte(1.164 * (Y - 16) + 2.018 * (U - 128)); // BT.601 full range YUV to RGB reference (aka JPEG) // RGB 的范围是 [0,255],Y 和 UV 的范围是[0,255]。如果计算结果超过了取值范围要进行截取 R = RoundToByte(Y + 1.40200 * (V - 128)); G = RoundToByte(Y - 0.34414 * (U - 128) - 0.71414 * (V - 128)); B = RoundToByte(Y + 1.77200 * (U - 128)); // BT.709 limited range YUV to RGB reference R = RoundToByte(1.164 * (Y - 16) + 1.793 * (V - 128)); G = RoundToByte(1.164 * (Y - 16) - 0.213 * (U - 128) - 0.533 * (V - 128)); B = RoundToByte(1.164 * (Y - 16) + 2.112 * (U - 128)); // BT.709 full range YUV to RGB reference R = RoundToByte(Y + 1.57480 * (V - 128)); G = RoundToByte(Y - 0.18732 * (U - 128) - 0.46812 * (V - 128)); B = RoundToByte(Y + 1.85560 * (U - 128)); // BT.2020 limited range YUV to RGB reference R = RoundToByte(1. * (Y - 16) + 1. * (V - 128)); G = RoundToByte(1. * (Y - 16) - 0. * (U - 128) - 0. * (V - 128)); B = RoundToByte(1. * (Y - 16) + 2. * (U - 128)); // BT.2020 full range YUV to RGB reference R = RoundToByte(Y + 1. * (V - 128)); G = RoundToByte(Y - 0. * (U - 128) - 0. * (V - 128)); B = RoundToByte(Y + 1. * (U - 128)); 
  • RGB 转 YUV:输入图像的宽度和高度必须为偶数
#define ROUND(f) static_cast<int>(f + 0.5f) static int RoundToByte(float f) { 
    int i = ROUND(f); return (i < 0) ? 0 : ((i > 255) ? 255 : i); } // RGB to YCbCr(YUV444),YCbCr 是 YUV 颜色空间的偏移版本,适用于数字视频 // RGB 范围 [0,255],Y 范围 [16,235] ,UV 范围 [16,239]。如果计算结果超过了取值范围要进行截取。 // BT.601 limited range RGB to YUV reference Y = RoundToByte(0.257 * R + 0.504 * G + 0.098 * B + 16); U = RoundToByte(-0.148 * R - 0.291 * G + 0.439 * B + 128); V = RoundToByte(0.439 * R - 0.368 * G - 0.071 * B + 128); // BT.601 full range RGB to YUV reference (aka JPEG) Y = RoundToByte(0.299 * R + 0.587 * G + 0.114 * B); U = RoundToByte(-0.169 * R - 0.331 * G + 0.500 * B + 128); V = RoundToByte(0.500 * R - 0.419 * G - 0.081 * B + 128); // BT.709 limited range RGB to YUV reference Y = RoundToByte(0.183 * R + 0.614 * G + 0.062 * B + 16); U = RoundToByte(-0.101 * R - 0.339 * G + 0.439 * B + 128); V = RoundToByte(0.439 * R - 0.399 * G - 0.040 * B + 128); // BT.709 full range RGB to YUV reference (aka JPEG) Y = RoundToByte(0.213 * R + 0.715 * G + 0.072 * B); U = RoundToByte(-0.115 * R - 0.385 * G + 0.500 * B + 128); V = RoundToByte(0.500 * R - 0.454 * G - 0.046 * B + 128); // OpenCV 读取 BGR 图像转换成 YUV444  for(l32Index1 = 0; l32Index1 < cv_img.rows; l32Index1++) { 
    for(l32Index2 = 0; l32Index2 < cv_img.cols; l32Index2++) { 
    # Y = 0.257*R + 0.504*G + 0.098*B + 16 f32YTmp = 0.257 * (int)(cv_img.at<cv::Vec3b>(l32Index1, l32Index2)[2]) + 0.504 * (int)(cv_img.at<cv::Vec3b>(l32Index1, l32Index2)[1]) + 0.098 * (int)(cv_img.at<cv::Vec3b>(l32Index1, l32Index2)[0]) + 16; u8YTmp = MIN(256, (uchar)(f32YTmp + 0.5f)); cv_img_yuv.at<cv::Vec3b>(l32Index1, l32Index2)[0] = u8YTmp; # U = -0.148*R - 0.291*G + 0.439*B + 128 f32UTmp = -0.148 * (int)(cv_img.at<cv::Vec3b>(l32Index1, l32Index2)[2]) - 0.291 * (int)(cv_img.at<cv::Vec3b>(l32Index1, l32Index2)[1]) + 0.439 * (int)(cv_img.at<cv::Vec3b>(l32Index1, l32Index2)[0]) + 128; u8UTmp = (uchar)(f32UTmp + 0.5f); cv_img_yuv.at<cv::Vec3b>(l32Index1, l32Index2)[1] = u8UTmp; # V = 0.439*R - 0.368*G - 0.071*B + 128 f32VTmp = 0.439 * (int)(cv_img.at<cv::Vec3b>(l32Index1, l32Index2)[2]) - 0.368 * (int)(cv_img.at<cv::Vec3b>(l32Index1, l32Index2)[1]) - 0.071 * (int)(cv_img.at<cv::Vec3b>(l32Index1, l32Index2)[0]) + 128; u8VTmp = (uchar)(f32VTmp + 0.5f); cv_img_yuv.at<cv::Vec3b>(l32Index1, l32Index2)[2] = u8VTmp; } } 

二、YUV 采样格式介绍

在这里插入图片描述

2.1、YUV420(NV12、NV21、I420、YV12)

# NV12、NV21 的存储格式为 Y 平面,UV 打包一个平面,共两个平面,即:先连续存储 Y,然后连续交叉存储 UV # 不同点在于 UV 的排列顺序,SP 为 Semi-Planar 的缩写 # NV12:先是 w * h 长度的 Y,后面跟 w * h * 0.5 长度的 UV(交叉存储),总长度为 w * h * 1.5 # NV21:先是 w * h 长度的 Y,后面跟 w * h * 0.5 长度的 VU(交叉存储),总长度为 w * h * 1.5 NV12: YYYYYYYY UVUV => YUV420SP # iOS 平台常用 NV12 NV21: YYYYYYYY VUVU => YUV420SP # Android 平台常用 NV21 # I420、YV12 三个分量均为平面格式,共三个平面,即:先连续存储 Y,然后连续存储 U,最后连续存储 V # 不同点在于 U V 的排列顺序,P 为 Planar 的缩写 # I420:先是 w * h 长度的 Y,后面跟 w * h * 0.25 长度的 U, 最后是 w * h * 0.25 长度的 V,总长度为 w * h * 1.5 # YV12:先是 w * h 长度的 Y,后面跟 w * h * 0.25 长度的 V, 最后是 w * h * 0.25 长度的 U,总长度为 w * h * 1.5 I420: YYYYYYYY UU VV => YUV420P # Android 平台常用 I420 YV12: YYYYYYYY VV UU => YUV420P 
  • 假设一个分辨率为6X4YUV 图像(占用内存大小 6 ∗ 4 ∗ 1.5 = 36 6*4*1.5=36 641.5=36),它们的格式如下图:

在这里插入图片描述

2.1.1、NV12/NV21 转 BGR/RGB HWC
  • 转换的时候指定转换标准,默认是 BT601 limited 会稍微亮一点,如果想要暗部细节体现,可以使用BT601 full,结构体指定不一样可以转换成 BGR/RGB
    在这里插入图片描述
// 1、NV12ToRGB24(BGRHWC)和 NV21ToRGB24(BGRHWC) 的函数接口(convert_argb.cc) // 注意这两个函数底层传入的都是 &kYuvI601Constants;没有 kYvuI601Constants // Convert NV12 to RGB24. LIBYUV_API int NV12ToRGB24(const uint8_t* src_y, int src_stride_y, const uint8_t* src_uv, int src_stride_uv, uint8_t* dst_rgb24, int dst_stride_rgb24, int width, int height) { 
    return NV12ToRGB24Matrix(src_y, src_stride_y, src_uv, src_stride_uv, dst_rgb24, dst_stride_rgb24, &kYuvI601Constants, width, height); } // Convert NV21 to RGB24. LIBYUV_API int NV21ToRGB24(const uint8_t* src_y, int src_stride_y, const uint8_t* src_vu, int src_stride_vu, uint8_t* dst_rgb24, int dst_stride_rgb24, int width, int height) { 
    return NV21ToRGB24Matrix(src_y, src_stride_y, src_vu, src_stride_vu, dst_rgb24, dst_stride_rgb24, &kYuvI601Constants, width, height); } // 2、 Conversion matrix for YUV to RGB 结构体矩阵(kYuvI601Constants)定义在 convert_argb.h 中 // 实现在 row_common.cc(MAKEYUVCONSTANTS) // BT.601 full range YUV to RGB reference (aka JPEG) // * R = Y + V * 1.40200 // * G = Y - U * 0.34414 - V * 0.71414 // * B = Y + U * 1.77200 // KR = 0.299; KB = 0.114 // U and V contributions to R,G,B. #define UB 113 /* round(1.77200 * 64) */ #define UG 22 /* round(0.34414 * 64) */ #define VG 46 /* round(0.71414 * 64) */ #define VR 90 /* round(1.40200 * 64) */ // Y contribution to R,G,B. Scale and bias. #define YG 16320 /* round(1.000 * 64 * 256 * 256 / 257) */ #define YB 32 /* 64 / 2 */ MAKEYUVCONSTANTS(JPEG, YG, YB, UB, UG, VG, VR) // 3、convert_argb.h 中定义的转换结构体 // Conversion matrix for YUV to RGB(实际使用下来是 BGR) LIBYUV_API extern const struct YuvConstants kYuvI601Constants; // BT.601 LIBYUV_API extern const struct YuvConstants kYuvJPEGConstants; // BT.601 full LIBYUV_API extern const struct YuvConstants kYuvH709Constants; // BT.709 LIBYUV_API extern const struct YuvConstants kYuvF709Constants; // BT.709 full LIBYUV_API extern const struct YuvConstants kYuv2020Constants; // BT.2020 LIBYUV_API extern const struct YuvConstants kYuvV2020Constants; // BT.2020 full // Conversion matrix for YVU to BGR(实际使用下来是 RGB) LIBYUV_API extern const struct YuvConstants kYvuI601Constants; // BT.601 LIBYUV_API extern const struct YuvConstants kYvuJPEGConstants; // BT.601 full LIBYUV_API extern const struct YuvConstants kYvuH709Constants; // BT.709 LIBYUV_API extern const struct YuvConstants kYvuF709Constants; // BT.709 full LIBYUV_API extern const struct YuvConstants kYvu2020Constants; // BT.2020 LIBYUV_API extern const struct YuvConstants kYvuV2020Constants; // BT.2020 full // 4、NV12ToRGB24Matrix + 自由选择转换标准进行转换 // 4.1、NV12 To BGRHWC libyuv::NV12ToRGB24Matrix(pu8DstY, s32Width, pu8DstUV, s32Width, bgr_hwc_img, s32Width * 3, &libyuv::kYuvJPEGConstants, // BT601 FULL s32Width, s32Height); // 4.2、NV12 To RGBHWC:使用 NV21 接口函数 libyuv::NV21ToRGB24Matrix(pu8DstY, s32Width, pu8DstUV, s32Width, bgr_hwc_img, s32Width * 3, &libyuv::kYuvJPEGConstants, // BT601 FULL s32Width, s32Height); // 5、NV12ToRGB24Matrix + 自由选择转换标准进行转换 // 5.1、NV21 To BGRHWC libyuv::NV21ToRGB24Matrix(pu8DstY, s32Width, pu8DstUV, s32Width, bgr_hwc_img, s32Width * 3, &libyuv::kYuvJPEGConstants, // BT601 FULL s32Width, s32Height); // 5.2、NV21 To RGBHWC:使用 NV12 接口函数 libyuv::NV12ToRGB24Matrix(pu8DstY, s32Width, pu8DstUV, s32Width, bgr_hwc_img, s32Width * 3, &libyuv::kYuvJPEGConstants, // BT601 FULL s32Width, s32Height); 
  • NV12RoiScaleToBGRHWC
void NV12RoiScaleToBGRHWC(uint8_t *src_y, uint8_t *src_uv, uint32_t src_y_stride, uint32_t roi_x, uint32_t roi_y, uint32_t roi_w, uint32_t roi_h, uint8_t *dst_nv12_buffer, uint8_t *dst_bgr_hwc_buffer, uint32_t dst_w, uint32_t dst_h) { 
    // 裁剪的坐标 X 和 Y 必须是偶数,否则可能得到 NV21 // 裁剪的宽和高必须是偶数(2n*2n*1.5 = 6n2),正好是 6 的倍数(每四个 Y 对应一个 UV),roi 坐标不需要返回 roi_x = (roi_x % 2) ? (roi_x - 1) : roi_x; roi_y = (roi_y % 2) ? (roi_y - 1) : roi_y; roi_w = (roi_w % 2) ? (roi_w - 1) : roi_w; roi_h = (roi_h % 2) ? (roi_h - 1) : roi_h; // 取得 roi 区域 y 的首地址 // 取得 roi 区域的 uv 首地址,由于 uv 在竖直方向上进行了 1/2 下采样,水平方向上并未做下采样,所以只需 roi_y 减半 uint8_t *src_roi_y = src_y + src_y_stride * roi_y + roi_x; uint8_t *src_roi_uv = src_uv + src_y_stride * roi_y / 2 + roi_x; // dst nv12 tmp buffer  uint8_t *dst_y = dst_nv12_buffer; uint8_t *dst_uv = dst_nv12_buffer + dst_w * dst_h; // src roi nv12 resize-->dst nv12 tmp buffer libyuv::ScalePlane(src_roi_y, src_y_stride, roi_w, roi_h, dst_y, dst_w, dst_w, dst_h, libyuv::kFilterBilinear); libyuv::ScalePlane_16((uint16_t *) src_roi_uv, src_y_stride / 2, (roi_w) / 2, (roi_h) / 2, (uint16_t *) dst_uv, dst_w / 2, dst_w / 2, dst_h / 2, libyuv::kFilterNone); // dst nv12 tmp buffer-->dst bgr hwc buffer // libyuv::NV12ToRGB24(dst_y, dst_w, dst_uv, dst_w, dst_bgr_hwc_buffer, dst_w * 3, dst_w, dst_h); libyuv::NV12ToRGB24Matrix(dst_y, dst_w, dst_uv, dst_w, dst_bgr_hwc_buffer, dst_w * 3, &libyuv::kYuvI601Constants, // 默认是 BT.601 limited; BT.601 full(kYuvJPEGConstants) 会暗一点 dst_w, dst_h); } 
  • NV12RoiScaleToRGBHWC
void NV12RoiScaleToRGBHWC(uint8_t *src_y, uint8_t *src_uv, uint32_t src_y_stride, uint32_t roi_x, uint32_t roi_y, uint32_t roi_w, uint32_t roi_h, uint8_t *dst_nv12_buffer, uint8_t *dst_rgb_hwc_buffer, uint32_t dst_w, uint32_t dst_h) { 
    // 裁剪的坐标 X 和 Y 必须是偶数,否则可能得到 NV21 // 裁剪的宽和高必须是偶数(2n*2n*1.5 = 6n2),正好是 6 的倍数(每四个 Y 对应一个 UV),roi 坐标不需要返回 roi_x = (roi_x % 2) ? (roi_x - 1) : roi_x; roi_y = (roi_y % 2) ? (roi_y - 1) : roi_y; roi_w = (roi_w % 2) ? (roi_w - 1) : roi_w; roi_h = (roi_h % 2) ? (roi_h - 1) : roi_h; // 取得 roi 区域 y 的首地址 // 取得 roi 区域的 uv 首地址,由于 uv 在竖直方向上进行了 1/2 下采样,水平方向上并未做下采样,所以只需 roi_y 减半 uint8_t *src_roi_y = src_y + src_y_stride * roi_y + roi_x; uint8_t *src_roi_uv = src_uv + src_y_stride * roi_y / 2 + roi_x; // dst nv12 tmp buffer uint8_t *dst_y = dst_nv12_buffer; uint8_t *dst_uv = dst_nv12_buffer + dst_w * dst_h; // src roi nv12 resize-->dst nv12 tmp buffer libyuv::ScalePlane(src_roi_y, src_y_stride, roi_w, roi_h, dst_y, dst_w, dst_w, dst_h, libyuv::kFilterBilinear); libyuv::ScalePlane_16((uint16_t *) src_roi_uv, src_y_stride / 2, (roi_w) / 2, (roi_h) / 2, (uint16_t *) dst_uv, dst_w / 2, dst_w / 2, dst_h / 2, libyuv::kFilterNone); // dst nv12 tmp buffer-->dst rgb hwc buffer //libyuv::NV21ToRGB24(dst_y, dst_w, dst_uv, dst_w, dst_rgb_hwc_buffer, dst_w * 3, dst_w, dst_h); libyuv::NV21ToRGB24Matrix(dst_y, dst_w, dst_uv, dst_w, dst_rgb_hwc_buffer, dst_w * 3, &libyuv::kYuvI601Constants, // 默认是 BT.601 limited; BT.601 full(kYuvJPEGConstants) 会暗一点 dst_w, dst_h); } 
  • NV21RoiScaleToBGRHWC
void NV21RoiScaleToBGRHWC(uint8_t *src_y, uint8_t *src_uv, uint32_t src_y_stride, uint32_t roi_x, uint32_t roi_y, uint32_t roi_w, uint32_t roi_h, uint8_t *dst_nv21_buffer, uint8_t *dst_bgr_hwc_buffer, uint32_t dst_w, uint32_t dst_h) { 
    // 裁剪的坐标 X 和 Y 必须是偶数,否则可能得到 NV21 // 裁剪的宽和高必须是偶数(2n*2n*1.5 = 6n2),正好是 6 的倍数(每四个 Y 对应一个 UV),roi 坐标不需要返回 roi_x = (roi_x % 2) ? (roi_x - 1) : roi_x; roi_y = (roi_y % 2) ? (roi_y - 1) : roi_y; roi_w = (roi_w % 2) ? (roi_w - 1) : roi_w; roi_h = (roi_h % 2) ? (roi_h - 1) : roi_h; // 取得 roi 区域 y 的首地址 // 取得 roi 区域的 uv 首地址,由于 uv 在竖直方向上进行了 1/2 下采样,水平方向上并未做下采样,所以只需 roi_y 减半 uint8_t *src_roi_y = src_y + src_y_stride * roi_y + roi_x; uint8_t *src_roi_uv = src_uv + src_y_stride * roi_y / 2 + roi_x; // dst nv21 tmp buffer uint8_t *dst_y = dst_nv21_buffer; uint8_t *dst_uv = dst_nv21_buffer + dst_w * dst_h; // src roi nv21 resize-->dst nv12 tmp buffer libyuv::ScalePlane(src_roi_y, src_y_stride, roi_w, roi_h, dst_y, dst_w, dst_w, dst_h, libyuv::kFilterBilinear); libyuv::ScalePlane_16((uint16_t *) src_roi_uv, src_y_stride / 2, (roi_w) / 2, (roi_h) / 2, (uint16_t *) dst_uv, dst_w / 2, dst_w / 2, dst_h / 2, libyuv::kFilterNone); // dst nv12 tmp buffer-->dst bgr hwc buffer libyuv::NV21ToRGB24Matrix(dst_y, dst_w, dst_uv, dst_w, dst_bgr_hwc_buffer, dst_w * 3, &libyuv::kYuvI601Constants, // 默认是 BT.601 limited; BT.601 full(kYuvJPEGConstants) 会暗一点 dst_w, dst_h); } 
  • NV21RoiScaleToRGBHWC
void NV21RoiScaleToRGBHWC(uint8_t *src_y, uint8_t *src_uv, uint32_t src_y_stride, uint32_t roi_x, uint32_t roi_y, uint32_t roi_w, uint32_t roi_h, uint8_t *dst_nv21_buffer, uint8_t *dst_rgb_hwc_buffer, uint32_t dst_w, uint32_t dst_h) { 
    // 裁剪的坐标 X 和 Y 必须是偶数,否则可能得到 NV21 // 裁剪的宽和高必须是偶数(2n*2n*1.5 = 6n2),正好是 6 的倍数(每四个 Y 对应一个 UV),roi 坐标不需要返回 roi_x = (roi_x % 2) ? (roi_x - 1) : roi_x; roi_y = (roi_y % 2) ? (roi_y - 1) : roi_y; roi_w = (roi_w % 2) ? (roi_w - 1) : roi_w; roi_h = (roi_h % 2) ? (roi_h - 1) : roi_h; // 取得 roi 区域 y 的首地址 // 取得 roi 区域的 uv 首地址,由于 uv 在竖直方向上进行了 1/2 下采样,水平方向上并未做下采样,所以只需 roi_y 减半 uint8_t *src_roi_y = src_y + src_y_stride * roi_y + roi_x; uint8_t *src_roi_uv = src_uv + src_y_stride * roi_y / 2 + roi_x; // dst nv21 tmp buffer uint8_t *dst_y = dst_nv21_buffer; uint8_t *dst_uv = dst_nv21_buffer + dst_w * dst_h; // src roi nv21 resize-->dst nv21 tmp buffer libyuv::ScalePlane(src_roi_y, src_y_stride, roi_w, roi_h, dst_y, dst_w, dst_w, dst_h, libyuv::kFilterBilinear); libyuv::ScalePlane_16((uint16_t *) src_roi_uv, src_y_stride / 2, (roi_w) / 2, (roi_h) / 2, (uint16_t *) dst_uv, dst_w / 2, dst_w / 2, dst_h / 2, libyuv::kFilterNone); // dst nv21 tmp buffer-->dst rgb hwc buffer libyuv::NV12ToRGB24Matrix(dst_y, dst_w, dst_uv, dst_w, dst_rgb_hwc_buffer, dst_w * 3, &libyuv::kYuvI601Constants, // 默认是 BT.601 limited; BT.601 full(kYuvJPEGConstants) 会暗一点 dst_w, dst_h); } 
2.1.2、BGR/RGB 转 NV12/NV21
  • 注意设置 flag_hwc
#define ROUND(f) static_cast<int>(f + 0.5f) static int RoundToByte(float f) { 
    int i = ROUND(f); return (i < 0) ? 0 : ((i > 255) ? 255 : i); } static void BGRToNV12(u8 *src, int src_stride, u8 *dst_y, u8 *dst_uv, int dst_stride, int width, int height, int flag_hwc = 1) { 
    int b, g, r; for (int i = 0; i < height; i++) { 
    for (int j = 0; j < width; j++) { 
    if (flag_hwc) { 
    b = src[i * src_stride * 3 + j * 3 + 0]; g = src[i * src_stride * 3 + j * 3 + 1]; r = src[i * src_stride * 3 + j * 3 + 2]; } else { 
    b = src[(i + height * 0) * src_stride + j]; g = src[(i + height * 1) * src_stride + j]; r = src[(i + height * 2) * src_stride + j]; } // BT.601 limited range RGB to YUV dst_y[i * dst_stride + j] = RoundToByte(0.257 * r + 0.504 * g + 0.098 * b + 16); // i 和 j 均为奇数的时候 if ((i % 2) && (j % 2)) { 
    dst_uv[i / 2 * dst_stride + j - 1] = RoundToByte(-0.148 * r - 0.291 * g + 0.439 * b + 128); // U 分量 dst_uv[i / 2 * dst_stride + j] = RoundToByte(0.439 * r - 0.368 * g - 0.071 * b + 128); // V 分量 } } } } static void RGBToNV12(u8 *src, int src_stride, u8 *dst_y, u8 *dst_uv, int dst_stride, int width, int height, int flag_hwc = 1) { 
    int b, g, r; for (int i = 0; i < height; i++) { 
    for (int j = 0; j < width; j++) { 
    if (flag_hwc) { 
    r = src[i * src_stride * 3 + j * 3 + 0]; g = src[i * src_stride * 3 + j * 3 + 1]; b = src[i * src_stride * 3 + j * 3 + 2]; } else { 
    r = src[(i + height * 0) * src_stride + j]; g = src[(i + height * 1) * src_stride + j]; b = src[(i + height * 2) * src_stride + j]; } // BT.601 limited range RGB to YUV dst_y[i * dst_stride + j] = RoundToByte(0.257 * r + 0.504 * g + 0.098 * b + 16); // i 和 j 均为奇数的时候 if ((i % 2) && (j % 2)) { 
    dst_uv[i / 2 * dst_stride + j - 1] = RoundToByte(-0.148 * r - 0.291 * g + 0.439 * b + 128); // U 分量 dst_uv[i / 2 * dst_stride + j] = RoundToByte(0.439 * r - 0.368 * g - 0.071 * b + 128); // V 分量 } } } } static void BGRToNV21(u8 *src, int src_stride, u8 *dst_y, u8 *dst_uv, int dst_stride, int width, int height, int flag_hwc = 1) { 
    int b, g, r; for (int i = 0; i < height; i++) { 
    for (int j = 0; j < width; j++) { 
    if (flag_hwc) { 
    b = src[i * src_stride * 3 + j * 3 + 0]; g = src[i * src_stride * 3 + j * 3 + 1]; r = src[i * src_stride * 3 + j * 3 + 2]; } else { 
    b = src[(i + height * 0) * src_stride + j]; g = src[(i + height * 1) * src_stride + j]; r = src[(i + height * 2) * src_stride + j]; } // BT.601 limited range RGB to YUV dst_y[i * dst_stride + j] = RoundToByte(0.257 * r + 0.504 * g + 0.098 * b + 16); // i 和 j 均为奇数的时候 if ((i % 2) && (j % 2)) { 
    dst_uv[i / 2 * dst_stride + j - 1] = RoundToByte(0.439 * r - 0.368 * g - 0.071 * b + 128); // V 分量 dst_uv[i / 2 * dst_stride + j] = RoundToByte(-0.148 * r - 0.291 * g + 0.439 * b + 128); // U 分量 } } } } static void RGBToNV21(u8 *src, int src_stride, u8 *dst_y, u8 *dst_uv, int dst_stride, int width, int height, int flag_hwc = 1) { 
    int b, g, r; for (int i = 0; i < height; i++) { 
    for (int j = 0; j < width; j++) { 
    if (flag_hwc) { 
    r = src[i * src_stride * 3 + j * 3 + 0]; g = src[i * src_stride * 3 + j * 3 + 1]; b = src[i * src_stride * 3 + j * 3 + 2]; } else { 
    r = src[(i + height * 0) * src_stride + j]; g = src[(i + height * 1) * src_stride + j]; b = src[(i + height * 2) * src_stride + j]; } // BT.601 limited range RGB to YUV dst_y[i * dst_stride + j] = RoundToByte(0.257 * r + 0.504 * g + 0.098 * b + 16); // i 和 j 均为奇数的时候 if ((i % 2) && (j % 2)) { 
    dst_uv[i / 2 * dst_stride + j - 1] = RoundToByte(0.439 * r - 0.368 * g - 0.071 * b + 128); // V 分量 dst_uv[i / 2 * dst_stride + j] = RoundToByte(-0.148 * r - 0.291 * g + 0.439 * b + 128); // U 分量 } } } } 
  • NEON BGRHWCToNV12/NV21
static void BGRHWCToNV12(uint8_t *src_bgr, uint32_t src_stride_bgr, uint8_t *dst_y, uint32_t dst_stride_y, uint8_t *dst_uv, uint32_t dst_stride_uv, uint32_t width, uint32_t height, int32_t flag_nv12) { 
    uint8_t *pu8DstY = dst_y; uint8_t *pu8DstU = dst_uv; uint8_t *pu8Srcbgr = src_bgr; #ifdef _ARM_NEON_ uint8x8x3_t u883RGB; uint8x8x2_t u882UV; uint8x8_t u8x8Y, u8x8UV; for (int32_t i = 0; i < height; i++) { 
    for (int32_t j = 0; j < src_stride_bgr * 3; j += 24) { 
    u883RGB = vld3_u8(pu8Srcbgr + i * src_stride_bgr * 3 + j); // 一次读取3 * 8个u8数据 uint16x8_t u16x8_B = vmulq_n_u16(vmovl_u8(u883RGB.val[0]), 29); // 一次读取8 x 2 个 uint8的数据,并强制转换成uint16 uint16x8_t u16x8_G = vmulq_n_u16(vmovl_u8(u883RGB.val[1]), 150); // 一次读取8 x 2 个 uint8的数据,并强制转换成uint16 uint16x8_t u16x8_R = vmulq_n_u16(vmovl_u8(u883RGB.val[2]), 74); // 一次读取8 x 2 个 uint8的数据,并强制转换成uint16 uint16x8_t u16x8_Y = vaddq_u16(vaddq_u16(u16x8_B, u16x8_G), u16x8_R); // 74 * r + 150 * g + 29 * b u8x8Y = vmin_u8(vmax_u8(vmovn_u16(vshrq_n_u16(u16x8_Y, 8)), vdup_n_u8(0)), vdup_n_u8(255)); vst1_u8(pu8DstY + j / 3, u8x8Y); // Y if ((i % 2)) { 
    uint16x8_t u16x8_B1 = vmulq_n_u16(vmovl_u8(u883RGB.val[0]), 128); // 一次读取8 x 2 个 uint8的数据,并强制转换成uint16 uint16x8_t u16x8_G1 = vmulq_n_u16(vmovl_u8(u883RGB.val[1]), 84); // 一次读取8 x 2 个 uint8的数据,并强制转换成uint16 uint16x8_t u16x8_R1 = vmulq_n_u16(vmovl_u8(u883RGB.val[2]), 43); // 一次读取8 x 2 个 uint8的数据,并强制转换成uint16 uint16x8_t u16x8_U = vsubq_u16(vsubq_u16(u16x8_B1, u16x8_G1), u16x8_R1); // -43 * r - 84 * g + 128 * b // u8x8U = vmovn_u16(vshrq_n_u16(u16x8_U, 8)); uint16x8_t u16x8_B2 = vmulq_n_u16(vmovl_u8(u883RGB.val[0]), 21); // 一次读取8 x 2 个 uint8的数据,并强制转换成uint16 uint16x8_t u16x8_G2 = vmulq_n_u16(vmovl_u8(u883RGB.val[1]), 107); // 一次读取8 x 2 个 uint8的数据,并强制转换成uint16 uint16x8_t u16x8_R2 = vmulq_n_u16(vmovl_u8(u883RGB.val[2]), 128); // 一次读取8 x 2 个 uint8的数据,并强制转换成uint16 uint16x8_t u16x8_V = vsubq_u16(vsubq_u16(u16x8_R2, u16x8_G2), u16x8_B2); // 128 * r - 107 * g - 21 * b // u8x8V = vmovn_u16(vshrq_n_u16(u16x8_V, 8)); if (flag_nv12) { 
    // nv12 u882UV = vtrn_u8(vmovn_u16(vshrq_n_u16(u16x8_U, 8)), vmovn_u16(vshrq_n_u16(u16x8_V, 8))); } else { 
    // nv21 u882UV = vtrn_u8(vmovn_u16(vshrq_n_u16(u16x8_V, 8)), vmovn_u16(vshrq_n_u16(u16x8_U, 8))); } u8x8UV = vmin_u8(vmax_u8(vadd_u8(u882UV.val[0], vdup_n_u8(128)), vdup_n_u8(0)), vdup_n_u8(255)); vst1_u8(pu8DstU + j / 3, u8x8UV); // UV } } int32_t num = src_stride_bgr / 8; int32_t rest = src_stride_bgr % 8; for (int32_t j = 0; j < rest; j++) { 
    int32_t b = pu8Srcbgr[i * src_stride_bgr * 3 + num * 3 * 8 + j * 3]; int32_t g = pu8Srcbgr[i * src_stride_bgr * 3 + num * 3 * 8 + j * 3 + 1]; int32_t r = pu8Srcbgr[i * src_stride_bgr * 3 + num * 3 * 8 + j * 3 + 2]; int32_t y = ((74 * r + 150 * g + 29 * b) >> 8); // 0-255, BT601 FULL pu8DstY[i * dst_stride_y + num * 8 + j] = (y < 0) ? 0 : ((y > 255) ? 255 : y); if ((i % 2) && (j % 2)) { 
    int32_t u = ((-43 * r - 84 * g + 128 * b) >> 8) + 128; // 0-255 int32_t v = ((128 * r - 107 * g - 21 * b) >> 8) + 128; // 0-255 if (flag_nv12) { 
    pu8DstU[i / 2 * dst_stride_uv + num * 8 + j - 1] = (u < 0) ? 0 : ((u > 255) ? 255 : u); // NV12 pu8DstU[i / 2 * dst_stride_uv + num * 8 + j] = (v < 0) ? 0 : ((v > 255) ? 255 : v); } else { 
    pu8DstU[i / 2 * dst_stride_uv + num * 8 + j - 1] = (v < 0) ? 0 : ((v > 255) ? 255 : v); // NV21 pu8DstU[i / 2 * dst_stride_uv + num * 8 + j] = (u < 0) ? 0 : ((u > 255) ? 255 : u); } } } pu8DstY += dst_stride_y; if ((i % 2)) { 
    pu8DstU += dst_stride_uv; } } #else int32_t i, j; int32_t b, g, r, y, u, v; for (i = 0; i < height; i++) { 
    for (j = 0; j < width; j++) { 
    b = pu8Srcbgr[i * src_stride_bgr * 3 + j * 3]; g = pu8Srcbgr[i * src_stride_bgr * 3 + j * 3 + 1]; r = pu8Srcbgr[i * src_stride_bgr * 3 + j * 3 + 2]; // y = ((66 * r + 129 * g + 25 * b) >> 8) + 16; // 16-235, BT601 limited y = ((74 * r + 150 * g + 29 * b) >> 8); // 0-255, BT601 FULL pu8DstY[i * dst_stride_y + j] = (y < 0) ? 0 : ((y > 255) ? 255 : y); if ((i % 2) && (j % 2)) { 
    // 16-235, BT601 limited // u = ((-38 * r - 74 * g + 112 * b) >> 8) + 128;//16-235 // v = ((112 * r - 94 * g - 18 * b) >> 8) + 128;//16-235 u = ((-43 * r - 84 * g + 128 * b) >> 8) + 128; // 0-255 v = ((128 * r - 107 * g - 21 * b) >> 8) + 128; // 0-255 if (flag_nv12) { 
    pu8DstU[i / 2 * dst_stride_uv + j - 1] = (u < 0) ? 0 : ((u > 255) ? 255 : u); // NV12 pu8DstU[i / 2 * dst_stride_uv + j] = (v < 0) ? 0 : ((v > 255) ? 255 : v); } else { 
    pu8DstU[i / 2 * dst_stride_uv + j - 1] = (v < 0) ? 0 : ((v > 255) ? 255 : v); // NV21 pu8DstU[i / 2 * dst_stride_uv + j] = (u < 0) ? 0 : ((u > 255) ? 255 : u); } } } } #endif } 

2.2、YUV422(NV16、NV61、I422、YV16、YUVY、VYUY、UYVY)

# NV16、NV61 的存储格式为 Y 平面,UV 打包一个平面,共两个平面,即:先连续存储 Y,然后连续交叉存储 UV # 不同点在于 UV 的排列顺序,SP 为 Semi-Planar 的缩写 # NV16:先是 w * h 长度的 Y,后面跟 w * h 长度的 UV(交叉存储),总长度为 w * h * 2 # NV61:先是 w * h 长度的 Y,后面跟 w * h 长度的 VU(交叉存储),总长度为 w * h * 2 NV16: YYYYYYYY UVUVUVUV => YUV422SP NV61: YYYYYYYY VUVUVUVU => YUV422SP # I422、YV16 三个分量均为平面格式,共三个平面,即:先连续存储 Y,然后连续存储 U,最后连续存储 V # 不同点在于 U V 的排列顺序,P 为 Planar 的缩写 # I422:先是 w * h 长度的 Y,后面跟 w * h * 0.5 长度的 U, 最后是 w * h * 0.5 长度的 V,总长度为 w * h * 2 # YV16:先是 w * h 长度的 Y,后面跟 w * h * 0.5 长度的 V, 最后是 w * h * 0.5 长度的 U,总长度为 w * h * 2 I422: YYYYYYYY UUUU VVVV => YUV422P YV16: YYYYYYYY VVVV UUUU => YUV422P # YUVY、VYUY、UYVY 为打包格式:每个像素点的 Y,U,V 是连续交叉存储 # YUVY:在 Packed 内部,YUV 的排列顺序是 YUVY,两个 Y 共用一组 UV # VYUY:在 Packed 内部,YUV 的排列顺序是 VYUY,两个 Y 共用一组 UV # UYVY:在 Packed 内部,YUV 的排列顺序是 UYVY,两个 Y 共用一组 UV YUVY: YUVY YUVY YUVY YUVY => YUV422 VYUY: VYUY VYUY VYUY VYUY => YUV422 UYVY: UYVY UYVY UYVY UYVY => YUV422 

在这里插入图片描述在这里插入图片描述


2.3、YUV444(I444、YV24)

# I444、YV24三个分量均为平面格式,共三个平面,即:先连续存储 Y,然后连续存储 U,最后连续存储 V # 不同点在于 U V 的排列顺序,P 为 Planar 的缩写 # I444:先是 w * h 长度的 Y,后面跟 w * h 长度的 U, 最后是 w * h 长度的 V,总长度为 w * h * 3 # YV24:先是 w * h 长度的 Y,后面跟 w * h 长度的 V, 最后是 w * h 长度的 U,总长度为 w * h * 3 I444: YYYYYYYY UUUUUUUU VVVVVVVV => YUV444 YV24: YYYYYYYY VVVVVVVV UUUUUUUU => YUV444 

在这里插入图片描述
在这里插入图片描述

  • NV12 转 I444
void NV12_to_I444(uint8_t* nv12data, uint8_t* i444data, int frameWidth, int frameHeight) { 
    uint8_t* nv12_src[2]; nv12_src[0] = nv12data; nv12_src[1] = nv12_src[0] + frameWidth * frameHeight; uint8_t* yuv444_dst[3]; yuv444_dst[0] = i444data; yuv444_dst[1] = yuv444_dst[0] + frameWidth * frameHeight; yuv444_dst[2] = yuv444_dst[1] + frameWidth * frameHeight; // Y memcpy(yuv444_dst[0], nv12_src[0], frameWidth * frameHeight); // UV for (int i = 0; i < frameWidth * frameHeight / 2; i++) { 
    if (i % 2 == 0) { 
   // U,NV12中一位对应444中4位 *(yuv444_dst[1]) = *(nv12_src[1] + i); *(yuv444_dst[1] + frameWidth) = *(nv12_src[1] + i); yuv444_dst[1]++; *(yuv444_dst[1]) = *(nv12_src[1] + i); *(yuv444_dst[1] + frameWidth) = *(nv12_src[1] + i); yuv444_dst[1]++; } else { 
   // V,NV12中一位对应444中4位 *(yuv444_dst[2]) = *(nv12_src[1] + i); *(yuv444_dst[2] + frameWidth) = *(nv12_src[1] + i); yuv444_dst[2]++; *(yuv444_dst[2]) = *(nv12_src[1] + i); *(yuv444_dst[2] + frameWidth) = *(nv12_src[1] + i); yuv444_dst[2]++; } if ((i > frameWidth && i % frameWidth == 0)) { 
    // UV 分量跳行 yuv444_dst[1] = yuv444_dst[1] + frameWidth; yuv444_dst[2] = yuv444_dst[2] + frameWidth; } } } // 使用 libyuv 实现,NV12ToI420 或 NV21ToI420 libyuv::NV12ToI420(ptr_tmp_y, s32width, (uint8_t*)ptr_tmp_uv, s32width , ptr_tmpI420_y, s32width, ptr_tmpI420_u, s32width/2, ptr_tmpI420_v, s32width/2 ,s32width, s32height ); libyuv::I420ToI444(ptr_tmpI420_y, s32width, ptr_tmpI420_u, s32width / 2, ptr_tmpI420_v , s32width / 2, ptr_tmpI444_y, s32width, ptr_tmpI444_u, s32width, ptr_tmpI444_v, s32width, s32width, s32height ); 
  • NV12 转 I444 同时进行缩放
/*==================================================================== 函数名 : UtiBilinearResizeRoiOneChannel 功能 : 采用双线性插值的方法将单通道图像的Roi区域进行缩放 算法实现 : 引用全局变量: 无 输入参数说明: u8 *pu8SrcImage : 源图像地址[in] u8 *pu8DstImage : 目的图像的地址[out] tRectIn : 源图像的ROI框[in] u32SrcStride : 源图像的步进值[in] u32DstWidth : 目的图像的宽度[in] u32DstHeight : 目的图像的高度[in] u32DstStride : 目的图像的步进值[in] 返回值说明 : 无 ====================================================================*/ void UtiBilinearResizeRoiOneChannel(u8 *pu8SrcImage, u8 *pu8DstImage, TRect tRectIn, u32 u32SrcStride, u32 u32DstWidth, u32 u32DstHeight, u32 u32DstStride) { 
    u8 *pu8SrcLine1 = NULL; u8 *pu8SrcLine2 = NULL; u8 *pu8Dst = NULL; u32 u32WY1, u32WY2; u32 u32WX1, u32WX2; u32 u32XPosition, u32YPosition; u32 u32XSrc, u32YSrc; u32 u32XStride, u32YStride; u32 u32Vn0, u32Vn1; u32 u32RowIndex; u32 u32LineIndex; u32 u32YPositionInit = 0; u32 u32XPositionInit = 0; u32 u32SrcXOffset = tRectIn.l32Left; u32 u32SrcYOffset = tRectIn.l32Top; u32 u32SrcWidth = tRectIn.l32Width; u32 u32SrcHeight = tRectIn.l32Height; u32XStride = ((u32SrcWidth - 1) << 16) / (u32DstWidth - 1);//pow(2,16)*Src_width/Dst_width u32YStride = ((u32SrcHeight - 1) << 16) / (u32DstHeight - 1);//pow(2,16)*Src_height/Dst_height //判断是否需要进行起始点偏移 if(0 == ((u32XStride << 16) >> 27)) { 
    u32XStride = ((u32SrcWidth - 2) << 16) / (u32DstWidth - 1);//pow(2,16)*Src_width/Dst_width u32XPositionInit = 1 << 15; } if(0 == ((u32YStride << 16) >> 27)) { 
    u32YStride = ((u32SrcHeight - 2) << 16) / (u32DstHeight - 1);//pow(2,16)*Src_height/Dst_height u32YPositionInit = 1 << 15; } //按起始坐标偏移 u32YPosition = u32YPositionInit + (u32SrcYOffset << 16); for(u32RowIndex = 0; u32RowIndex < u32DstHeight; u32RowIndex++) { 
    u32YSrc = u32YPosition >> 16; //垂直方向权值 u32WY2 = (u32YPosition << 16) >> 29; u32WY1 = 8 - u32WY2; //放大权值以利用_dotprsu2指令右移16位特点 u32WY2 *= 1024; u32WY1 *= 1024; pu8SrcLine1 = pu8SrcImage + u32YSrc * u32SrcStride; pu8SrcLine2 = u32WY2 == 0 ? pu8SrcLine1 : pu8SrcLine1 + u32SrcStride; //在最后一行的时候,读的数据是不参与运算的,但VC不允许越界读,因此这里加判断防止读越界 pu8Dst = pu8DstImage + u32RowIndex * u32DstStride;//u32DstWidth; //按起始坐标偏移 u32XPosition = u32XPositionInit + (u32SrcXOffset << 16); for(u32LineIndex = 0; u32LineIndex < u32DstWidth; u32LineIndex++) { 
    //定点数的高16位就是整数部分 u32XSrc = u32XPosition >> 16; //定点数的低16位就是小数部分,我们使用其高3位作为权值(范围0-8) u32WX2 = (u32XPosition << 16) >> 29; u32WX1 = 8 - u32WX2; //水平线性滤波--下面的操作对应于_dotpu4指令 u32Vn0 = pu8SrcLine1[u32XSrc] * u32WX1 + pu8SrcLine1[u32XSrc + 1] * u32WX2; u32Vn1 = pu8SrcLine2[u32XSrc] * u32WX1 + pu8SrcLine2[u32XSrc + 1] * u32WX2; //垂直线性滤波--下面的操作对应于_dotprsu2指令 *pu8Dst++ = (u8)((u32Vn0 * u32WY1 + u32Vn1 * u32WY2 + 0x8000) >> 16); u32XPosition += u32XStride; } u32YPosition += u32YStride; } } /*==================================================================== 函数名 : UtiBilinearResizeRoiTwoChannel 功能 : 采用双线性插值的方法将交织的两通道图像的Roi区域进行分通道缩放,如uv数据 算法实现 : 引用全局变量: 无 输入参数说明: u8 *pu8SrcData : 源图像地址[in] u8 *pu8DstChn1 : 目的图像的地址[out] u8 *pu8DstChn2 : 目的图像的地址[out] tRectIn : 源图像的ROI框[in] u32SrcStride : 源图像的步进值[in] u32DstWidth : 目的图像的宽度[in] u32DstHeight : 目的图像的高度[in] u32DstStride : 目的图像的步进值[in] 返回值说明 : 无 ====================================================================*/ void UtiBilinearResizeRoiTwoChannel(u8 *pu8SrcData, u8 *pu8DstChn1, u8 *pu8DstChn2, TRect tRect, u32 u32SrcStride, u32 u32DstWidth, u32 u32DstHeight, u32 u32DstStride) { 
    u8 *pu8SrcLine1 = NULL; u8 *pu8SrcLine2 = NULL; u8 *pu8DstChn1Tmp = NULL; u8 *pu8DstChn2Tmp = NULL; u32 u32WY1, u32WY2; u32 u32WX1, u32WX2; u32 u32XPosition, u32YPosition; u32 u32XSrc, u32YSrc; u32 u32XStride, u32YStride; u32 u32Vn0, u32Vn1; u32 u32RowIndex; u32 u32LineIndex; u32 u32YPositionInit = 0; u32 u32XPositionInit = 0; u32 u32SrcXOffset = tRect.l32Left; u32 u32SrcYOffset = tRect.l32Top; u32 u32SrcWidth = tRect.l32Width; u32 u32SrcHeight = tRect.l32Height; u32XStride = ((u32SrcWidth - 1) << 16) / (u32DstWidth - 1);//pow(2,16)*Src_width/Dst_width u32YStride = ((u32SrcHeight - 1) << 16) / (u32DstHeight - 1);//pow(2,16)*Src_height/Dst_height //判断是否需要进行起始点偏移 if(0 == ((u32XStride << 16) >> 27)) { 
    u32XStride = ((u32SrcWidth - 2) << 16) / (u32DstWidth - 1);//pow(2,16)*Src_width/Dst_width u32XPositionInit = 1 << 15; } if(0 == ((u32YStride << 16) >> 27)) { 
    u32YStride = ((u32SrcHeight - 2) << 16) / (u32DstHeight - 1);//pow(2,16)*Src_height/Dst_height u32YPositionInit = 1 << 15; } //按起始坐标偏移 u32YPosition = u32YPositionInit + (u32SrcYOffset << 16); for(u32RowIndex = 0; u32RowIndex < u32DstHeight; u32RowIndex++) { 
    u32YSrc = u32YPosition >> 16; //垂直方向权值 u32WY2 = (u32YPosition << 16) >> 29; u32WY1 = 8 - u32WY2; //放大权值以利用_dotprsu2指令右移16位特点 u32WY2 *= 1024; u32WY1 *= 1024; pu8SrcLine1 = pu8SrcData + u32YSrc * u32SrcStride; pu8SrcLine2 = u32WY2 == 0 ? pu8SrcLine1 : pu8SrcLine1 + u32SrcStride; //在最后一行的时候,读的数据是不参与运算的,但VC不允许越界读,因此这里加判断防止读越界 pu8DstChn1Tmp = pu8DstChn1 + (u32RowIndex)* u32DstStride; pu8DstChn2Tmp = pu8DstChn2 + (u32RowIndex)* u32DstStride; //按起始坐标偏移 u32XPosition = u32XPositionInit + (u32SrcXOffset << 16); for(u32LineIndex = 0; u32LineIndex < u32DstWidth; u32LineIndex++) { 
    //定点数的高16位就是整数部分 u32XSrc = u32XPosition >> 16; u32XSrc *= 2; //定点数的低16位就是小数部分,我们使用其高3位作为权值(范围0-8) u32WX2 = (u32XPosition << 16) >> 29; u32WX1 = 8 - u32WX2; //水平线性滤波--下面的操作对应于_dotpu4指令 u32Vn0 = pu8SrcLine1[u32XSrc] * u32WX1 + pu8SrcLine1[u32XSrc + 2] * u32WX2; u32Vn1 = pu8SrcLine2[u32XSrc] * u32WX1 + pu8SrcLine2[u32XSrc + 2] * u32WX2; //垂直线性滤波--下面的操作对应于_dotprsu2指令 *pu8DstChn1Tmp++ = (u8)((u32Vn0 * u32WY1 + u32Vn1 * u32WY2 + 0x8000) >> 16); //水平线性滤波--下面的操作对应于_dotpu4指令 u32Vn0 = pu8SrcLine1[u32XSrc + 1] * u32WX1 + pu8SrcLine1[u32XSrc + 3] * u32WX2; u32Vn1 = pu8SrcLine2[u32XSrc + 1] * u32WX1 + pu8SrcLine2[u32XSrc + 3] * u32WX2; //垂直线性滤波--下面的操作对应于_dotprsu2指令 *pu8DstChn2Tmp++ = (u8)((u32Vn0 * u32WY1 + u32Vn1 * u32WY2 + 0x8000) >> 16); u32XPosition += u32XStride; } u32YPosition += u32YStride; } } /*==================================================================== 函数名 : UtiBilinearResizeRoiNV12ToYUV444 功能 : 输入图像NV12转Y4444,同时转类型和缩放 算法实现 : 引用全局变量: 无 输入参数说明: ptSrcImg: 源图像[in] ptDstImg : 目的图像(Y444格式的YUV)[out] tRect: 源图待转区域 返回值说明 : -1 缩放失败 0 缩放成功 ====================================================================*/ static l32 UtiBilinearResizeRoiNV12ToYUV444(TImage *ptSrcImg, TImage *ptDstImg, TRect tRectIn) { 
    u8 *pu8SrcY = NULL; u8 *pu8SrcU = NULL; u8 *pu8SrcV = NULL; u8 *pu8DstY = NULL; u8 *pu8DstU = NULL; u8 *pu8DstV = NULL; u8 *pu8SrcUV = NULL; l32 l32SrcImgStride; l32 l32SrcWidth = ptSrcImg->atPlane[0].l32Width; l32 l32SrcHeight = ptSrcImg->atPlane[0].l32Height; l32 l32SrcStride = ptSrcImg->atPlane[0].l32Stride; l32 l32DstWidth = ptDstImg->atPlane[0].l32Width; l32 l32DstHeight = ptDstImg->atPlane[0].l32Height; l32 l32DstStride = ptDstImg->atPlane[0].l32Stride; TRect tRectInTmp; //参数检查 if(AI_NV12 != ptSrcImg->u32Type) { 
    printf("The src image type is not supported in UtiBilinearResizeRoiNV12ToYUV444!\n"); return -1; } //如果输入框的宽高为0,则默认是全图 if(tRectIn.l32Width == 0 || tRectIn.l32Height == 0) { 
    tRectIn.l32Left = 0; tRectIn.l32Top = 0; tRectIn.l32Width = ptSrcImg->atPlane[0].l32Width; tRectIn.l32Height = ptSrcImg->atPlane[0].l32Height; } //保证起始点对应uv的正确位置和长宽是偶数 tRectInTmp.l32Left = (tRectIn.l32Left >> 1) << 1; tRectInTmp.l32Top = (tRectIn.l32Top >> 1) << 1; tRectInTmp.l32Width = (tRectIn.l32Width >> 1) << 1; tRectInTmp.l32Height = (tRectIn.l32Height >> 1) << 1; l32SrcImgStride = ptSrcImg->atPlane[0].l32Stride; //源图像 pu8SrcY = (u8 *)ptSrcImg->atPlane[0].pvBuffer; pu8SrcUV = (u8 *)ptSrcImg->atPlane[1].pvBuffer; //目的图像 pu8DstY = (u8 *)ptDstImg->atPlane[0].pvBuffer; pu8DstU = (u8 *)ptDstImg->atPlane[1].pvBuffer; pu8DstV = (u8 *)ptDstImg->atPlane[2].pvBuffer; //Y分量 UtiBilinearResizeRoiOneChannel(pu8SrcY, pu8DstY, tRectInTmp, l32SrcImgStride, l32DstWidth, l32DstHeight, l32DstWidth); //UV分量 tRectInTmp.l32Left = tRectInTmp.l32Left >> 1; tRectInTmp.l32Top = tRectInTmp.l32Top >> 1; tRectInTmp.l32Width = tRectInTmp.l32Width >> 1; tRectInTmp.l32Height = tRectInTmp.l32Height >> 1; l32SrcImgStride = ptSrcImg->atPlane[1].l32Stride; UtiBilinearResizeRoiTwoChannel(pu8SrcUV, pu8DstU, pu8DstV, tRectInTmp, l32SrcImgStride, l32DstWidth, l32DstHeight, l32DstWidth); return 0; } 
  • RGB2I444
def round2byte(f): f = np.round(f) f = np.where(f < 0, 0, f) f = np.where(f > 255, 255, f) return f.astype(np.uint8) # 训练的时候可以考虑用不同的标准 BT601/BT601FULL/BT709/BT709FULL/BT2020/BT2020FULL def rgb2i444(img_rgb_resized): h, w = img_rgb_resized.shape[:2] img_i444 = np.zeros((h, w, 3), dtype=np.uint8) img_i444[:, :, 0] = round2byte(np.sum(img_rgb_resized * np.array([0.257, 0.504, 0.098]), axis=2) + 16) img_i444[:, :, 1] = round2byte(np.sum(img_rgb_resized * np.array([-0.148, -0.291, 0.439]), axis=2) + 128) img_i444[:, :, 2] = round2byte(np.sum(img_rgb_resized * np.array([0.439, -0.368, -0.071]), axis=2) + 128) return img_i444 

三、常用视频分辨率及码率、帧率介绍

  • 常用视频分辨率(w*h):
    • 720P(1280*720): 表示视频有 720 行像素(height),大约 100w 像素,P(Progressive 的缩写)本身表示的是逐行扫描
    • 1080P(1920*1080):表示视频有 1080 行像素(height),大约 200w 像素
    • 2K(2048*1080):表示视频有 2048(2*2^10=2K) 列的像素数(width),大约 200w 像素,最常见的是影院级别的 2K,其它还有 2048×1536(QXGA)2560×1600(WQXGA),2560×1440(Quad HD)
    • 4K(3840*2160/4096*2160):表示视频有 4096(4*2^10=4K) 列的像素数(width),大约 800~900 w 像素
  • 码流(Data Rate):
    • 也叫码率,指的是视频文件在单位时间内使用的数据流量,对视频编码画面质量的控制起到重要作用
    • 在同样分辨率下,视频文件码流越大,压缩比就越小,画面质量就越好
  • 帧率(fps):
    • 指的是每秒钟传输图片的帧数,帧率越高,性能越好
  • H.264 和 H.265 的区别:
    • 均指视频编码的标准,H265 在保证清晰度的同时降低了码流,可以节约存储空间,同时降低了网络带宽
      在这里插入图片描述

四、读取 YUV(NV12) 视频文件并保存

  • 1、使用 FFMPEG 工具实现:
    • nv12 数据转 jpg:.\ffmpeg.exe -y -s 1920x1080 -pix_fmt nv12 -i .\output\1_NV12.yuv .\output\1_NV12\image%d.jpg
    • jpg 数据转 nv12: .\ffmpeg.exe -s 1920x1080 -i .\input\image%d.jpg -pix_fmt nv12 test.yuv
    • h265 数据转 nv12:.\ffmpeg.exe -i .\input\1.h265 -s 1920x1080 -pix_fmt nv12 .\output\1_NV12.yuv
    • nv12 数据转 h265:.\ffmpeg.exe -video_size 1920x1080 -pix_fmt nv21 -i input.yuv -vcodec h265 output.h265
    • imglist 转 mp4:.\ffmpeg.exe -y -r 30 -f concat -safe 0 -i filelist.txt -b:v -c:v libx264 -vf fps=30 -pix_fmt yuv420p out.mp4
    • Note:ffmpeg 还有其它很强大的一些功能,如 imglist 转 mp4、yuv 转 h265 等等
  • 2、python 代码实现如下:
#!/usr/bin/env python3 # -*- coding: utf-8 -*- import cv2 import numpy as np def yuv2bgr(file_name, height, width, start_frame): """ :param file_name: 待处理 YUV 视频的名字 :param height: YUV 视频中图像的高 :param width: YUV 视频中图像的宽 :param start_frame: 起始帧 :return: None """ fp = open(file_name, 'rb') fp.seek(0, 2) # 设置文件指针到文件流的尾部 + 偏移 0 fp_end = fp.tell() # 获取文件尾指针位置 frame_size = height * width * 3 // 2 # 一帧图像所占用的内存字节数 num_frame = fp_end // frame_size # 计算 YUV 文件包含图像数 print("This yuv file has {} frame imgs!".format(num_frame)) fp.seek(frame_size * start_frame, 0) # 设置文件指针到文件流的起始位置 + 偏移 frame_size * startframe print("Extract imgs start frame is {}!".format(start_frame + 1)) for i in range(num_frame - start_frame): yyyy_uv = np.zeros(shape=frame_size, dtype='uint8', order='C') for j in range(frame_size): yyyy_uv[j] = ord(fp.read(1)) # 读取 YUV 数据,并转换为 unicode img = yyyy_uv.reshape((height * 3 // 2, width)).astype('uint8') # NV12 的存储格式为:YYYY UV 分布在两个平面(其在内存中为 1 维) bgr_img = cv2.cvtColor(img, cv2.COLOR_YUV2BGR_NV12) # 由于 opencv 不能直接读取 YUV 格式的文件, 所以要转换一下格式,支持的转换格式可参考资料 5 cv2.imwrite('yuv2bgr/{}.jpg'.format(i + 1), bgr_img) # 改变后缀即可实现不同格式图片的保存(jpg/bmp/png...) print("Extract frame {}".format(i + 1)) fp.close() print("job done!") return None if __name__ == '__main__': yuv2bgr(filename='xxx.yuv', height=1080, width=1920, startfrm=0) # BGR2NV21 def bgr2nv21(image_path, image_height, image_width): bgr = cv2.imread(image_path) B = [] G = [] R = [] YUV = [] for i in range(image_height): for j in range(image_width): B.append(bgr[i * image_width * 3 + j * 3]) G.append(bgr[i * image_width * 3 + j * 3 + 1]) R.append(bgr[i * image_width * 3 + j * 3 + 2]) y = ((74 * R + 150 * G + 29 * B) / 256) u = ((-43 * R - 84 * G + 112 * B) / 255) + 128 v = ((128 * R - 107 * G - 21 * B) / 255) + 128 YUV[i * image_width + j] = min(max(y, 0), 255) YUV[(i // 2 + image_height) * image_width + (j // 2) * 2] = min(max(v, 0), 255) YUV[(i // 2 + image_height) * image_width + (j // 2) * 2 + 1] = min(max(u, 0), 255) yuv_name = image_path.replace("png", "yuv") np.tofile(yuv_name) 
  • 3、OpenCV 中支持的颜色转换如下所示(py opencv 只需把 cv:: 换成 cv. 即可):
enum cv::ColorConversionCodes { 
    cv::COLOR_BGR2BGRA = 0, cv::COLOR_RGB2RGBA = COLOR_BGR2BGRA, cv::COLOR_BGRA2BGR = 1, cv::COLOR_RGBA2RGB = COLOR_BGRA2BGR, cv::COLOR_BGR2RGBA = 2, cv::COLOR_RGB2BGRA = COLOR_BGR2RGBA, cv::COLOR_RGBA2BGR = 3, cv::COLOR_BGRA2RGB = COLOR_RGBA2BGR, cv::COLOR_BGR2RGB = 4, cv::COLOR_RGB2BGR = COLOR_BGR2RGB, cv::COLOR_BGRA2RGBA = 5, cv::COLOR_RGBA2BGRA = COLOR_BGRA2RGBA, cv::COLOR_BGR2GRAY = 6, cv::COLOR_RGB2GRAY = 7, cv::COLOR_GRAY2BGR = 8, cv::COLOR_GRAY2RGB = COLOR_GRAY2BGR, cv::COLOR_GRAY2BGRA = 9, cv::COLOR_GRAY2RGBA = COLOR_GRAY2BGRA, cv::COLOR_BGRA2GRAY = 10, cv::COLOR_RGBA2GRAY = 11, cv::COLOR_BGR2BGR565 = 12, cv::COLOR_RGB2BGR565 = 13, cv::COLOR_BGR5652BGR = 14, cv::COLOR_BGR5652RGB = 15, cv::COLOR_BGRA2BGR565 = 16, cv::COLOR_RGBA2BGR565 = 17, cv::COLOR_BGR5652BGRA = 18, cv::COLOR_BGR5652RGBA = 19, cv::COLOR_GRAY2BGR565 = 20, cv::COLOR_BGR5652GRAY = 21, cv::COLOR_BGR2BGR555 = 22, cv::COLOR_RGB2BGR555 = 23, cv::COLOR_BGR5552BGR = 24, cv::COLOR_BGR5552RGB = 25, cv::COLOR_BGRA2BGR555 = 26, cv::COLOR_RGBA2BGR555 = 27, cv::COLOR_BGR5552BGRA = 28, cv::COLOR_BGR5552RGBA = 29, cv::COLOR_GRAY2BGR555 = 30, cv::COLOR_BGR5552GRAY = 31, cv::COLOR_BGR2XYZ = 32, cv::COLOR_RGB2XYZ = 33, cv::COLOR_XYZ2BGR = 34, cv::COLOR_XYZ2RGB = 35, cv::COLOR_BGR2YCrCb = 36, cv::COLOR_RGB2YCrCb = 37, cv::COLOR_YCrCb2BGR = 38, cv::COLOR_YCrCb2RGB = 39, cv::COLOR_BGR2HSV = 40, cv::COLOR_RGB2HSV = 41, cv::COLOR_BGR2Lab = 44, cv::COLOR_RGB2Lab = 45, cv::COLOR_BGR2Luv = 50, cv::COLOR_RGB2Luv = 51, cv::COLOR_BGR2HLS = 52, cv::COLOR_RGB2HLS = 53, cv::COLOR_HSV2BGR = 54, cv::COLOR_HSV2RGB = 55, cv::COLOR_Lab2BGR = 56, cv::COLOR_Lab2RGB = 57, cv::COLOR_Luv2BGR = 58, cv::COLOR_Luv2RGB = 59, cv::COLOR_HLS2BGR = 60, cv::COLOR_HLS2RGB = 61, cv::COLOR_BGR2HSV_FULL = 66, cv::COLOR_RGB2HSV_FULL = 67, cv::COLOR_BGR2HLS_FULL = 68, cv::COLOR_RGB2HLS_FULL = 69, cv::COLOR_HSV2BGR_FULL = 70, cv::COLOR_HSV2RGB_FULL = 71, cv::COLOR_HLS2BGR_FULL = 72, cv::COLOR_HLS2RGB_FULL = 73, cv::COLOR_LBGR2Lab = 74, cv::COLOR_LRGB2Lab = 75, cv::COLOR_LBGR2Luv = 76, cv::COLOR_LRGB2Luv = 77, cv::COLOR_Lab2LBGR = 78, cv::COLOR_Lab2LRGB = 79, cv::COLOR_Luv2LBGR = 80, cv::COLOR_Luv2LRGB = 81, cv::COLOR_BGR2YUV = 82, cv::COLOR_RGB2YUV = 83, cv::COLOR_YUV2BGR = 84, cv::COLOR_YUV2RGB = 85, // YUV 4:2:0 formats family cv::COLOR_YUV2RGB_NV12 = 90, cv::COLOR_YUV2BGR_NV12 = 91, cv::COLOR_YUV2RGB_NV21 = 92, cv::COLOR_YUV2BGR_NV21 = 93, cv::COLOR_YUV420sp2RGB = COLOR_YUV2RGB_NV21, cv::COLOR_YUV420sp2BGR = COLOR_YUV2BGR_NV21, cv::COLOR_YUV2RGBA_NV12 = 94, cv::COLOR_YUV2BGRA_NV12 = 95, cv::COLOR_YUV2RGBA_NV21 = 96, cv::COLOR_YUV2BGRA_NV21 = 97, cv::COLOR_YUV420sp2RGBA = COLOR_YUV2RGBA_NV21, cv::COLOR_YUV420sp2BGRA = COLOR_YUV2BGRA_NV21, cv::COLOR_YUV2RGB_YV12 = 98, cv::COLOR_YUV2BGR_YV12 = 99, cv::COLOR_YUV2RGB_IYUV = 100, cv::COLOR_YUV2BGR_IYUV = 101, cv::COLOR_YUV2RGB_I420 = COLOR_YUV2RGB_IYUV, cv::COLOR_YUV2BGR_I420 = COLOR_YUV2BGR_IYUV, cv::COLOR_YUV420p2RGB = COLOR_YUV2RGB_YV12, cv::COLOR_YUV420p2BGR = COLOR_YUV2BGR_YV12, cv::COLOR_YUV2RGBA_YV12 = 102, cv::COLOR_YUV2BGRA_YV12 = 103, cv::COLOR_YUV2RGBA_IYUV = 104, cv::COLOR_YUV2BGRA_IYUV = 105, cv::COLOR_YUV2RGBA_I420 = COLOR_YUV2RGBA_IYUV, cv::COLOR_YUV2BGRA_I420 = COLOR_YUV2BGRA_IYUV, cv::COLOR_YUV420p2RGBA = COLOR_YUV2RGBA_YV12, cv::COLOR_YUV420p2BGRA = COLOR_YUV2BGRA_YV12, cv::COLOR_YUV2GRAY_420 = 106, cv::COLOR_YUV2GRAY_NV21 = COLOR_YUV2GRAY_420, cv::COLOR_YUV2GRAY_NV12 = COLOR_YUV2GRAY_420, cv::COLOR_YUV2GRAY_YV12 = COLOR_YUV2GRAY_420, cv::COLOR_YUV2GRAY_IYUV = COLOR_YUV2GRAY_420, cv::COLOR_YUV2GRAY_I420 = COLOR_YUV2GRAY_420, cv::COLOR_YUV420sp2GRAY = COLOR_YUV2GRAY_420, cv::COLOR_YUV420p2GRAY = COLOR_YUV2GRAY_420, // YUV 4:2:2 formats family cv::COLOR_YUV2RGB_UYVY = 107, cv::COLOR_YUV2BGR_UYVY = 108, cv::COLOR_YUV2RGB_Y422 = COLOR_YUV2RGB_UYVY, cv::COLOR_YUV2BGR_Y422 = COLOR_YUV2BGR_UYVY, cv::COLOR_YUV2RGB_UYNV = COLOR_YUV2RGB_UYVY, cv::COLOR_YUV2BGR_UYNV = COLOR_YUV2BGR_UYVY, cv::COLOR_YUV2RGBA_UYVY = 111, cv::COLOR_YUV2BGRA_UYVY = 112, cv::COLOR_YUV2RGBA_Y422 = COLOR_YUV2RGBA_UYVY, cv::COLOR_YUV2BGRA_Y422 = COLOR_YUV2BGRA_UYVY, cv::COLOR_YUV2RGBA_UYNV = COLOR_YUV2RGBA_UYVY, cv::COLOR_YUV2BGRA_UYNV = COLOR_YUV2BGRA_UYVY, cv::COLOR_YUV2RGB_YUY2 = 115, cv::COLOR_YUV2BGR_YUY2 = 116, cv::COLOR_YUV2RGB_YVYU = 117, cv::COLOR_YUV2BGR_YVYU = 118, cv::COLOR_YUV2RGB_YUYV = COLOR_YUV2RGB_YUY2, cv::COLOR_YUV2BGR_YUYV = COLOR_YUV2BGR_YUY2, cv::COLOR_YUV2RGB_YUNV = COLOR_YUV2RGB_YUY2, cv::COLOR_YUV2BGR_YUNV = COLOR_YUV2BGR_YUY2, cv::COLOR_YUV2RGBA_YUY2 = 119, cv::COLOR_YUV2BGRA_YUY2 = 120, cv::COLOR_YUV2RGBA_YVYU = 121, cv::COLOR_YUV2BGRA_YVYU = 122, cv::COLOR_YUV2RGBA_YUYV = COLOR_YUV2RGBA_YUY2, cv::COLOR_YUV2BGRA_YUYV = COLOR_YUV2BGRA_YUY2, cv::COLOR_YUV2RGBA_YUNV = COLOR_YUV2RGBA_YUY2, cv::COLOR_YUV2BGRA_YUNV = COLOR_YUV2BGRA_YUY2, cv::COLOR_YUV2GRAY_UYVY = 123, cv::COLOR_YUV2GRAY_YUY2 = 124, cv::COLOR_YUV2GRAY_Y422 = COLOR_YUV2GRAY_UYVY, cv::COLOR_YUV2GRAY_UYNV = COLOR_YUV2GRAY_UYVY, cv::COLOR_YUV2GRAY_YVYU = COLOR_YUV2GRAY_YUY2, cv::COLOR_YUV2GRAY_YUYV = COLOR_YUV2GRAY_YUY2, cv::COLOR_YUV2GRAY_YUNV = COLOR_YUV2GRAY_YUY2, // alpha premultiplication cv::COLOR_RGBA2mRGBA = 125, cv::COLOR_mRGBA2RGBA = 126, cv::COLOR_RGB2YUV_I420 = 127, cv::COLOR_BGR2YUV_I420 = 128, cv::COLOR_RGB2YUV_IYUV = COLOR_RGB2YUV_I420, cv::COLOR_BGR2YUV_IYUV = COLOR_BGR2YUV_I420, cv::COLOR_RGBA2YUV_I420 = 129, cv::COLOR_BGRA2YUV_I420 = 130, cv::COLOR_RGBA2YUV_IYUV = COLOR_RGBA2YUV_I420, cv::COLOR_BGRA2YUV_IYUV = COLOR_BGRA2YUV_I420, cv::COLOR_RGB2YUV_YV12 = 131, cv::COLOR_BGR2YUV_YV12 = 132, cv::COLOR_RGBA2YUV_YV12 = 133, cv::COLOR_BGRA2YUV_YV12 = 134, cv::COLOR_BayerBG2BGR = 46, cv::COLOR_BayerGB2BGR = 47, cv::COLOR_BayerRG2BGR = 48, cv::COLOR_BayerGR2BGR = 49, cv::COLOR_BayerBG2RGB = COLOR_BayerRG2BGR, cv::COLOR_BayerGB2RGB = COLOR_BayerGR2BGR, cv::COLOR_BayerRG2RGB = COLOR_BayerBG2BGR, cv::COLOR_BayerGR2RGB = COLOR_BayerGB2BGR, cv::COLOR_BayerBG2GRAY = 86, cv::COLOR_BayerGB2GRAY = 87, cv::COLOR_BayerRG2GRAY = 88, cv::COLOR_BayerGR2GRAY = 89, cv::COLOR_BayerBG2BGR_VNG = 62, cv::COLOR_BayerGB2BGR_VNG = 63, cv::COLOR_BayerRG2BGR_VNG = 64, cv::COLOR_BayerGR2BGR_VNG = 65, cv::COLOR_BayerBG2RGB_VNG = COLOR_BayerRG2BGR_VNG, cv::COLOR_BayerGB2RGB_VNG = COLOR_BayerGR2BGR_VNG, cv::COLOR_BayerRG2RGB_VNG = COLOR_BayerBG2BGR_VNG, cv::COLOR_BayerGR2RGB_VNG = COLOR_BayerGB2BGR_VNG, cv::COLOR_BayerBG2BGR_EA = 135, cv::COLOR_BayerGB2BGR_EA = 136, cv::COLOR_BayerRG2BGR_EA = 137, cv::COLOR_BayerGR2BGR_EA = 138, cv::COLOR_BayerBG2RGB_EA = COLOR_BayerRG2BGR_EA, cv::COLOR_BayerGB2RGB_EA = COLOR_BayerGR2BGR_EA, cv::COLOR_BayerRG2RGB_EA = COLOR_BayerBG2BGR_EA, cv::COLOR_BayerGR2RGB_EA = COLOR_BayerGB2BGR_EA, cv::COLOR_BayerBG2BGRA = 139, cv::COLOR_BayerGB2BGRA = 140, cv::COLOR_BayerRG2BGRA = 141, cv::COLOR_BayerGR2BGRA = 142, cv::COLOR_BayerBG2RGBA = COLOR_BayerRG2BGRA, cv::COLOR_BayerGB2RGBA = COLOR_BayerGR2BGRA, cv::COLOR_BayerRG2RGBA = COLOR_BayerBG2BGRA, cv::COLOR_BayerGR2RGBA = COLOR_BayerGB2BGRA, cv::COLOR_COLORCVT_MAX = 143 } 

五、libyuv 库的编译和使用

1、libyuv 下载

  • git 下载:可通过 README.chromium 查看版本号
    • 非官方可用版本:git clone https://github.com/lemenkov/libyuv
    • 官网:git clone https://chromium.googlesource.com/libyuv/libyuv
  • 或者通过网页下载压缩包:https://chromium.googlesource.com/libyuv/libyuv/+/refs/heads/master
    在这里插入图片描述

2、libyuv 编译

# 编译指定平台的库,只需要把相应的编译器换一下就好了 # 1、使用 make 来编译(默认为 gcc/g++ 编译器,生成静态库 libyuv.a) cd libyuv-master/ vim linux.mk # 更改为自己的编译器和编译选项  CC=/home/manzp/projects/19.sigmastar/gcc-sigmastar-9.1.0-2020.07-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf-sigmastar-9.1.0-gcc CFLAGS:=-mcpu=cortex-a53 -fno-aggressive-loop-optimizations -O3 -fomit-frame-pointer -ffast-math -Wall -fPIC -fpermissive -mfpu=neon-vfpv4 CFLAGS+=-Iinclude/ CXX=/home/manzp/projects/19.sigmastar/gcc-sigmastar-9.1.0-2020.07-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf-sigmastar-9.1.0-g++ CXXFLAGS:=-mcpu=cortex-a53 -fno-aggressive-loop-optimizations -O3 -fomit-frame-pointer -ffast-math -Wall -fPIC -fpermissive -mfpu=neon-vfpv4 CXXFLAGS+=-Iinclude/ # 编译和重新编译 make -f linux.mk -j16 # 默认生成静态库 make -f linux.mk clean # 使用不同的编译器重新编译前记得 clean 一下 # 2、使用 cmake 来编译,默认 debug build,out 下面有 libyuv.a libyuv.so 及 yuvconvert 产生 mkdir out cd out cmake .. cmake --build . # release build/install,如果想用自己的编译器,加上 -DCROSS_COMPILE=arm-himix200-linux mkdir out cd out cmake -DCMAKE_INSTALL_PREFIX="/usr/lib" -DCMAKE_BUILD_TYPE="Release" .. cmake --build . --config Release sudo cmake --build . --target install --config Release # 3、使用 ndk-build 来编译 mkdir prj_android_build/jni cp Android.mk prj_android_build/jni # 修改编译路径 # 新增Application.mk /opt/android-ndk-r21e/ndk-build 

3、libyuv 使用

libyuv is an open source project that includes YUV scaling and conversion functionality.

  • Scale YUV to prepare content for compression, with point, bilinear or box filter.
  • Convert to YUV from webcam formats for compression.
  • Convert to RGB formats for rendering/effects.
  • Rotate by 90/180/270 degrees to adjust for mobile devices in portrait mode.
  • Optimized for SSSE3/AVX2 on x86/x64
  • Optimized for Neon on Arm.
  • Optimized for MSA on Mips.
  • 将 libyuv.h 包含到工程,直接调用转换函数即可
void NV12Crop(uint8_t *src_y, uint8_t *src_uv, int src_stride_y, int* roi_x, int* roi_y, int* roi_w, int* roi_h, uint8_t *dst_yuv) { 
    // 裁剪的坐标 X 和 Y 必须是偶数,否则可能得到 NV21 // 裁剪的宽和高必须是偶数(2n*2n*1.5 = 6n2),正好是 6 的倍数(每四个 Y 对应一个 UV) *roi_x = (*roi_x % 2) ? (*roi_x - 1) : *roi_x; *roi_y = (*roi_y % 2) ? (*roi_y - 1) : *roi_y; *roi_w = (*roi_w % 2) ? (*roi_w - 1) : *roi_w; *roi_h = (*roi_h % 2) ? (*roi_h - 1) : *roi_h; // 取得 SRC YUV 首地址 unsigned char *src_roi_y = src_y + src_stride_y * (*roi_y) + (*roi_x); // NV12在竖直方向上进行了 1/2 下采样,水平方向上并未做下采样,所以只需 roi_y 减半 unsigned char *src_roi_uv = src_uv + src_stride_y * (*roi_y) / 2 + (*roi_x); unsigned char *dst_y = dst_yuv; unsigned char *dst_uv = dst_yuv + (*roi_w) * (*roi_h); for (int h = 0; h < *roi_h; h++) { 
    // ROI 按行 copy Y memcpy(dst_y + (*roi_w) * h, src_roi_y + src_stride_y * h, *roi_w); if (h < *roi_h / 2) { 
    // ROI 按行 copy UV memcpy(dst_uv + (*roi_w) * h, src_roi_uv + src_stride_y * h, *roi_w); } } } void NV12RoiScaleToBGRHWC(uint8_t *src_y, uint8_t *src_uv, uint32_t src_y_stride, uint32_t roi_x, uint32_t roi_y, uint32_t roi_w, uint32_t roi_h, uint8_t *dst_nv12_buffer, uint8_t *dst_bgr_hwc_buffer, uint32_t dst_w, uint32_t dst_h) { 
    // 裁剪的坐标 X 和 Y 必须是偶数,否则可能得到 NV21 // 裁剪的宽和高必须是偶数(2n*2n*1.5 = 6n2),正好是 6 的倍数(每四个 Y 对应一个 UV),roi 坐标不需要返回 roi_x = (roi_x % 2) ? (roi_x - 1) : roi_x; roi_y = (roi_y % 2) ? (roi_y - 1) : roi_y; roi_w = (roi_w % 2) ? (roi_w - 1) : roi_w; roi_h = (roi_h % 2) ? (roi_h - 1) : roi_h; // 取得 roi 区域 y 的首地址 // 取得 roi 区域的 uv 首地址,由于 uv 在竖直方向上进行了 1/2 下采样,水平方向上并未做下采样,所以只需 roi_y 减半 uint8_t *src_roi_y = src_y + src_y_stride * roi_y + roi_x; uint8_t *src_roi_uv = src_uv + src_y_stride * roi_y / 2 + roi_x; // dst nv12 tmp buffer  uint8_t *dst_y = dst_nv12_buffer; uint8_t *dst_uv = dst_nv12_buffer + dst_w * dst_h; // src roi nv12 resize-->dst nv12 tmp buffer libyuv::ScalePlane(src_roi_y, src_y_stride, roi_w, roi_h, dst_y, dst_w, dst_w, dst_h, libyuv::kFilterBilinear); libyuv::ScalePlane_16((uint16_t *) src_roi_uv, src_y_stride / 2, (roi_w) / 2, (roi_h) / 2, (uint16_t *) dst_uv, dst_w / 2, dst_w / 2, dst_h / 2, libyuv::kFilterNone); // dst nv12 tmp buffer-->dst bgr hwc buffer // libyuv::NV12ToRGB24(dst_y, dst_w, dst_uv, dst_w, dst_bgr_hwc_buffer, dst_w * 3, dst_w, dst_h); libyuv::NV12ToRGB24Matrix(dst_y, dst_w, dst_uv, dst_w, dst_bgr_hwc_buffer, dst_w * 3, &libyuv::kYuvI601Constants, // 默认是 BT.601 limited; BT.601 full(kYuvJPEGConstants) 会暗一点 dst_w, dst_h); } // dst_tmp_buffer 要比 dst_bgr_hwc_buffer 开辟的大一些 int NV12RoiToBGRHWCPerspective(unsigned char *src_y, unsigned char *src_uv, int src_y_stride, int left_top_x, int left_top_y, int right_top_x, int right_top_y, int left_bottom_x, int left_bottom_y, int right_bottom_x, int right_bottom_y, unsigned char *dst_tmp_buffer, unsigned char *dst_bgr_hwc_buffer, int dst_w, int dst_h) { 
    // 上下左右各自外扩 10 个像素(防止旋转出现黑边),为透视变换做准备 int offset = 10; int roi_x = int(left_top_x - offset); int roi_y = int(left_top_y - offset); int roi_w = int(right_bottom_x - left_top_x + 1 + 2 * offset); int roi_h = int(right_bottom_y - left_top_y + 1 + 2 * offset); // 边界情况处理;roi_x + roi_w > src_w; roi_y + roi_h > src_h 情况待处理 roi_x = (roi_x < 0) ? 0 : roi_x; // 防止扩大后越界 roi_y = (roi_y < 0) ? 0 : roi_y; // 裁剪的坐标 X 和 Y 必须是偶数,否则可能得到 NV21 // 裁剪的宽和高必须是偶数(2n*2n*1.5 = 6n2),正好是 6 的倍数(每四个 Y 对应一个 UV) roi_x = (roi_x % 2) ? (roi_x - 1) : roi_x; roi_y = (roi_y % 2) ? (roi_y - 1) : roi_y; roi_w = (roi_w % 2) ? (roi_w - 1) : roi_w; roi_h = (roi_h % 2) ? (roi_h - 1) : roi_h; // 防止 roi 区域过大 printf("roi x y w h is %d %d %d %d\n", roi_x, roi_y, roi_w, roi_h); if (roi_w * roi_h >= 512 * 512) { 
    printf("plate size %d, is too big!\n", roi_w * roi_h); return -1; } // 取得 roi 区域 y 的首地址 // 取得 roi 区域的 uv 首地址,由于 uv 在竖直方向上进行了 1/2 下采样,水平方向上并未做下采样,所以只需 roi_y 减半 unsigned char *src_roi_y = src_y + src_y_stride * roi_y + roi_x; unsigned char *src_roi_uv = src_uv + src_y_stride * roi_y / 2 + roi_x; // libyuv::NV12ToRGB24(src_roi_y, src_y_stride, src_roi_uv, src_y_stride, dst_tmp_buffer, roi_w * 3, roi_w, roi_h); // roi nv12 to tmp bgr hwc buffer libyuv::NV12ToRGB24Matrix(src_roi_y, src_y_stride, src_roi_uv, src_y_stride, dst_tmp_buffer, roi_w * 3, &libyuv::kYuvI601Constants, // 默认是 BT.601 limited; BT.601 full(kYuvJPEGConstants) 会暗一点 roi_w, roi_h); // 透视变换 KIM_PerspectiveTransform(dst_tmp_buffer, roi_w, roi_h, dst_bgr_hwc_buffer, dst_w, dst_h, left_top_x - roi_x, left_top_y - roi_y, right_top_x - roi_x, right_top_y - roi_y, left_bottom_x - roi_x, left_bottom_y - roi_y, right_bottom_x - roi_x, right_bottom_y - roi_y, 1); // 需输入相对位置 return 0; } // dst_tmp_buffer 要比 dst_bgr_hwc_buffer 开辟的大一些 int NV12RoiToRGBHWCPerspective(unsigned char *src_y, unsigned char *src_uv, int src_y_stride, int left_top_x, int left_top_y, int right_top_x, int right_top_y, int left_bottom_x, int left_bottom_y, int right_bottom_x, int right_bottom_y, unsigned char *dst_tmp_buffer, unsigned char *dst_bgr_hwc_buffer, int dst_w, int dst_h) { 
    // 上下左右各自外扩 10 个像素(防止旋转出现黑边),为透视变换做准备 int offset = 10; int roi_x = int(left_top_x - offset); int roi_y = int(left_top_y - offset); int roi_w = int(right_bottom_x - left_top_x + 1 + 2 * offset); int roi_h = int(right_bottom_y - left_top_y + 1 + 2 * offset); // 边界情况处理;roi_x + roi_w > src_w; roi_y + roi_h > src_h 情况待处理 roi_x = (roi_x < 0) ? 0 : roi_x; // 防止扩大后越界 roi_y = (roi_y < 0) ? 0 : roi_y; // 裁剪的坐标 X 和 Y 必须是偶数,否则可能得到 NV21 // 裁剪的宽和高必须是偶数(2n*2n*1.5 = 6n2),正好是 6 的倍数(每四个 Y 对应一个 UV) roi_x = (roi_x % 2) ? (roi_x - 1) : roi_x; roi_y = (roi_y % 2) ? (roi_y - 1) : roi_y; roi_w = (roi_w % 2) ? (roi_w - 1) : roi_w; roi_h = (roi_h % 2) ? (roi_h - 1) : roi_h; printf("roi x y w h is %d %d %d %d\n", roi_x, roi_y, roi_w, roi_h); // 防止 roi 区域过大 if (roi_w * roi_h >= 512 * 512) { 
    printf("plate size %d, is too big!\n", roi_w * roi_h); return -1; } // 取得 roi 区域 y 的首地址 // 取得 roi 区域的 uv 首地址,由于 uv 在竖直方向上进行了 1/2 下采样,水平方向上并未做下采样,所以只需 roi_y 减半 unsigned char *src_roi_y = src_y + src_y_stride * roi_y + roi_x; unsigned char *src_roi_uv = src_uv + src_y_stride * roi_y / 2 + roi_x; // libyuv::NV12ToRGB24(src_roi_y, src_y_stride, src_roi_uv, src_y_stride, dst_tmp_buffer, roi_w * 3, roi_w, roi_h); // roi nv12 to tmp rgb hwc buffer libyuv::NV21ToRGB24Matrix(src_roi_y, src_y_stride, src_roi_uv, src_y_stride, dst_tmp_buffer, roi_w * 3, &libyuv::kYuvI601Constants, // 默认是 BT.601 limited; BT.601 full(kYuvJPEGConstants) 会暗一点 roi_w, roi_h); // 透视变换 KIM_PerspectiveTransform(dst_tmp_buffer, roi_w, roi_h, dst_bgr_hwc_buffer, dst_w, dst_h, left_top_x - roi_x, left_top_y - roi_y, right_top_x - roi_x, right_top_y - roi_y, left_bottom_x - roi_x, left_bottom_y - roi_y, right_bottom_x - roi_x, right_bottom_y - roi_y, 1); // 需输入相对位置 return 0; } 
  • 透视变换输入对齐图像不外扩(产生黑边且截断)与外扩的区别:
    在这里插入图片描述在这里插入图片描述

六、参考资料

1、详解YUV数据格式
2、YUV 格式详解,只看这一篇就够了
3、图像原始格式(YUV444 YUV422 YUV420)一探究竟
4、libyuv库简单使用
5、详解 YUV,一文搞定 YUV 是什么!
6、ARMv8 SIMD和浮点指令编程:Libyuv I420 转 ARGB 流程分析

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/141875.html

(0)
上一篇 2025-05-14 14:15
下一篇 2025-05-14 14:26

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信