Contactez-nous

1

Correction automatique des niveaux des images

25 juin 2006
par ARNO*

[SPIP 1.9 et GD2] Nous allons ici exploiter deux techniques présentées récemment : l’analyse de l’histogramme et la modification de la courbe d’une image. Les filtres présentés ici vont réaliser l’automatisme suivant :
— dans un premier temps, analyser l’histogramme d’une image,
— en fonction de ces résultats, corriger la courbe, de façon à « rééquilibrer » les couleurs de l’image.

Dans Photoshop, cette fonctionnalité se nomme « Niveaux automatiques » (Auto Levels). Lorsque l’on retouche des photographies numériques, cette opération est incroyablement pratique : elle rééquilibre les images avec efficacité, et pour l’utilisateur moyen, il n’est souvent pas besoin d’aller plus loin pour améliorer la qualité de l’image.

Pour un rappel sur la correction des niveaux, vous pouvez lire ce tutorial en ligne.

Rééquilibrer l’intensité lumineuse

Commençons par analyser notre image d’origine :

Une photo de cymagenpdx sur Flickr

Nous pouvons afficher avec SPIP son histogramme :

Le code pour afficher l’image et son histogramme est, rappelons-le :

  1. [(#FICHIER|image_reduire{400})]
  2. [(#FICHIER|image_reduire{400}|image_histo)]

Télécharger

L’information qui nous intéresse ici est la courbe grise, qui représente la répartition des niveaux d’intensité lumineuse. Remarquons en particulier que, sur le côté droit de la courbe, une large plage ne comporte aucun pixel. Entre les niveaux d’intensité 210 et 255 (proches du blanc, donc), il n’y a aucun pixel de l’image contenant ces valeurs. Dit autrement : l’image ne comporte pas de zones « très » claires.

De manière moins importante, on peut considérer que l’image comporte très peu de zones « très » sombres.

Le but du filtre va consister à corriger cela, en « décalant » l’intensité des pixels, de façon à ce que les zones les plus claires de l’image deviennent quasiment « blanches », et les zones les plus sombres deviennent quasiment « noires ».

Cette transformation se réalise très simplement selon le principe de notre filtre de modification de la courbe de l’image.

Grosso modo, notre nouveau filtre va simplement combiner les méthodes de deux filtres déjà présentés dans ces pages : d’abord extraire les informations de l’histogramme, puis modifier la courbe de l’image en fonction de ces informations.

Voici notre nouveau filtre :

  1. function image_niveaux_gris_auto($im, $limite=1000) {
  2.  
  3. // $limite=1000: les nuances min et max representent 0,1% du total
  4.  
  5. $image = valeurs_image_trans($im, "niveaux_gris_auto-$limite");
  6. if (!$image) return("");
  7.  
  8. $x_i = $image["largeur"];
  9. $y_i = $image["hauteur"];
  10. $im = $image["fichier"];
  11. $dest = $image["fichier_dest"];
  12. $creer = $image["creer"];
  13.  
  14. if ($creer) {
  15. $im = $image["fonction_imagecreatefrom"]($im);
  16.  
  17. // Calculer les poids des differentes nuances
  18. for ($x = 0; $x < $x_i; $x++) {
  19. for ($y=0; $y < $y_i; $y++) {
  20.  
  21. $rgb = ImageColorAt($im, $x, $y);
  22. $a = ($rgb >> 24) & 0xFF;
  23. $r = ($rgb >> 16) & 0xFF;
  24. $g = ($rgb >> 8) & 0xFF;
  25. $b = $rgb & 0xFF;
  26.  
  27. $a = (127-$a) / 127;
  28. $a=1;
  29.  
  30. $gris = round($a*($r+$g+$b) / 3);
  31. $r = round($a*$r);
  32. $g = round($a*$g);
  33. $b = round($a*$b);
  34.  
  35. $val_gris[$gris] ++;
  36. }
  37. }
  38.  
  39. $total = $x_i * $y_i;
  40.  
  41. for ($bas = 0; $somme_bas < $total/$limite; $bas++) {
  42. $somme_bas += $val_gris[$bas];
  43. }
  44.  
  45. for ($haut = 255; $somme_haut < $total/$limite ; $haut--) {
  46. $somme_haut += $val_gris[$haut];
  47. }
  48.  
  49. $courbe[0] = 0;
  50. $courbe[255] = 255;
  51. $courbe[$bas] = 0;
  52. $courbe[$haut] = 255;
  53.  
  54. // Calculer le tableau des correspondances
  55. ksort($courbe);
  56. while (list($key, $val) = each($courbe)) {
  57. if ($key > 0) {
  58. $key1 = $key_old;
  59. $val1 = $val_old;
  60. $prop = ($val - $val1) / ($key-$key1);
  61. for ($i = $key1; $i < $key; $i++) {
  62. $valeur = round($prop * ($i - $key1) + $val1);
  63. $courbe[$i] = $valeur;
  64. }
  65. $key_old = $key;
  66. $val_old = $val;
  67. } else {
  68. $key_old = $key;
  69. $val_old = $val;
  70. }
  71. }
  72.  
  73. // Appliquer les correspondances
  74. $im2 = imagecreatetruecolor($x_i, $y_i);
  75. @imagealphablending($im2, false);
  76. @imagesavealpha($im2,true);
  77. $color_t = ImageColorAllocateAlpha( $im2, 255, 255, 255 , 0 );
  78. imagefill ($im2, 0, 0, $color_t);
  79.  
  80. for ($x = 0; $x < $x_i; $x++) {
  81. for ($y=0; $y < $y_i; $y++) {
  82. $rgb = ImageColorAt($im, $x, $y);
  83. $a = ($rgb >> 24) & 0xFF;
  84. $r = ($rgb >> 16) & 0xFF;
  85. $v = ($rgb >> 8) & 0xFF;
  86. $b = $rgb & 0xFF;
  87.  
  88. $r = $courbe[$r];
  89. $v = $courbe[$v];
  90. $b = $courbe[$b];
  91.  
  92. $color = ImageColorAllocateAlpha( $im2, $r, $v, $b , $a );
  93. imagesetpixel ($im2, $x, $y, $color);
  94. }
  95. }
  96.  
  97. $image["fonction_image"]($im2, "$dest");
  98. imagedestroy($im2);
  99. }
  100.  
  101. $class = $image["class"];
  102. if (strlen($class) > 1) $tags=" class='$class'";
  103. $tags = "$tags alt='".$image["alt"]."'";
  104. $style = $image["style"];
  105. if (strlen($style) > 1) $tags="$tags style='$style'";
  106.  
  107. return "<img src='$dest'$tags />";
  108. }

Télécharger

On applique ce filtre ainsi :

  1. [(#FICHIER|image_niveaux_gris_auto|image_reduire{400})]
  2. [(#FICHIER|image_niveaux_gris_auto|image_reduire{400}|image_histo)]

Télécharger

On peut indiquer une valeur de « limite » (par défaut, 1000). Cela signifie que l’on corrige l’image en « brûlant » les niveaux dont le nombre de pixel est inférieur à 1/1000 du nombre total de pixels (c’est le réglage standard, d’ailleurs, de Photoshop).

Voici l’image obtenue :

et son histogramme :

On constate que la courbe grise » a été décalée vers la droite. On a renforcé, ici, le contraste de l’image, sans modifier les dominantes de couleur (l’image a toujours une forte dominante rouge).

N.B. L’intérêt d’un tel filtre par rapport à un simple renforcement du contraste lumineux, c’est que si l’image d’origine est déjà correctement équilibrée, alors elle n’est pas modifiée. La correction de la luminosité se fait en fonction de l’image elle-même, et non de valeurs passées a priori par le webmestre.

Corriger les niveaux des couleurs

Le filtre précédent me semble le moins dangereux à utiliser en tant qu’automatisme sur un site Web : les dominantes de couleurs ne sont pas modifiées, on peut penser que le résultat sera une amélioration systématique des images, sans trop de risques de modifier sauvagement une « belle » image installée par un rédacteur doué en photographie...

Cependant, allons plus loin. Dans Photoshop, la fonction de niveau automatique ne concerne pas seulement le rééquilibrage de la luminosité, mais corrige en réalité indépendamment le niveau de chaque couche de couleur (rouge, vert, bleu).

C’est ce que réalise le filtre ci-dessous :

  1. function image_niveaux_auto($im, $limite=1000) {
  2.  
  3. // $limite=1000: les nuances min et max representent 0,1% du total
  4.  
  5. $image = valeurs_image_trans($im, "niveaux_auto-$limite");
  6. if (!$image) return("");
  7.  
  8. $x_i = $image["largeur"];
  9. $y_i = $image["hauteur"];
  10. $im = $image["fichier"];
  11. $dest = $image["fichier_dest"];
  12. $creer = $image["creer"];
  13.  
  14. if ($creer) {
  15. $im = $image["fonction_imagecreatefrom"]($im);
  16.  
  17. // Calculer les poids des differentes nuances
  18. for ($x = 0; $x < $x_i; $x++) {
  19. for ($y=0; $y < $y_i; $y++) {
  20.  
  21. $rgb = ImageColorAt($im, $x, $y);
  22. $a = ($rgb >> 24) & 0xFF;
  23. $r = ($rgb >> 16) & 0xFF;
  24. $g = ($rgb >> 8) & 0xFF;
  25. $b = $rgb & 0xFF;
  26.  
  27. $a = (127-$a) / 127;
  28. $a=1;
  29.  
  30. $gris = round($a*($r+$g+$b) / 3);
  31. $r = round($a*$r);
  32. $g = round($a*$g);
  33. $b = round($a*$b);
  34.  
  35. $val_r[$r] ++;
  36. $val_v[$g] ++;
  37. $val_b[$b] ++;
  38. }
  39. }
  40.  
  41. $total = $x_i * $y_i;
  42.  
  43. // Calculer le tableau des correspondances
  44. // Rouge
  45. for ($bas_r = 0; $somme_bas_r < $total/$limite; $bas_r++) {
  46. $somme_bas_r += $val_r[$bas_r];
  47. }
  48.  
  49. for ($haut_r = 255; $somme_haut_r < $total/$limite ; $haut_r--) {
  50. $somme_haut_r += $val_r[$haut_r];
  51. }
  52.  
  53. $courbe_r[0] = 0;
  54. $courbe_r[255] = 255;
  55. $courbe_r[$bas_r] = 0;
  56. $courbe_r[$haut_r] = 255;
  57.  
  58. ksort($courbe_r);
  59. while (list($key, $val) = each($courbe_r)) {
  60. if ($key > 0) {
  61. $key1 = $key_old;
  62. $val1 = $val_old;
  63. $prop = ($val - $val1) / ($key-$key1);
  64. for ($i = $key1; $i < $key; $i++) {
  65. $valeur = round($prop * ($i - $key1) + $val1);
  66. $courbe_r[$i] = $valeur;
  67. }
  68. $key_old = $key;
  69. $val_old = $val;
  70. } else {
  71. $key_old = $key;
  72. $val_old = $val;
  73. }
  74. }
  75.  
  76. // Bleu
  77. for ($bas_b = 0; $somme_bas_b < $total/$limite; $bas_b++) {
  78. $somme_bas_b += $val_b[$bas_b];
  79. }
  80.  
  81. for ($haut_b = 255; $somme_haut_b < $total/$limite ; $haut_b--) {
  82. $somme_haut_b += $val_b[$haut_b];
  83. }
  84.  
  85. $courbe_b[0] = 0;
  86. $courbe_b[255] = 255;
  87. $courbe_b[$bas_b] = 0;
  88. $courbe_b[$haut_b] = 255;
  89.  
  90. ksort($courbe_b);
  91. while (list($key, $val) = each($courbe_b)) {
  92. if ($key > 0) {
  93. $key1 = $key_old;
  94. $val1 = $val_old;
  95. $prop = ($val - $val1) / ($key-$key1);
  96. for ($i = $key1; $i < $key; $i++) {
  97. $valeur = round($prop * ($i - $key1) + $val1);
  98. $courbe_b[$i] = $valeur;
  99. }
  100. $key_old = $key;
  101. $val_old = $val;
  102. } else {
  103. $key_old = $key;
  104. $val_old = $val;
  105. }
  106. }
  107.  
  108. // Vert
  109. for ($bas_v = 0; $somme_bas_v < $total/$limite; $bas_v++) {
  110. $somme_bas_v += $val_v[$bas_v];
  111. }
  112.  
  113. for ($haut_v = 255; $somme_haut_v < $total/$limite ; $haut_v--) {
  114. $somme_haut_v += $val_v[$haut_v];
  115. }
  116.  
  117. $courbe_v[0] = 0;
  118. $courbe_v[255] = 255;
  119. $courbe_v[$bas_v] = 0;
  120. $courbe_v[$haut_v] = 255;
  121.  
  122. ksort($courbe_v);
  123. while (list($key, $val) = each($courbe_v)) {
  124. if ($key > 0) {
  125. $key1 = $key_old;
  126. $val1 = $val_old;
  127. $prop = ($val - $val1) / ($key-$key1);
  128. for ($i = $key1; $i < $key; $i++) {
  129. $valeur = round($prop * ($i - $key1) + $val1);
  130. $courbe_v[$i] = $valeur;
  131. }
  132. $key_old = $key;
  133. $val_old = $val;
  134. } else {
  135. $key_old = $key;
  136. $val_old = $val;
  137. }
  138. }
  139.  
  140. // Appliquer les correspondances
  141. $im2 = imagecreatetruecolor($x_i, $y_i);
  142. @imagealphablending($im2, false);
  143. @imagesavealpha($im2,true);
  144. $color_t = ImageColorAllocateAlpha( $im2, 255, 255, 255 , 0 );
  145. imagefill ($im2, 0, 0, $color_t);
  146.  
  147. for ($x = 0; $x < $x_i; $x++) {
  148. for ($y=0; $y < $y_i; $y++) {
  149. $rgb = ImageColorAt($im, $x, $y);
  150. $a = ($rgb >> 24) & 0xFF;
  151. $r = ($rgb >> 16) & 0xFF;
  152. $v = ($rgb >> 8) & 0xFF;
  153. $b = $rgb & 0xFF;
  154.  
  155. $r = $courbe_r[$r];
  156. $v = $courbe_v[$v];
  157. $b = $courbe_b[$b];
  158.  
  159. $color = ImageColorAllocateAlpha( $im2, $r, $v, $b , $a );
  160. imagesetpixel ($im2, $x, $y, $color);
  161. }
  162. }
  163.  
  164. $image["fonction_image"]($im2, "$dest");
  165. imagedestroy($im2);
  166. }
  167.  
  168. $class = $image["class"];
  169. if (strlen($class) > 1) $tags=" class='$class'";
  170. $tags = "$tags alt='".$image["alt"]."'";
  171. $style = $image["style"];
  172. if (strlen($style) > 1) $tags="$tags style='$style'";
  173.  
  174. return "<img src='$dest'$tags />";
  175. }

Télécharger

On peut par exemple l’utiliser ainsi :

  1. [(#FICHIER|image_niveaux_auto|image_reduire{400})]
  2. [(#FICHIER|image_niveaux_auto|image_reduire{400}|image_histo)]

Télécharger

On obtient l’image suivante :

et son histogramme :

Notez que la correction est nettement plus « violente » : la dominante de couleur rouge de l’image d’origine a été supprimée. Si le site concerné a des prétentions artistiques, on peut préférer éviter cet effet, qui contredira les choix de couleur du photographe. Si en revanche le site est mis à jour avec des images illustratives par des rédacteurs sans compétences graphiques spécifiques, alors cela peut être intéressant, justement, pour corriger les dominantes de couleur.

Voici une copie d’écran réunissant les trois images associées à leur histogramme respectif, de façon à vous permettre de les comparer :

Les 3 version de l’image
Cliquez sur la vignette pour obtenir l’image d’origine.

Notez bien : ces filtres recherchent la subtilité plutôt que l’effet graphique visible (« typer » le graphisme). Il convient donc de les tester en situation de une grande variété de photographies, pour vérifier qu’ils conviennent au contenu du site concerné. D’après mes propres essais, les résultats sont très convaincants : les images déjà bien équilibrées sont peu ou pas du tout modifiées, les autres présentent une nette amélioration.

  • ndd
    Mars 2012

    Bonsoir Arno.

    Sur un SPIP 3, les fonctions image_niveaux_auto et image_niveaux_gris_auto ne sont pas prises en compte ("Filtre image_niveaux_auto non défini"). Le filtre image_renforcement, lui, fonctionne très bien.

    J’imagine qu’il s’agirait de peu de choses pour que tout roule, mais je maitrise ni php ni les changements internes de SPIP 3. Auriez-vous une piste ?

    SPIP 3.0.0-beta2 SVN [19073], PHP/5.2.4

Qui êtes-vous ?
Votre message

Ce formulaire accepte les raccourcis SPIP [->url] {{gras}} {italique} <quote> <code> et le code HTML <q> <del> <ins>. Pour créer des paragraphes, laissez simplement des lignes vides.