Как конвертировать sRGB в формат NV12 с помощью NumPy?

Формат NV12 определяет порядок конкретных цветовых каналов в цветовом пространстве YUV с подвыборкой 420.
Формат NV12 в основном используется в конвейере кодирования / декодирования видео.

либюв описание NV12:

NV12 - это бипланарный формат с полноразмерной плоскостью Y, за которой следует одна плоскость цветности с сотканными значениями U и V. NV21 такой же, но с сотканными значениями V и U. 12 в NV12 относится к 12 битам на пиксель. NV12 имеет канал цветности половинной ширины и половинной высоты, и, следовательно, является подвыбором 420.

В контексте NV12 формат YUV в основном называется цветовым пространством YCbCr.
Элементы NV12 имеют 8 бит на элемент (uint8 тип).
В контексте поста, элементы YUV соответствуют стандарту "ограниченного диапазона": диапазон Y равен [16, 235], диапазон U,V равен [16, 240].

sRGB (стандарт Red Green Blue) - это стандартное цветовое пространство, используемое системами ПК.
В контексте публикации диапазон цветовых компонентов sRGB равен [0, 255] (uint8 тип).
Порядок элементов RGB не имеет отношения к посту (предположим, 3 цветовых плоскости).

В настоящее время существует как минимум 2 возможных формата YCbCr с применением NV12:

  • BT.601 - применяется SDTV.
  • BT.709 - применяется HDTV.

Пример заказа элементов NV12:
YYYYYY
YYYYYY
UVUVUV

Преобразование RGB в NV12 можно описать следующими этапами:

  • Преобразование цветового пространства - преобразование из цветового пространства sRGB в YUV.
  • Уменьшение цветности - сжатие каналов U,V в x2 раза по каждой оси (преобразование из YUV444 в YUV420).
  • Чередование цветовых элементов - расположите элементы U,V как U,V,U,V...

На следующем рисунке показаны этапы преобразования с применением размера изображения 6x6 пикселей:

Как мы можем конвертировать sRGB в NV12, используя NumPy?

Примечание:
Вопрос касается реализации Python, которая демонстрирует процесс преобразования (публикация не предназначена для существующей функции, такой как реализация OpenCV).

1 ответ

Решение

Преобразование sRGB в формат NV12 с использованием NumPy

Целью поста является демонстрация процесса конвертации.
Приведенная ниже реализация Python использует NumPy и намеренно избегает использования OpenCV.

Этапы преобразования RGB в NV12:

  • Преобразование цветового пространства - преобразование из цветового пространства sRGB в YUV:
    Используйте формулу преобразования sRGB в YCbCr.
    Умножьте каждую матрицу преобразования RGB на 3 × 3 и добавьте вектор из 3 смещений.
    В посте показаны преобразования как BT.709, так и BT.601 (единственная разница - матрица коэффициентов).
  • Уменьшение цветности - сжатие каналов U,V в x2 раза по каждой оси (преобразование из YUV444 в YUV420).
    Реализация изменяет размеры U,V в 0,5 раза по каждой оси, используя билинейную интерполяцию.
    Примечание: билинейная интерполяция не является оптимальным методом понижающей дискретизации, но обычно достаточно хорош.
    Вместо того, чтобы использовать cv2.resize, код использует среднее из каждых 2x2 пикселей (результат эквивалентен билинейной интерполяции).
    Примечание. Реализация завершается неудачно, если разрешение входных данных не совпадает в обоих измерениях
  • Чередование цветовых элементов - расположите элементы U,V как U,V,U,V...
    Реализовано манипулированием индексацией массива.

Вот пример кода Python для преобразования RGB в стандарт NV12:

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 (отображается как изображение в градациях серого):

Другие вопросы по тегам