Déchiffrer le JPEG

Les images JPEG sont partout dans notre vie numérique, mais derrière le voile de la familiarité se cachent des algorithmes qui suppriment des détails imperceptibles à l’œil humain. Cela permet d’obtenir la meilleure qualité visuelle avec la taille de fichier la plus petite, mais à quoi cela ressemble-t-il ? Voyons ce que nos yeux ne peuvent pas voir !

Il est facile de tenir pour acquis que vous pouvez envoyer une photo à un ami sans vous soucier du périphérique, du navigateur ou du système d’exploitation qu’il utilise, mais les choses n’ont pas toujours été comme ça. Au début des années 1980, les ordinateurs pouvaient stocker et afficher des images numériques, mais il y avait beaucoup d’idées concurrentes sur la meilleure façon de le faire. On ne pouvait pas simplement envoyer une image d’un ordinateur à un autre et s’attendre à ce qu’elle fonctionne.

Pour résoudre ce problème, le Groupe mixte d’experts photographiques (JPEG), un comité d’experts du monde entier, a été créé en 1986 par l‘ISO (Organisation internationale de normalisation) et la CEI (Commission électrotechnique internationale), deux organisations internationales de normalisation ayant leur siège à Genève, Suisse.

JPEG, le groupe de personnes, a créé JPEG, une norme pour la compression d’images numériques, en 1992. Quiconque a déjà utilisé Internet a probablement vu une image codée en JPEG. C’est de loin le moyen le plus répandu d’encoder, d’envoyer et de stocker des images. Qu’il s’agisse de pages Web, de courriels ou de médias sociaux, le format JPEG est utilisé des milliards de fois par jour, presque chaque fois que nous visualisons ou envoyons des images en ligne. Sans JPEG, le web serait un peu moins coloré, beaucoup plus lent et aurait probablement beaucoup moins de photos de chats !

Cet article explique comment décoder une image JPEG. En d’autres termes, il s’agit de ce qu’il faut pour convertir les données compressées stockées sur votre ordinateur en l’image qui apparaît à l’écran. Cela vaut la peine d’apprendre non seulement parce qu’il est important de comprendre la technologie que nous utilisons tous au quotidien, mais aussi parce que, lorsque nous démêlons les couches de compression, nous en apprenons un peu sur la perception et la vision, et sur les détails auxquels nos yeux sont les plus sensibles.

C’est aussi très amusant de jouer avec les images de cette façon.

Jumelage à l’intérieur d’un JPEG

Tout ce qui se trouve sur un ordinateur est stocké sous la forme d’une série de nombres binaires. Typiquement, ces bits, les zéros et les uns, sont disposés en groupes de huit, appelés octets. Lorsque vous ouvrez une image JPEG sur votre ordinateur, quelque chose (le navigateur, votre système d’exploitation ou autre) doit décoder les octets pour récupérer l’image originale sous forme de liste de couleurs qui peuvent ensuite être affichées.

Si vous téléchargez cette image du chat et que vous l’ouvrez à l’aide d’un éditeur de texte, vous verrez un tas de caractères brouillés.

En ouvrant une image dans un éditeur de texte, vous avez confondu l’ordinateur, de la même façon que vous confondez votre cerveau lorsque vous vous frottez les yeux trop fort et que vous commencez à voir des taches de couleur et d’obscurité !

Ces taches que vous voyez – connues sous le nom de phosphènes – ne proviennent pas d’un stimulus lumineux, et ce ne sont pas non plus des hallucinations maquillées dans votre esprit. Ils surviennent parce que votre cerveau présume que tout signal électrique arrivant par les nerfs de votre œil transmet de l’information lumineuse. Le cerveau doit faire cette supposition parce qu’il n’y a aucun moyen de savoir si un signal donné est un son, une vue ou autre chose. Tous les nerfs de votre corps transportent exactement le même type d’impulsion électrique. Lorsque vous exercez une pression en vous frottant les yeux, vous envoyez des signaux non visuels qui déclenchent les récepteurs dans vos yeux, que votre cerveau interprète – à tort, dans ce cas-ci, comme une vision. Vous pouvez littéralement voir la pression !

C’est amusant de penser à quel point les ordinateurs ressemblent à notre cerveau, mais c’est aussi une analogie utile parce qu’elle illustre à quel point le sens des données – qu’elles soient transmises par les nerfs ou stockées dans un ordinateur – repose sur la façon dont on les interprète. Toutes les données binaires sont composées de un et de zéros, composants de base qui peuvent véhiculer n’importe quel type d’information. Votre ordinateur devine souvent comment l’interpréter à l’aide d’indices, comme l’extension du fichier. Ici, nous l’avons forcé à l’interpréter comme du texte, parce que c’est ce qu’attend un éditeur de texte.

Pour comprendre comment une image JPEG est décodée, nous devons voir les signaux originaux eux-mêmes – les données binaires. Ceci peut être fait avec un éditeur hexadécimal, ou bien ici même sur cette page web ! Ci-dessous se trouve l’image à côté de tous ses octets, représentés par des nombres décimaux. Vous pouvez apporter des modifications aux octets, et il redécodera et affichera la nouvelle image modifiée au fur et à mesure que vous tapez.

Il y a beaucoup de choses que vous pouvez apprendre en jouant avec cet éditeur. Par exemple, pouvez-vous déterminer l’ordre dans lequel les pixels sont stockés ?

Quelque chose d’étrange dans l’exemple ci-dessus est que changer certains chiffres ne semble pas avoir d’impact sur l’image, alors que mettre le 17 sur la ligne un à 0 ruine complètement l’image ! D’autres actions, comme le réglage du 7 sur la ligne 1988 à 254 changent la couleur, mais seulement pour les pixels suivants.

Ce qui est peut-être le plus étrange, c’est que certains chiffres changent non seulement la couleur mais aussi la forme de l’image. Changez le 70 de la ligne 12 en 2 et regardez la ligne supérieure de l’image pour voir ce que je veux dire. Quelle que soit l’image JPEG que vous utilisez, vous trouverez toujours ces mystérieux motifs en damier lorsque vous éditez les octets.

Il est difficile de déchiffrer comment l’image peut être reconstruite à partir de ces octets en jouant ainsi, car la compression JPEG est en fait composée de trois techniques de compression différentes, qui sont appliquées par couches successives. Nous examinerons chacune de ces couches de compression séparément pour élucider ce mystérieux comportement que nous voyons.

Les trois couches de compression JPEG

  • Sous-échantillonnage de chrominance
  • Transformation et quantification du cosinus discret
  • Encodage Delta & Huffman, longueur de course

Pour vous donner une idée de l’échelle de cette compression, notez que l’image ci-dessus est représentée en utilisant exactement 79 819 chiffres, ce qui fait environ 79 kilo-octets. Si elle était stockée sans compression, il faudrait trois nombres pour chaque pixel – un pour chacun des composants rouge, vert et bleu. Cela représente un total de 917 700 numéros, soit environ 917 kilo-octets. Avec la compression JPEG, le fichier résultant est dix fois plus petit !

En fait, cette image peut être compressée en beaucoup moins d’octets. Ci-dessous se trouve l’image à côté d’une version qui a été compressée à seulement 16 kilo-octets, ce qui la rend cinquante-sept fois plus petite que la version non compressée le serait !

Si vous regardez attentivement, vous verrez que ces images ne sont pas identiques. Les deux sont des images codées en JPEG, mais celle de droite est beaucoup plus petite en termes de taille de fichier. Elle n’a pas non plus l’air aussi jolie (remarquez à quel point les couleurs sont bloquées à l’arrière-plan). C’est pourquoi le JPEG est connu sous le nom de technique de compression avec perte ; l’image change et perd une partie de ses détails en raison de la compression.

1. Sous-échantillonnage de chrominance

Voici l’image avec seulement la première couche de compression appliquée.

C’est un peu plus simple à déchiffrer maintenant. C’est presque une simple liste de couleurs, où chaque octet change exactement un pixel, et pourtant c’est déjà presque deux fois plus petit que l’image non compressée (qui serait d’environ 300 kb pour cette taille plus petite). Pouvez-vous deviner pourquoi ?

Vous pouvez dire que ces chiffres ne représentent pas les composantes standard rouge, vert et bleu parce que remplacer tous les chiffres par 0 rend l’image verte (par opposition au noir).

C’est parce que ces octets représentent le Y (luminosité), le Cb (bleu relatif) et le Cr (rouge relatif) de l’image.

Pourquoi ne pas simplement utiliser RGB ? Après tout, c’est ainsi que fonctionnent la plupart des écrans modernes. Votre moniteur peut afficher n’importe quelle couleur en allumant les lumières rouge, verte et bleue à différentes intensités pour chaque pixel. Le blanc s’affiche en allumant les trois couleurs à pleine luminosité, tandis que le noir s’affiche en les éteignant toutes.

C’est aussi très semblable au fonctionnement des yeux humains. Les récepteurs de couleur dans nos yeux connus sous le nom de « cônes » sont divisés en trois types, chacun d’eux étant le plus sensible au rouge, au vert ou au bleu. Les bâtonnets, l’autre type de récepteur que nous avons dans nos yeux, ne peuvent détecter que les changements de luminosité, mais ils sont beaucoup plus sensibles. Nous avons environ 120 millions de tiges dans les yeux, comparativement à six millions de cônes.

Cela signifie que nos yeux détectent beaucoup mieux les changements de luminosité qu’ils n’en détectent les changements de couleur. Si nous pouvons séparer la couleur de la luminosité, nous pouvons enlever un peu de la couleur sans que personne ne s’en aperçoive. Le sous-échantillonnage de chrominance est le processus qui consiste à représenter les composantes de couleur d’une image à une résolution inférieure à ses composantes de luminance. Dans l’exemple ci-dessus, chaque pixel a exactement une composante Y, tandis que chaque groupe discret de quatre pixels a exactement une composante Cb et une composante Cr. L’image ne contient donc que le quart des informations de couleur qu’elle contenait à l’origine.

L’utilisation de l’espace colorimétrique YCbCr n’est pas unique à JPEG. En fait, il a été développé à l’origine en 1938 pour les émissions de télévision. Tout le monde n’avait pas de téléviseur couleur, donc séparer la couleur de la luminance permettait à tout le monde de recevoir la même transmission, et les téléviseurs qui ne prenaient pas en charge la couleur n’utilisaient que la composante luminance.

C’est pourquoi la suppression d’un numéro de l’éditeur ci-dessus ruine complètement la couleur. Ici, les composants sont stockés sous la forme Y Y Y Y Cb Cr. En supprimant le premier nombre, la valeur Cb est interprétée comme Y, la valeur Cr comme Cb, et crée un effet d’ondulation qui fait basculer toutes les couleurs de l’image.

Il n’y a rien dans la spécification JPEG qui dit que vous devez utiliser YCbCr. La plupart des images JPEG l’utilisent parce qu’elles ont tendance à produire des images de meilleure qualité après sous-échantillonnage par rapport au RVB. Vous n’avez pas à tenir cela pour acquis. Vous pouvez voir par vous-même dans la grille ci-dessous à quoi cela ressemble de sous-échantillonner chaque composant individuellement à travers RGB ainsi que YCbCr.

Le sous-échantillonnage de la composante Y (en bas à gauche) a le plus grand effet sur la qualité de l’image. Même un tout petit peu est déjà perceptible. Vous pouvez déplacer le curseur pour voir comment la suppression d’un pourcentage plus élevé de chaque composant affecte l’image.

La conversion d’une image de RGB en YCbCr ne réduit pas la taille du fichier, mais elle facilite la recherche de détails moins visibles à supprimer. C’est la deuxième étape où la compression avec perte se produit. Cette idée de trouver de nouvelles façons de représenter les données pour les rendre plus compressibles est au cœur de ce que fait la couche suivante.

2. Transformation et quantification du cosinus discret

Cette couche de compression est en grande partie la caractéristique principale de JPEG. Une fois les couleurs converties en YCbCr, les composants sont compressés individuellement, de sorte que nous pouvons nous concentrer uniquement sur le composant Y pour le reste de l’article. Voici à quoi ressemblent les octets du composant Y avec cette couche appliquée.

A première vue, cela semble être une compression très faible. Il y a 100 000 pixels dans cette image, et pourtant il faut 102 400 chiffres pour représenter la luminance de chaque pixel – c’est pire que de ne pas le compresser du tout !

En fait, tous ces zéros peuvent être supprimés sans que l’image ne soit modifiée. Il ne reste donc que 26 000 numéros, ce qui le rend environ quatre fois plus petit !

Dans cette couche se trouve le secret des motifs en damier. Contrairement aux autres effets que nous avons vus, l’apparence de ces motifs n’est pas un problème. Ils sont en fait les éléments constitutifs de l’image entière. Chaque ligne de l’éditeur ci-dessus contient exactement 64 nombres, connus sous le nom de coefficients de transformation discrète du cosinus (DCT), qui correspondent à des intensités de 64 motifs uniques.

Ces motifs sont formés à partir d’ondes cosinus. Voici à quoi ressemblent certains d’entre eux :

Vous trouverez ci-dessous une image qui les montre tous les 64 individuellement.

Ces motifs sont spéciaux parce qu’ils forment la base des images 8×8. Si vous n’êtes pas familier avec l’algèbre linéaire, cela signifie que n’importe quelle image 8×8, tout ce que vous pouvez imaginer, peut être faite à partir de ces 64 motifs spécifiques. La transformation discrète du cosinus est le processus qui consiste à diviser l’image en blocs de 8×8 et à convertir chaque bloc en une combinaison de ces 64 coefficients. Voici comment former un cercle en combinant ces motifs, ou le visage du chat. Vous pouvez cliquer ici pour retourner à la grille des 64 motifs.

Il semble magique de dire que n’importe quelle image peut être représentée en utilisant 64 motifs spécifiques. Mais c’est la même chose que de dire que n’importe quel endroit de la Terre peut être représenté en utilisant seulement deux nombres : la longitude et la latitude. Nous traitons souvent la surface de la Terre comme une surface bidimensionnelle, donc seulement deux nombres sont nécessaires. Une image 8×8 est soixante-quatre dimensions, il nous faut donc 64 nombres.

Pour ce qui est de la compression, il n’est pas évident en quoi cela nous aide. Si nous avons besoin de soixante-quatre nombres pour représenter une image 8×8, pourquoi est-ce mieux que de stocker les soixante-quatre composantes de luminance ? Nous le faisons pour la même raison que nous avons converti les trois nombres de RVB en trois nombres de YCbCr : cela nous permet de supprimer les détails qui sont moins visibles.

Il est difficile de voir exactement à quoi ressemblent les détails qui sont supprimés dans cette étape de compression car JPEG n’applique la transformation en cosinus discret qu’aux blocs de 8×8 pixels à la fois. Cependant, il n’y a aucune raison de ne pas l’appliquer à l’image entière. Voici à quoi ressemble l’application du DCT à la composante Y de l’image entière :

Nous pouvons supprimer plus de 60 000 numéros à la fin sans presque aucun changement notable. Mais remarquez que si nous mettons juste les cinq premiers chiffres à zéro (en ignorant le premier parce qu’il rend juste l’image plus sombre), il y a déjà une différence évidente.

On dirait le job d’Instagram !

Les chiffres au début représentent les changements de fréquence plus faibles dans l’image, que nos yeux sont plus aptes à détecter. Les chiffres vers la fin représentent les changements de fréquence plus élevés, qui sont plus difficiles à voir pour nous, de sorte que nous ne remarquons pas quand ils sont partis. Pour voir « ce que nos yeux ne peuvent pas voir », nous pouvons isoler ces détails à haute fréquence en mettant les 5 000 premiers chiffres à zéro.

Ce que vous voyez ici, ce sont toutes les zones de l’image qui ont le plus grand changement d’un pixel à l’autre. Les yeux du chat, ses moustaches, sa couverture pelucheuse et ses ombres dans le coin inférieur gauche ressortent tous. Ceci peut être poussé encore plus loin, jusqu’à mettre les 10 000 premiers nombres à zéro, 20 000, 40 000 ou 60 000.

Ces détails haute fréquence sont ce que JPEG supprime pendant cette étape de compression. La conversion des couleurs aux coefficients DCT n’est pas une opération avec perte. C’est l’étape de quantification qui est la perte, où les valeurs qui sont de haute fréquence, proches de zéro, ou les deux, sont supprimées. Lorsque vous sélectionnez un paramètre de qualité inférieure lors de la création d’une image JPEG, cela augmente le seuil pour le nombre de ces valeurs supprimées, ce qui conduit à une taille de fichier plus petite mais à une image plus bloquée. C’est pourquoi la version de l’image de la première section, qui était 57 fois plus petite, semblait bloquée. Chaque bloc de 8×8 était représenté par beaucoup moins de coefficients DCT que la version de qualité supérieure.

Une chose vraiment cool que vous pouvez faire avec cette technique est de diffuser progressivement des images en continu. Imaginez voir une version floue de l’image entière et la voir lentement devenir de plus en plus détaillée au fur et à mesure que le téléchargement progresse et que davantage de coefficients DCT sont disponibles. C’est en fait possible avec JPEG, mais pas aussi couramment utilisé.

Juste pour s’amuser, voici à quoi ça ressemble d’utiliser seulement 24 000 numéros, ou seulement 5000 numéros. Plutôt flou, mais presque reconnaissable !

3. Encodage Delta & Huffman, longueur de course

Jusqu’à présent, toutes les étapes de compression ont été perdues. Cette dernière couche, en revanche, est sans perte. Il ne supprime aucune information, mais il rend la taille du fichier beaucoup plus petite.

Comment compresser quelque chose sans jeter aucune information ? Pensez à la façon dont vous représenteriez une simple image noire solide.

JPEG utilise environ 5 000 chiffres pour représenter cela, mais nous pouvons faire beaucoup mieux. Pouvez-vous imaginer un schéma d’encodage pour représenter cette image en utilisant le moins d’octets possible ?

La plus petite chose à laquelle je pouvais penser serait 4 octets : 3 pour spécifier la couleur et un pour spécifier combien de pixels ont cette couleur. L’idée d’exprimer toutes les valeurs répétées de façon concise de cette façon est appelée codage en longueur d’exécution. C’est sans perte car nous pouvons récupérer les données encodées exactement comme avant.

La taille du fichier de l’image JPEG noire solide est beaucoup plus grande que 4 octets car rappelez-vous que dans la couche DCT, la compression est appliquée aux blocs 8×8 à la fois. Il nous faut donc au minimum un coefficient DCT pour chaque bloc de 64 pixels. Nous n’en avons besoin que d’un seul car au lieu de stocker un coefficient DCT suivi de 63 zéros pour cette image, l’encodage en longueur nous permet de stocker un seul nombre et de dire « le reste est zéro ».

L’encodage delta est la technique qui consiste à stocker chaque octet en tant que valeur relative par rapport à une valeur antérieure au lieu de stocker sa valeur absolue. C’est la raison pour laquelle l’édition de certains octets modifiera la couleur de tous les pixels suivants. Par exemple, au lieu de stocker :

Vous commenceriez par 12, et de là, stockez juste combien vous devez ajouter ou soustraire pour obtenir le prochain nombre. Ainsi, une fois le delta-encodé, la séquence ci-dessus devient :

Encore une fois, les données transformées ne sont pas plus petites que l’original, mais elles sont plus compressibles. Appliquer le codage delta avant le run-length peut aider beaucoup, tout en restant une étape de compression sans perte.

L’encodage Delta est l’une des rares techniques appliquées en dehors des blocs 8×8. Sur les 64 coefficients DCT, le premier n’est qu’une fonction d’onde constante (vous le voyez comme une couleur unie). Il représente la luminosité moyenne de chaque bloc pour les composantes de luminance, ou la luminance moyenne du bleu pour les composantes Cb, etc. Cette première valeur dans chaque bloc DCT est appelée valeur DC, et chaque valeur DC est codée en delta par rapport aux valeurs précédentes. Ainsi, changer la luminosité du tout premier bloc affectera tous les blocs de l’image.

Tout cela ne laisse qu’un dernier mystère : comment le changement d’un seul chiffre peut-il complètement détruire l’image ? Jusqu’à présent, il ne s’agissait pas d’une propriété d’une des couches de compression. La réponse se trouve dans l’en-tête JPEG. Ce sont les quelques 500 premiers octets qui contiennent des métadonnées sur l’image, comme sa largeur et sa hauteur, et qui ont été omis de tous les éditeurs d’octets à ce jour.

Ci-dessous se trouve l’image originale avec l’en-tête inclus.

Sans l’en-tête, il est pratiquement impossible (ou du moins très difficile) de décoder l’image JPEG. Ce serait comme si j’essayais de vous décrire un tableau, et j’ai commencé à inventer des mots pour communiquer ce que je voyais. Ce sera probablement une description très concise, puisque je peux définir les mots pour signifier exactement ce que je veux communiquer, mais cela n’aurait aucun sens pour quiconque autre que moi.

Ça peut paraître ridicule, mais c’est exactement ce qui se passe ici. Chaque image JPEG est compressée avec un code spécifique à cette image. Ces codes sont définis dans un dictionnaire stocké dans l’en-tête. Cette technique s’appelle l’encodage Huffman, et le dictionnaire s’appelle une table Huffman. Ce tableau est marqué dans l’en-tête par deux octets : 255 suivi de 196. Chaque composant de couleur peut avoir sa propre table Huffman.

Les modifications apportées à ces tables Huffman auront les effets les plus spectaculaires sur n’importe quelle image. Le passage du deuxième 1 à 12 à la ligne 15 en est un bon exemple. Changer quoi que ce soit après la 125 sur cette ligne fonctionne aussi.

Les tables Huffman ont un effet dramatique sur l’image parce qu’elles nous disent comment lire les bits individuels. Jusqu’à présent, nous n’avons traité que les nombres binaires en décimales. Ceci cache le fait que si vous voulez stocker le nombre 1 dans un octet, il ressemblerait à 0000000001, parce que chaque octet doit avoir exactement huit bits même s’il n’a besoin que d’un bit.

C’est potentiellement un énorme gaspillage de stockage si vous avez beaucoup de petits nombres. L’encodage Huffman est une technique qui nous permet d’assouplir cette exigence que chaque nombre doit occuper huit bits. Cela signifie que si vous voyez les deux octets :

D’après le tableau de Huffman, il pourrait s’agir en fait de trois valeurs. Pour les extraire, vous devrez d’abord les décomposer en morceaux individuels :

Suivez ensuite le tableau pour savoir comment les regrouper. Par exemple, il pourrait s’agir des six premiers bits (111010) qui sont 58 en décimal, suivis de cinq autres bits (10011) qui sont 19 et enfin des quatre derniers bits (0011), qui sont trois.

C’est pourquoi il est très difficile de donner un sens aux octets de cette couche de compression. Les octets ne représentent pas vraiment ce qu’ils semblent représenter. Je n’entrerai pas dans les détails de la façon d’extraire le tableau de Huffman et de traduire les bits dans cet article, mais il existe de nombreuses bonnes ressources à ce sujet si vous êtes curieux.


En résumé, que faut-il pour décoder une image JPEG ? Tu dois le faire :

  • Extraire la (les) table(s) Huffman de l’en-tête et décoder les bits.
  • Extrayez les coefficients de transformation de cosinus discret pour chaque composante couleur/luminance, pour chaque bloc 8×8, en annulant les codages de longueur de course et delta.
  • Combinez les ondes cosinus en fonction des coefficients pour récupérer les valeurs des pixels pour chaque bloc de 8×8 (c’est ce qu’on appelle la transformation discrète inverse du cosinus).
  • Mettre à l’échelle les composantes de chrominance si elles ont été sous-échantillonnées (l’en-tête contient cette information).
  • Convertissez le YCbCr résultant de chaque pixel en RVB.
  • Affichez l’image !

C’est beaucoup de travail pour voir une simple photo de chat ! Mais ce que j’aime dans tout cela, c’est que vous pouvez voir à quel point JPEG est une technologie très centrée sur l’humain. Elle s’appuie sur les bizarreries de notre perception pour atteindre des taux de compression bien plus élevés qu’avec les techniques d’usage général. Et maintenant que vous comprenez le fonctionnement de JPEG, vous pouvez imaginer combien de ces techniques peuvent être étendues à d’autres domaines. Par exemple, l’application de l’encodage delta en vidéo peut produire une énorme réduction de la taille du fichier car il y a souvent des zones qui ne changent pas du tout entre les images (comme l’arrière-plan).

Tout le code pour cet article est open source et inclut des instructions pour remplacer les images dans ces éditeurs d’octets par les vôtres.

Via ParametricPress

Laisser un commentaire

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.