Как конвертировать 16-битный RGB565 в 24-битный RGB888? - PullRequest
21 голосов
/ 14 марта 2010

Я получил 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), который делает снимки экрана. Насколько я могу судить по чтению кода , он реализует ту же логику; все же его скриншоты получаются различными, и белый цвет отображает белый.

Вот необработанный кадровый буфер , интеллектуальное преобразование по ддм и немое преобразование по вышеуказанному алгоритму. Обратите внимание, что последний немного темнее и зеленее.

(Кстати, это преобразование реализовано в ffmpeg , но оно просто выполняет тупое преобразование, указанное выше, оставляя младшие биты равными нулю.)

Наверное, у меня есть два вопроса:

  • Какой самый разумный способ конвертировать rgb565 в rgb888?
  • Как DDMS конвертирует свои скриншоты?

Ответы [ 8 ]

21 голосов
/ 14 марта 2010

Вы хотите отобразить каждый из них из пространства 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-битных преобразований.

20 голосов
/ 30 января 2012

Мои несколько центов:

Если вам нужно точное отображение, но быстрый алгоритм, вы можете рассмотреть это:

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;

Константы были найдены с помощью поиска методом грубой силы с некоторыми ранними отклонениями, чтобы немного ускорить процесс.

16 голосов
/ 14 марта 2010

Вы можете сдвинуться и затем или с самыми значимыми битами; т.е.

Red 10101 becomes 10101000 | 101 => 10101101
    12345         12345---   123    12345123

Это свойство, которое вы ищете, но это не самое линейное отображение значений из одного пространства в другое. Это быстро, хотя. :)

Ответ Клетуса более полный и, вероятно, лучше. :)

5 голосов
/ 29 марта 2015

Преобразование iOS vImage

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. Это можно исправить, используя shift со вставкой, чтобы поместить некоторые старшие биты в младшие биты.

В качестве примера для Android я обнаружил преобразование YUV-в-RGB , написанное на встроенных элементах.

4 голосов
/ 15 марта 2010

Попробуйте это:

red5 = (buf & 0xF800) >> 11;
red8 = (red5 << 3) | (red5 >> 2);

Это отобразит все нули во все нули, все 1 во все 1 и все промежуточное во все промежуточное. Вы можете сделать его более эффективным, переместив биты на место за один шаг:

redmask = (buf & 0xF800);
rgb888 = (redmask << 8) | ((redmask<<3)&0x070000) | /* green, blue */

Аналогично для зеленого и синего (для 6 битов сдвиг влево на 2 и вправо соответственно в верхнем методе).

3 голосов
/ 22 октября 2010

Ошибка jleedev !!!

unsigned char green = (buf & 0x07c0) >> 5;
unsigned char blue = buf & 0x003f;

хороший код

unsigned char green = (buf & 0x07e0) >> 5;
unsigned char blue = buf & 0x001f;

Ура, Andy

3 голосов
/ 15 марта 2010

Общее решение состоит в том, чтобы рассматривать числа как двоичные дроби - таким образом, 6-битное число 63/63 совпадает с 8-битным числом 255/255. Вы можете вычислить это сначала с использованием математики с плавающей запятой, а затем вычислить таблицу подстановки, как предлагают другие авторы. Это также имеет преимущество в том, что он более интуитивен, чем решения с бит-битами. :)

0 голосов
/ 26 февраля 2012

Я использовал следующее и получил хорошие результаты. Оказалось, что моя камера 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;
}        
...