web-dev-qa-db-fra.com

Comment convertir une RGB565 16 bits en une RGB888 24 bits?

J'ai mis la main sur une image 16 bits rgb565 (en particulier, un vidage de framebuffer sous Android), et j'aimerais la convertir au format 24 bits rgb888 pour l'affichage sur un moniteur normal.

La question est de savoir comment convertir un canal de 5 ou 6 bits en 8 bits. La réponse évidente est de le déplacer. J'ai commencé par écrire ceci:

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);
}

Cependant, il n’ya pas une propriété que je voudrais, qui est que 0xffff mappe sur 0xffffff au lieu de 0xf8fcf8. Je dois augmenter la valeur d’une certaine manière, mais je ne suis pas sûr de savoir comment cela devrait fonctionner.

Le SDK Android est livré avec un outil appelé ddms (Dalvik Debug Monitor) qui prend des captures d’écran. D'après ce que je peux dire de en lisant le code , il met en œuvre la même logique; pourtant, ses captures d'écran sont différentes, et blanc correspond à blanc.

Voici le raw framebuffer , le conversion intelligente de ddms et le dumb conversion de l’algorithme ci-dessus. Notez que ce dernier est légèrement plus sombre et plus vert.

(En passant, cette conversion est implémentée dans ffmpeg , mais elle n’effectue que la conversion muette indiquée ci-dessus, laissant les LSB à zéro.)

Je suppose que j'ai deux questions:

  • Quel est le moyen le plus sensé de convertir rgb565 en rgb888?
  • Comment DDMS convertit-il ses captures d'écran?
21
Josh Lee

Vous pouvez décaler et ensuite ou avec les bits les plus significatifs; c'est à dire.

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

Cela a la propriété que vous recherchez, mais ce n’est pas le mappage le plus linéaire des valeurs d’un espace à l’autre. C'est rapide, cependant. :)

La réponse de Cletus est plus complète et probablement meilleure. :)

16
Jakob Borg

Vous souhaitez mapper chacun de ceux-ci d'un espace de 5/6 bits à un espace de 8 bits.

  • 5 bits = 32 valeurs
  • 6 bits = 64 valeurs
  • 8 bits = 256 valeurs

Le code que vous utilisez adopte l'approche naïve selon laquelle x5 * 256/32 = x8, où 256/32 = 8 et multipliant par 8 correspond au décalage 3 mais, comme vous dites, cela ne remplit pas nécessairement le nouvel espace de nombre " correctement". 5 à 8 pour une valeur maximale comprise entre 31 et 255 et voilà votre indice de la solution.

x8 = 255/31 * x5
x8 = 255/63 * x6

x5, x6 et x8 sont respectivement des valeurs de 5, 6 et 8 bits.

Maintenant, il y a une question sur la meilleure façon de mettre en œuvre cela. Cela implique une division et avec la division entière, vous perdrez tout résultat restant (arrondissez le plus bas). La meilleure solution consiste donc probablement à faire de l'arithmétique en virgule flottante, puis à arrondir la moitié à un entier.

Cela peut être considérablement accéléré en utilisant simplement cette formule pour générer une table de recherche pour chacune des conversions à 5 et 6 bits.

21
cletus

Mes quelques centimes:

Si vous vous souciez d'une cartographie précise, mais d'un algorithme rapide, vous pouvez envisager ceci:

R8 = ( R5 * 527 + 23 ) >> 6;
G8 = ( G6 * 259 + 33 ) >> 6;
B8 = ( B5 * 527 + 23 ) >> 6;

Il utilise uniquement: MUL, ADD et SHR -> il est donc assez rapide! De l’autre côté, il est compatible à 100% en mappage en virgule flottante avec un arrondi approprié:

// 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);

Quelques centimes supplémentaires: Si vous êtes intéressé par la conversion de 888 à 565, cela fonctionne également très bien:

R5 = ( R8 * 249 + 1014 ) >> 11;
G6 = ( G8 * 253 +  505 ) >> 10;
B5 = ( B8 * 249 + 1014 ) >> 11;

Les constantes ont été découvertes à l’aide de la recherche par force brute et de certains rejets précoces afin d’accélérer un peu les choses.

20
Anonymous

conversion vImage iOS

Le iOS Accelerate Framework documente l'algorithme suivant pour la fonction vImageConvert_RGB565toARGB8888:

Pixel8 alpha = alpha
Pixel8 red   = (5bitRedChannel   * 255 + 15) / 31
Pixel8 green = (6bitGreenChannel * 255 + 31) / 63
Pixel8 blue  = (5bitBlueChannel  * 255 + 15) / 31

Pour une conversion ponctuelle, cela sera assez rapide, mais si vous souhaitez traiter plusieurs images, vous souhaitez utiliser quelque chose comme la conversion iOS vImage ou l'implémenter vous-même à l'aide de NEON intrinsics .

Tutoriel du forum de la communauté ARM

Premièrement, nous examinerons la conversion de RGB565 en RGB888. Nous supposons qu'il y a huit pixels de 16 bits dans le registre q0 et nous souhaitons séparer les rouges, les verts et les bleus en éléments de 8 bits sur trois registres 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.

Les effets de chaque instruction sont décrits dans les commentaires ci-dessus, mais en résumé, l'opération effectuée sur chaque canal est la suivante: Supprime les données de couleur pour les canaux adjacents à l'aide de décalages permettant de pousser les bits de chaque extrémité de l'élément. ____.] Utilisez un deuxième décalage pour positionner les données de couleur dans les bits les plus significatifs de chaque élément et réduisez-les pour réduire la taille de l'élément de 16 à huit bits.

Notez l'utilisation de tailles d'élément dans cette séquence pour traiter les éléments de 8 et 16 bits, afin de réaliser certaines des opérations de masquage.

Un petit problème

Vous remarquerez peut-être que si vous utilisez le code ci-dessus pour convertir le format RGB888, vos blancs ne sont pas tout à fait blancs. En effet, pour chaque canal, les deux ou trois bits les plus bas sont zéro, et non un; un blanc représenté dans RGB565 sous la forme (0x1F, 0x3F, 0x1F) devient (0xF8, 0xFC, 0xF8) en RGB888. Cela peut être corrigé en utilisant shift avec insert pour placer certains des bits les plus significatifs dans les bits inférieurs.

Pour un exemple spécifique à Android, j'ai trouvé une conversion YUV à RVB écrite en éléments intrinsèques.

5

Essaye ça:

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

Cela mappera tous les zéros en tous les zéros, tous les 1 en tous les 1 et tout le reste en tout. Vous pouvez le rendre plus efficace en déplaçant les bits en place en une seule étape:

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

Faites de même pour le vert et le bleu (pour 6 bits, décalez gauche 2 et droite 4 dans la méthode du haut).

4
Rex Kerr

Il y a une erreur jleedev !!!

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

le bon code

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

Cordialement, Andy

3
Andy

La solution générale consiste à traiter les nombres comme des fractions binaires - le nombre de 6 bits 63/63 est donc identique au nombre de 8 bits 255/255. Vous pouvez calculer ceci en utilisant initialement les maths en virgule flottante, puis calculer un tableau de recherche, comme le suggèrent d'autres afficheurs. Cela présente également l’avantage d’être plus intuitif que les solutions anti-arrachement de bits. :)

2
Nick Johnson

J'ai utilisé ce qui suit et obtenu de bons résultats. En fait, ma caméra Logitek était une caméra RGB555 à 16 bits et l’utilisation de ce qui suit pour convertir en une carte 24 bits RGB888 m’a permis d’enregistrer en tant que fichier jpeg en utilisant les plus petits animaux.

// 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;
}        
0
bravo