Основной причиной неправильных результатов является неверный тип данных, используемый для доступа к изображению. Правильный тип для доступа к 16-битным неподписанным пикселям: cv::Vec3w
(не cv::Vec3d
).
Следующая проблема заключается в том, что коэффициенты, которые используются для преобразования, предназначены для аналоговых сигналов (YPbPr). Для цифровых изображений мы должны использовать коэффициенты, предназначенные для цифровых изображений (YCbCr). Более подробную информацию можно найти в статье Википедии о YCbCr в разделе Преобразование МСЭ-R BT.601 .
В статье отсутствует информация о том, как изменятся коэффициенты, если изображения имеют 16-битную глубину без знака или 32-битную глубину с плавающей запятой? Ответ заключается в том, что нам придется масштабировать коэффициенты в соответствии с битовой глубиной нашего изображения.
Для изображений с глубиной без знака 16 бит масштабирование должно выполняться следующим образом:
auto Y = (R * 65.481f * scale) + (G * 128.553f * scale) + (B * 24.966f * scale) + (16.0f * offset);
auto Cb = (R * -37.797f * scale) + (G * -74.203f * scale) + (B * 112.0f * scale) + (128.0f * offset);
auto Cr = (R * 112.0f * scale) + (G * -93.786f * scale) + (B * -18.214f * scale) + (128.0f * offset);
, где scale
равно 257.0/65535.0
, а offset
равно 257.0
.
Этот метод преобразования был принят из исходного кода MATLAB для функции rgb2ycbcr
, которая ссылается на следующую книгу, описывающую масштабирование:
C.A. Пойнтон, «Техническое введение в цифровое видео», Джон Уайли
& Sons, Inc., 1996, глава 9, стр. 175`
Теперь, когда преобразование выполнено, третья проблема, с которой мы сталкиваемся, - это визуализация изображения, аналогичная OpenCV. Когда мы выполняем преобразование цветов с помощью OpenCV, выходное изображение сохраняется в порядке YCrCb
вместо обычного YCbCr
. Таким образом, чтобы получить то же изображение с нашей пользовательской логикой преобразования, мы должны хранить значения в соответствующем порядке.
Пример кода преобразования может выглядеть следующим образом:
if(image.type() == CV_16UC3)
{
const float scale = 257.0f / 65535.0f;
const float offset = 257.0f;
for (int i = 0; i < image.cols; i++)
{
for (int j = 0; j < image.rows; j++)
{
auto R = image.at<cv::Vec3w>(j, i)[2];
auto G = image.at<cv::Vec3w>(j, i)[1];
auto B = image.at<cv::Vec3w>(j, i)[0];
auto Y = (R * 65.481f * scale) + (G * 128.553f * scale) + (B * 24.966f * scale) + (16.0f * offset);
auto Cb = (R * -37.797f * scale) + (G * -74.203f * scale) + (B * 112.0f * scale) + (128.0f * offset);
auto Cr = (R * 112.0f * scale) + (G * -93.786f * scale) + (B * -18.214f * scale) + (128.0f * offset);
image.at<cv::Vec3w>(j, i)[0] = (unsigned short)Y;
image.at<cv::Vec3w>(j, i)[1] = (unsigned short)Cr;
image.at<cv::Vec3w>(j, i)[2] = (unsigned short)Cb;
}
}
}