Как конвертировать 16-битный RGB565 в 24-разрядный RGB888?
Я получил 16-битное изображение rgb565 (в частности, дамп фейерверка Android), и я хотел бы преобразовать его в 24-разрядный rgb888 для просмотра на обычном мониторе.
Вопрос в том, как преобразовать 5- или 6-битный канал в 8 бит? Очевидный ответ - сдвинуть его. Я начал писать:
puts("P6 320 480 255");
uint16_t buf;
while (read(0, &buf, sizeof buf)) {
unsigned char red = (buf & 0xf800) >> 11;
unsigned char green = (buf & 0x07e0) >> 5;
unsigned char blue = buf & 0x001f;
putchar(red << 3);
putchar(green << 2);
putchar(blue << 3);
}
Однако у этого не есть одно свойство, которое я хотел бы, что для 0xffff
для отображения в 0xffffff
вместо 0xf8fcf8
. Мне нужно каким-то образом расширить значение, но я не уверен, как это должно работать.
Android SDK поставляется с инструментом под названием ddms (Dalvik Debug Monitor), который берет захват экрана. Насколько я могу судить из читает код, он реализует ту же логику; но его скриншоты выделяются разными, а белый - белым.
Heres raw framebuffer, интеллектуальное преобразование на ddms и немой конверсии по вышеуказанному алгоритму. Обратите внимание, что последний немного темнее и зеленее.
(Кстати, это преобразование реализовано в ffmpeg, но оно просто выполняет немое преобразование, перечисленное выше, оставляя LSB на всех ноль.)
У меня есть два вопроса:
- Какой самый разумный способ конвертировать rgb565 в rgb888?
- Как DDMS конвертирует свои скриншоты?
Ответы
Ответ 1
Вы можете сдвинуть, а затем или с помощью наиболее значимых бит; то есть.
Red 10101 becomes 10101000 | 101 => 10101101
12345 12345--- 123 12345123
Это свойство, которое вы ищете, но это не самое линейное отображение значений из одного пространства в другое. Это быстро.:)
Ответ Клетуса более полный и, вероятно, лучше.:)
Ответ 2
Вы хотите сопоставить каждое из них с 5/6 бит пространства в 8-битное пространство.
- 5 бит = 32 значения
- 6 бит = 64 значения
- 8 бит = 256 значений
Используемый вами код использует наивный подход: x5 * 256/32 = x8, где 256/32 = 8 и умножение на 8 - сдвиг влево 3, но, как вы говорите, это необязательно заполняет новый номер пробела "правильно". 5 до 8 для максимального значения - от 31 до 255, и в этом заключается ваш ключ к решению.
x8 = 255/31 * x5
x8 = 255/63 * x6
где x5
, x6
и x8
- соответственно 5, 6 и 8 бит.
Теперь возникает вопрос о наилучшем способе реализации этого. Это связано с делением и с целым делением, вы потеряете любой остаток результата (в основном округлите), поэтому лучшим решением является, вероятно, выполнение арифметики с плавающей запятой, а затем округление до половины целого числа.
Это можно ускорить, просто используя эту формулу для создания таблицы поиска для каждой из 5 и 6 бит-преобразований.
Ответ 3
Мои несколько центов:
Если вам небезразлично точное сопоставление, но быстрый алгоритм вы можете рассмотреть это:
R8 = ( R5 * 527 + 23 ) >> 6;
G8 = ( G6 * 259 + 33 ) >> 6;
B8 = ( B5 * 527 + 23 ) >> 6;
Он использует только: MUL, ADD и SHR → , так что это довольно быстро!
С другой стороны он совместим в 100% с отображением с плавающей запятой с правильным округлением:
// R8 = (int) floor( R5 * 255.0 / 31.0 + 0.5);
// G8 = (int) floor( G6 * 255.0 / 63.0 + 0.5);
// B8 = (int) floor( R5 * 255.0 / 31.0 + 0.5);
Некоторые дополнительные центы:
Если вы заинтересованы в преобразовании с 888 по 565, это тоже очень хорошо работает:
R5 = ( R8 * 249 + 1014 ) >> 11;
G6 = ( G8 * 253 + 505 ) >> 10;
B5 = ( B8 * 249 + 1014 ) >> 11;
Константы были найдены с использованием поиска грубой силы с ранними отклонениями somions, чтобы ускорить процесс.
Ответ 4
iOS vImage Conversion
iOS Accelerate Framework документирует следующий алгоритм для функции vImageConvert_RGB565toARGB8888
:
Pixel8 alpha = alpha
Pixel8 red = (5bitRedChannel * 255 + 15) / 31
Pixel8 green = (6bitGreenChannel * 255 + 31) / 63
Pixel8 blue = (5bitBlueChannel * 255 + 15) / 31
Для одноразового преобразования это будет достаточно быстро, но если вы хотите обработать много кадров, вы хотите использовать что-то вроде преобразования iOS vImage или реализовать это самостоятельно, используя NEON intrinsics.
Из учебника сообщества сообщества ARMs
Сначала мы рассмотрим преобразование RGB565 в RGB888. Мы предполагаем, что в регистре q0 имеется восемь 16-битных пикселей, и мы хотели бы разделить красные, зеленые и синие на 8-битные элементы на трех регистрах с d2 на d4.
vshr.u8 q1, q0, #3 @ shift red elements right by three bits,
@ discarding the green bits at the bottom of
@ the red 8-bit elements.
vshrn.i16 d2, q1, #5 @ shift red elements right and narrow,
@ discarding the blue and green bits.
vshrn.i16 d3, q0, #5 @ shift green elements right and narrow,
@ discarding the blue bits and some red bits
@ due to narrowing.
vshl.i8 d3, d3, #2 @ shift green elements left, discarding the
@ remaining red bits, and placing green bits
@ in the correct place.
vshl.i16 q0, q0, #3 @ shift blue elements left to most-significant
@ bits of 8-bit color channel.
vmovn.i16 d4, q0 @ remove remaining red and green bits by
@ narrowing to 8 bits.
Эффекты каждой команды описаны в комментариях выше, но в целом, операция, выполняемая для каждого канала: Удалите данные цвета для соседних каналов с помощью сдвигов, чтобы вытолкнуть биты с любого конца элемента. Используйте вторую смену, чтобы расположить цветные данные в наиболее значимых битах каждого элемента и узкие, чтобы уменьшить размер элемента от 16 до восьми бит.
Обратите внимание на использование размеров элементов в этой последовательности для адреса 8 и 16-разрядных элементов, чтобы выполнить некоторые операции маскировки.
Небольшая проблема
Вы можете заметить, что, если вы используете приведенный выше код для преобразования в формат RGB888, ваши белые не совсем белые. Это связано с тем, что для каждого канала младшие два или три бита равны нулю, а не единице; белый, представленный в RGB565, как (0x1F, 0x3F, 0x1F) становится (0xF8, 0xFC, 0xF8) в RGB888. Это можно исправить с помощью сдвига со вставкой, чтобы поместить некоторые из наиболее значимых бит в младшие бит.
Для конкретного Android-приложения я обнаружил преобразование YUV-RGB, написанное в intrinsics.
Ответ 5
Попробуйте следующее:
red5 = (buf & 0xF800) >> 11;
red8 = (red5 << 3) | (red5 >> 2);
Это будет отображать все нули во все нули, все 1 во все 1, а все между ними - все между ними. Вы можете сделать это более эффективным, сдвинув биты на место за один шаг:
redmask = (buf & 0xF800);
rgb888 = (redmask << 8) | ((redmask<<3)&0x070000) | /* green, blue */
Аналогично для зеленого и синего (для 6 бит, сдвиг влево 2 и правый 4 соответственно в верхнем методе).
Ответ 6
Произошла ошибка jleedev!!!
unsigned char green = (buf & 0x07c0) >> 5;
unsigned char blue = buf & 0x003f;
хороший код
unsigned char green = (buf & 0x07e0) >> 5;
unsigned char blue = buf & 0x001f;
Cheers,
Andy
Ответ 7
Общее решение состоит в том, чтобы рассматривать числа как двоичные дроби - таким образом, 6-битный номер 63/63 совпадает с 8-битным номером 255/255. Вы можете вычислить это с использованием математики с плавающей запятой, а затем вычислить таблицу поиска, как предлагают другие плакаты. Это также имеет то преимущество, что оно более интуитивно понятное, чем решение для бит-битбирования.:)
Ответ 8
Я использовал следующее и получил хорошие результаты. Оказалось, что моя камера Logitek была 16-битным RGB555, и для преобразования в 24-битный RGB888 я мог сохранить как jpeg, используя меньшие животные ijg: Спасибо за подсказку, найденную здесь, в stackoverflow.
// Convert a 16 bit inbuf array to a 24 bit outbuf array
BOOL JpegFile::ByteConvert(BYTE* inbuf, BYTE* outbuf, UINT width, UINT height)
{ UINT row_cnt, pix_cnt;
ULONG off1 = 0, off2 = 0;
BYTE tbi1, tbi2, R5, G5, B5, R8, G8, B8;
if (inbuf==NULL)
return FALSE;
for (row_cnt = 0; row_cnt <= height; row_cnt++)
{ off1 = row_cnt * width * 2;
off2 = row_cnt * width * 3;
for(pix_cnt=0; pix_cnt < width; pix_cnt++)
{ tbi1 = inbuf[off1 + (pix_cnt * 2)];
tbi2 = inbuf[off1 + (pix_cnt * 2) + 1];
B5 = tbi1 & 0x1F;
G5 = (((tbi1 & 0xE0) >> 5) | ((tbi2 & 0x03) << 3)) & 0x1F;
R5 = (tbi2 >> 2) & 0x1F;
R8 = ( R5 * 527 + 23 ) >> 6;
G8 = ( G5 * 527 + 23 ) >> 6;
B8 = ( B5 * 527 + 23 ) >> 6;
outbuf[off2 + (pix_cnt * 3)] = R8;
outbuf[off2 + (pix_cnt * 3) + 1] = G8;
outbuf[off2 + (pix_cnt * 3) + 2] = B8;
}
}
return TRUE;
}