Как отменить искажение данных изображения I420? Эффективно

Я могу успешно отменить искажение изображения RGB.

Теперь я работаю над прямым неискажением данных I420, вместо того, чтобы сначала преобразовывать их в RGB.

Ниже приведены шаги, которые я выполнил после калибровки камеры.

K = cv::Matx33d(541.2152931632737, 0.0, 661.7479652584254,
                0.0, 541.0606969363056, 317.4524205037745,
                0.0,               0.0,               1.0);
D = cv::Vec4d(-0.042166406281296365, -0.001223961942208027, -0.0017036710622692108, 0.00023929900459453295);
newSize = cv::Size(3400, 1940);
cv::Matx33d new_K;
cv::fisheye::estimateNewCameraMatrixForUndistortRectify(K, D, cv::Size(W, H), cv::Mat::eye(3, 3, CV_64F), new_K, 1, newSize);    // W,H are the distorted image size
cv::fisheye::initUndistortRectifyMap(K, D, cv::Mat::eye(3, 3, CV_64F), new_K, newSize, CV_16SC2, mapx, mapy);

cv::remap(src, dst, mapx, mapy, cv::INTER_LINEAR);

Приведенный выше код успешно дает мне неискаженное изображение.

Теперь я хочу отменить искажение данных I420. Итак, теперь моим src будет данные I420 / YV12. Как я могу отменить искажение данных I420, не конвертируя их сначала в RGB?

Кстати, I420 - это формат изображения только с 1 каналом (в отличие от 3 каналов в RGB). Он имеет высоту = 1,5 * высота изображения. Его ширина равна ширине изображения.

Ниже приведен код для преобразования I420 в BGR.

cvtColor(src, BGR, CV_YUV2BGR_I420, 3);

BGR - расположение пикселей  BGR I420 - расположение пикселей  I


person Jai    schedule 23.01.2020    source источник


Ответы (2)


Наиболее эффективным решением является изменение размеров mapx и mapy и применение сжатых карт к каналам U и V с пониженной дискретизацией:

  • Сжимайте mapx и mapy в 2 раза по каждой оси - создавайте матрицы карт меньшего размера.
  • Разделите все элементы уменьшенных карт на 2 (применяется изображение с более низким разрешением).
  • Примените mapx и mapy к цветному каналу Y.
  • Примените shrunk_mapx и shrunk_mapy к цветным каналам U и V с пониженной дискретизацией.

Вот пример кода Python OpenCV (пожалуйста, прочтите комментарии):

import cv2 as cv
import numpy as np

# For the example, read Y, U and V as separate images.
srcY = cv.imread('DistortedChessBoardY.png', cv.IMREAD_GRAYSCALE) #  Y color channel (1280x720)
srcU = cv.imread('DistortedChessBoardU.png', cv.IMREAD_GRAYSCALE) #  U color channel (640x360)
srcV = cv.imread('DistortedChessBoardV.png', cv.IMREAD_GRAYSCALE) #  V color channel (640x360)

H, W = srcY.shape[0], srcY.shape[1]

K = np.array([[541.2152931632737, 0.0, 661.7479652584254],      
              [0.0, 541.0606969363056, 317.4524205037745],
              [0.0,               0.0,               1.0]])

D = np.array([-0.042166406281296365, -0.001223961942208027, -0.0017036710622692108, 0.00023929900459453295])

# newSize = cv::Size(3400, 1940);
newSize = (850, 480)

# cv::Matx33d new_K;
new_K = np.eye(3)

# cv::fisheye::estimateNewCameraMatrixForUndistortRectify(K, D, cv::Size(W, H), cv::Mat::eye(3, 3, CV_64F), new_K, 1, newSize);    // W,H are the distorted image size
new_K = cv.fisheye.estimateNewCameraMatrixForUndistortRectify(K, D, (W, H), np.eye(3), new_K, 1, newSize)

# cv::fisheye::initUndistortRectifyMap(K, D, cv::Mat::eye(3, 3, CV_64F), new_K, newSize, CV_16SC2, mapx, mapy);
mapx, mapy = cv.fisheye.initUndistortRectifyMap(K, D, np.eye(3), new_K, newSize, cv.CV_16SC2);

# cv::remap(src, dst, mapx, mapy, cv::INTER_LINEAR);
dstY = cv.remap(srcY, mapx, mapy, cv.INTER_LINEAR)

# Resize mapx and mapy by a factor of x2 in each axis, and divide each element in the map by 2
shrank_mapSize = (mapx.shape[1]//2, mapx.shape[0]//2)
shrunk_mapx = cv.resize(mapx, shrank_mapSize, interpolation = cv.INTER_LINEAR) // 2
shrunk_mapy = cv.resize(mapy, shrank_mapSize, interpolation = cv.INTER_LINEAR) // 2

# Remap U and V using shunk maps
dstU = cv.remap(srcU, shrunk_mapx, shrunk_mapy, cv.INTER_LINEAR, borderValue=128)
dstV = cv.remap(srcV, shrunk_mapx, shrunk_mapy, cv.INTER_LINEAR, borderValue=128)

cv.imshow('dstY', dstY)
cv.imshow('dstU', dstU)
cv.imshow('dstV', dstV)

cv.waitKey(0)
cv.destroyAllWindows()

Результат:

Y:
 dstY

U:
 dstU

V:
 dstV

После преобразования в RGB:
 RGB


Соображения по реализации C ++:

Поскольку формат I420 упорядочивает Y, U и V как 3 непрерывных плоскости в памяти, просто установить указатель на каждую «плоскость» и рассматривать ее как изображение в градациях серого.
Применяется тот же порядок данных выходное изображение - установить 3 указателя на выходные "плоскости".

Иллюстрация (предполагая, что ширина и высота равны, и предполагаем, что байтовый шаг равен ширине):

srcY -> YYYYYYYY           dstY -> YYYYYYYYYYYY
        YYYYYYYY                   YYYYYYYYYYYY
        YYYYYYYY                   YYYYYYYYYYYY
        YYYYYYYY                   YYYYYYYYYYYY
        YYYYYYYY   remap           YYYYYYYYYYYY
        YYYYYYYY  ======>          YYYYYYYYYYYY
srcU -> UUUU                       YYYYYYYYYYYY
        UUUU               dstU -> YYYYYYYYYYYY
        UUUU                       UUUUUU
srcV -> VVVV                       UUUUUU
        VVVV                       UUUUUU
        VVVV                       UUUUUU
                           dstV -> VVVVVV
                                   VVVVVV
                                   VVVVVV
                                   VVVVVV

Реализация приведенного выше рисунка - C ++

Предполагая, что ширина и высота равны, а байтовый шаг равен ширине, вы можете использовать следующий пример C ++ для преобразования I420 в плоскости Y, U и V:

Предположим: srcI420 - это Wx(H*3/2) матрица в формате I420, например cv::Mat srcI420(cv::Size(W, H * 3 / 2), CV_8UC1);.

int W = 1280, H = 720;  //Assume resolution of Y plane is 1280x720

//Pointer to Y plane
unsigned char *pY = (unsigned char*)srcI420.data;

//Y plane as cv::Mat, resolution of srcY is 1280x720
cv::Mat srcY = cv::Mat(cv::Size(W, H), CV_8UC1, (void*)pY);

//U plane as cv::Mat, resolution of srcU is 640x360 (in memory buffer, U plane is placed after Y).
cv::Mat srcU = cv::Mat(cv::Size(W/2, H/2), CV_8UC1, (void*)(pY + W*H));

//V plane as cv::Mat, resolution of srcV is 640x360 (in memory buffer, V plane is placed after U).
cv::Mat srcV = cv::Mat(cv::Size(W / 2, H / 2), CV_8UC1, (void*)(pY + W*H + (W/2*H/2)));

//Display srcY, srcU, srcV for testing
cv::imshow("srcY", srcY);
cv::imshow("srcU", srcU);
cv::imshow("srcV", srcV);
cv::waitKey(0);

В приведенном выше примере используются манипуляции с указателями без необходимости копирования данных.
Вы можете использовать те же манипуляции с указателями для целевого изображения I420.

Примечание. Решение будет работать в большинстве случаев, но не во всех случаях.

person Rotem    schedule 23.01.2020
comment
Спасибо за этот замечательный ответ. Что касается реализации C ++, то это похоже на 5 плоскостей. 1 большой и 4 маленьких самолета. Легко указать заголовок для большого Mat(H, W, CV_8UC1, i420.data). Как можно указать заголовок для маленьких. Проблема в том, что они соединены по горизонтали. - person Jai; 24.01.2020
comment
Поверьте, есть 3 плоскости: Y плоскость 1280x720, затем U плоскость 640x360, затем V плоскость 640x360. При отображении U и V, как если бы их ширина была 1280, вы получаете что-то, похожее на 5 плоскостей. - person Rotem; 24.01.2020
comment
Я добавил образец кода C ++, чтобы продемонстрировать, как реализовать разделение I420 на плоскости Y, U, V. - person Rotem; 24.01.2020
comment
Я понял, что вы пытались сказать о существовании трех самолетов, а не пяти. Чередующиеся ряды U-плоскости разделяются и преобразуются в два U-изображения. То же самое и с самолетом V. Спасибо за хорошо составленный ответ. - person Jai; 24.01.2020

РЕДАКТИРОВАТЬ: компоненты не чередуются в формате YV12, поэтому следующее не будет работать:

Если данные YV12 представляют собой одноканальное изображение, интерполяция операции remap применяется к значению, представленному всеми тремя данными YUV вместо отдельных компонентов Y, U и V.

Поэтому, грубо говоря, вместо того, чтобы делать

c.YYYYYYYY, c.UU, c.VV

он выполнит

c.YYYYYYYYUUVV

при линейной интерполяции.

Вы можете выполнить преобразование цвета YV12 -> BGR после переназначения, но цвета интерполированных пикселей будут неправильными.

Вместо выполнения линейной интерполяции попробуйте использовать интерполяцию ближайшего соседа в remap. Тогда вы сможете получить правильные цвета после преобразования цвета YV12 -> BGR.

Итак, найдите mapx, mapy, затем remap с помощью INTER_NEAREST и, наконец, выполните преобразование цвета YV12 -> BGR.

person dhanushka    schedule 23.01.2020
comment
Насколько мне известно в данных I420. Первые H строк - это только данные Y. и следующее количество строк H / 2 - это УФ-данные. - person Jai; 23.01.2020
comment
@Jai В этом случае предложенный подход не сработает. Я не знал об этом порядке данных. - person dhanushka; 23.01.2020
comment
Теперь я изобразил расположение пикселей в своем посте. - person Jai; 23.01.2020
comment
Если вас не интересует цветность, вы можете отменить искажение канала яркости, просто создав заголовок матрицы с этими данными. Поддерживает ли ваша камера упакованные форматы пикселей? YUV - VideoLAN Wiki - person dhanushka; 23.01.2020
comment
Я понял. Но мне также нужна цветность. Приходится работать с I420. Поскольку позже данные I420 будут отправлены на видеокодер h264, - person Jai; 23.01.2020