【问题标题】:How to convert sRGB to NV12 format using NumPy?如何使用 NumPy 将 sRGB 转换为 NV12 格式?
【发布时间】:2019-07-15 16:06:43
【问题描述】:

NV12 格式定义了具有 420 次采样的 YUV 颜色空间的特定颜色通道排序。
NV12 格式主要用于视频编码/解码管道。

libyuv description of NV12

NV12 是一种双平面格式,具有一个全尺寸 Y 平面,后跟一个具有编织 U 和 V 值的单个色度平面。 NV21 相同,但具有编织的 V 和 U 值。 NV12 中的 12 是指每像素 12 位。 NV12 具有半宽半高色度通道,因此是 420 次采样。

在NV12上下文中,YUV格式主要指YCbCr色彩空间。
NV12 元素是每个元素 8 位(uint8 类型)。
在这篇文章的上下文中,YUV 元素处于“有限范围”标准中:Y 范围是 [16, 235],U,V 范围是 [16, 240]。

sRGB(标准红绿蓝)是 PC 系统使用的标准色彩空间。
在帖子的上下文中,sRGB 颜色分量范围是 [0, 255](uint8 类型)。
RGB 元素排序与帖子无关(假设 3 个颜色平面)。

目前至少有 2 种可能的 YCbCr 格式应用 NV12:

  • BT.601 - 应用标清电视。
  • BT.709 - 适用于高清电视。

NV12 元素排序示例:
YYYYYY
YYYYYY
UVUVUV

RGB 到 NV12 的转换可以分为以下几个阶段:

  • 色彩空间转换 - 从 sRGB 转换为 YUV 色彩空间。
  • 色度下采样 - 在每个轴上将 U、V 通道缩小 x2 倍(从 YUV444 转换为 YUV420)。
  • 色度元素交错 - 将 U、V 元素排列为 U、V、U、V...

下图说明了应用 6x6 像素图像大小的转换阶段:

我们如何使用 NumPy 将 sRGB 转换为 NV12?

注意:
该问题涉及演示转换过程的 Python 实现(帖子不适用于 OpenCV 实现等现有功能)。

【问题讨论】:

    标签: python numpy image-processing video-processing nv12-nv21


    【解决方案1】:

    使用 NumPy 将 sRGB 转换为 NV12 格式

    帖子的目的是演示转换过程。
    下面的 Python 实现使用 NumPy,并故意避免使用 OpenCV。

    RGB 到 NV12 的转换阶段:

    • 色彩空间转换 - 从 sRGB 转换为 YUV 色彩空间:
      使用 sRGB 到 YCbCr 的转换公式。
      将每个 RGB 三元组乘以 3x3 转换矩阵,并添加一个包含 3 个偏移量的向量。
      该帖子显示了 BT.709 和 BT.601 转换(唯一的区别是系数矩阵)。
    • 色度下采样 - 在每个轴上将 U、V 通道缩小 x2 倍(从 YUV444 转换为 YUV420)。
      该实现使用双线性插值在每个轴上将 U、V 的大小调整为 0.5。
      注意:双线性插值不是最佳的下采样方法,但通常已经足够了。
      代码不使用cv2.resize,而是使用每2x2像素的平均值(结果相当于双线性插值)。
      注意:如果输入分辨率在两个维度上都不是,则实现失败。
    • 色度元素交错 - 将 U、V 元素排列为 U、V、U、V...
      通过数组索引操作实现。

    这是一个将 RGB 转换为 NV12 标准的 Python 代码示例:

    import numpy as np
    import matplotlib.pyplot as plt
    import matplotlib.image as mpimg
    
    do_use_bt709 = True; # True for BT.709, False for BT.601
    
    RGB = mpimg.imread('rgb_input.png')*255.0     # Read RGB input image, multiply by 255 (set RGB range to [0, 255]).
    R, G, B = RGB[:, :, 0], RGB[:, :, 1], RGB[:, :, 2]  # Split RGB to R, G and B numpy arrays.
    rows, cols = R.shape
    
    # I. Convert RGB to YUV (convert sRGB to YUV444)
    #################################################
    if do_use_bt709:
        # Convert sRGB to YUV, BT.709 standard
        # Conversion formula used: 8 bit sRGB to "limited range" 8 bit YUV (BT.709).
        Y =  0.18258588*R + 0.61423059*G + 0.06200706*B + 16.0
        U = -0.10064373*R - 0.33857195*G + 0.43921569*B + 128.0
        V =  0.43921569*R - 0.39894216*G - 0.04027352*B + 128.0
    else:
        # Convert sRGB to YUV, BT.601 standard.
        # Conversion formula used: 8 bit sRGB to "limited range" 8 bit YUV (BT.601).
        Y =  0.25678824*R + 0.50412941*G + 0.09790588*B + 16.0
        U = -0.14822290*R - 0.29099279*G + 0.43921569*B + 128.0
        V =  0.43921569*R - 0.36778831*G - 0.07142737*B + 128.0
    
    
    # II. U,V Downsampling (convert YUV444 to YUV420)
    ##################################################
    # Shrink U and V channels by a factor of x2 in each axis (use bi-linear interpolation).
    #shrunkU = cv2.resize(U, dsize=(cols//2, rows//2), interpolation=cv2.INTER_LINEAR)
    #shrunkV = cv2.resize(V, dsize=(cols//2, rows//2), interpolation=cv2.INTER_LINEAR)
    
    # Each element of shrunkU is the mean of 2x2 elements of U
    # Result is equvalent to resize by a factor of 0.5 with bi-linear interpolation.
    shrunkU = (U[0: :2, 0::2] + U[1: :2, 0: :2] + U[0: :2, 1: :2] + U[1: :2, 1: :2]) * 0.25
    shrunkV = (V[0: :2, 0::2] + V[1: :2, 0: :2] + V[0: :2, 1: :2] + V[1: :2, 1: :2]) * 0.25
    
    
    # III. U,V Interleaving
    ########################
    # Size of UV plane is half the number of rows, and same number of columns as Y plane.
    UV = np.zeros((rows//2, cols))  # Use // for integer division.
    
    # Interleave shrunkU and shrunkV and build UV palne (each row of UV plane is u,v,u,u,v...)
    UV[:, 0 : :2] = shrunkU
    UV[:, 1 : :2] = shrunkV
    
    # Place Y plane at the top, and UV plane at the bottom (number of rows NV12 matrix is rows*1.5)
    NV12 = np.vstack((Y, UV))
    
    # Round NV12, and cast to uint8 (use floor(x+0.5) instead of round to avoid "bankers rounding").
    NV12 = np.floor(NV12 + 0.5).astype('uint8')
    
    
    # Write NV12 array to binary file
    NV12.tofile('nv12_output.raw')
    
    # Display NV12 result (display as Grayscale image).
    plt.figure()
    plt.axis('off')
    plt.imshow(NV12, cmap='gray', interpolation='nearest')
    plt.show()
    

    示例 RGB 输入图像:

    NV12 结果(显示为灰度图):

    【讨论】:

      猜你喜欢
      • 2013-08-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-11-25
      • 2016-06-27
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多