menu
  Home  ==>  articles  ==>  graphique  ==>  correction_couleur   

Correction de Couleurs - John COLIBRI.

  • résumé : outil de modification des couleurs d'une image, en utilisant la décomposition RGB ou HSV
  • mots clé : graphique - tBitMap - correction de couleur - modèle RGB et HSV
  • logiciel utilisé : Windows XP, Delphi 6.0
  • matériel utilisé : Pentium 1.400Mhz, 256 M de mémoire
  • champ d'application : Delphi 1 à 2005 sur Windows
  • niveau : développeur Delphi
  • plan :


1 - Introduction

J'ai pris des photos de la maison que nous souhaitions louer dans le golfe du Morbihan. J'ai utilisé pour cela une caméra numérique, en utilisant la carte mémoire pour les photographies. Le résultat des image numériques transférées sur PC était très décevant: toutes les images étaient trop sombres:



J'avais donc plusieurs choix:

  • télécharger et apprendre à utiliser un quelconque photoshop
  • charger la bitmap en Delphi et la modifier ainsi


Ayant choisi d'effectuer les modifications moi-même, voici un exemple de correction de l'image précédente:




2 - Décomposition couleur

2.1 - La décomposition Rouge / Vert / Bleu

Le format des bitmap Windows le plus fréquent de nos jours contient pour chaque octet de l'image 1 octet par pixel: le rouge le vert et le bleu (RGB). Les fichiers .BMP contiennent un prologue (la taille, le nombre de bits par couleur etc) puis les 3 octets pour chaque point.

Une fois chargé en mémoire, nous pouvons utiliser Canvas.Pixels[x, y] pour récupérer dans un Integer (aussi défini comme un tColorRef) les 3 octets de couleur (le quatrième octet étant jadis utilisé pour coder les palettes).



2.2 - La ScanLine

Pour traiter chaque pixel, nous pouvons utiliser:

with my_bitmapCanvas do
  for l_line_index:= 0 to Width- 1 do
    for l_column_index:= 0 to Height- 1 do
      ... Pixels[l_column_indexl_line_index] ...

Or l'extraction de chaque point via Canvas.Pixels[x, y] utilise une double indexation qui coûte cher en temps de calcul. De plus, les .BMP sont stockés en mémoire dans une structure Windows appelée DIB (Device Independent Bitmap) qui stocke les couleurs sous forme de lignes de triplets RGB. Cette structure est accessible par la propriété Bitmap.ScanLine, et il s'avère que l'utilisation de cette structure accélère les traitements des points de la tBitmap. Il vaut donc mieux utiliser:

type // -- from Wintow
     t_RGB_triplepacked record
                     m_bluem_greenm_redByte;
                   end;

     // -- this represents a line of 3 color bytes
     t_RGB_arrayARRAY[0..0] OF t_RGB_triple;

     // -- a pointer to this line
     t_pt_RGB_array= ^ t_RGB_array;

var l_line_indexl_column_indexInteger;
    l_pt_source_rowt_pt_RGB_array;

  For l_line_index:= 0 to m_line_count- 1 do
  begin
    l_pt_source_row:= m_c_bitmap.Scanline[l_line_index];

    for l_column_index:= 0 to m_column_count- 1 do
    begin
      ... _pt_source_row[l_column_index].m_red ...
    end// for l_column_index
  end// for l_line_index



2.3 - La décomposition réversible

Pour modifier les couleurs d'une image, il suffit donc de modifier les valeurs des octets RGB. Par exemple, pour "éclaircir" l'image, il suffit d'augmenter tous les pixels d'un certain montant, par exemple 10.

Lorsque la valeur de la couleur se trouve au milieu de la plage 0..255, l'augmentation d'une petite valeur est réversible:

72+ 10 = 82
82- 10 = 72
Mais lorsque la modification dépasse l'intervalle 0..255, la nouvelle valeur est tronquée pour la conserver entre 0 et 255, et la modification inverse n'est plus possible:
250+ 10 = 260     qui est ramené à 255
255- 10 = 245     qui est différent de 250



Si nous souhaitons par conséquent modifier le contenu de l'image de façon interactive (via des tEdit, tUpDown, tTrackBar, tScrollBox ou autre), nous ne pourrons plus revenir en arrière: il faut recharger l'image de départ et repartir de cette image.

Une solution est de gérer en parallèle une image dont les valeurs RGB sont codées sous forme de Double: les contrôles visuels modifie la valeur des réels, et pour l'affichage, nous recopions, en tronquant au besoin, les valeurs des réels dans les octets de la tBitmap



2.4 - L'ergonomie

Notre premier programme était d'abord parti de logiciels existants. Google fournit plusieurs sources Delphi, qui effectuent la décomposition RGB, puis permettent des modifications incrémentales des valeurs RGB des octets. Tous ceux que j'ai trouvé travaillent directement sur la tBitmap, donc sur ces octets, et ne permettent donc pas l'annulation des corrections.

Il s'avère que la correction d'image est assez peu intuitive. Il n'est pas évident de décider s'il faut augmenter le contenu rouge, réduire toutes les valeurs, ou de quelle manière.

Notre seconde tentative fut donc d'utiliser le tableau parallèle de Double avec recopie dans la tBitmap.

La définition des modifications se faisait en tapant des valeurs dans des tEdit, éventuellement avec des tUpDown.

L'utilisation d'un tTrackBar pour ajuster la valeur de l'incrément fut l'extension naturelle. La modification sur tTrackBar.OnChange s'avéra désagréable: il fallait un peu agiter le curseur pour provoquer le changement depuis la valeur courante. D'ou la version actuelle, où la position du curseur fournit directement la modification depuis l'image originale.

Nous utilisons donc trois curseurs: Rouge, Vert, Bleu, et à chaque mouvement de curseur, nous recalculons la valeur de chaque point.

Puisque nous effectuons les modifications depuis l'original, le tableau de Double devient inutile: nous pourrions directement effectuer les calculs depuis la bitmap de départ. Nous avons conservé les tableaux de Double pour les calculs en HSV que nous allons présenter maintenant.



2.5 - La décomposition HSV

La décomposition Rouge Vert Bleu est surtout utile si une image est trop biaisée vers l'une de ces composante: trop verdâtre ou rougeâtre.

En revanche si l'image est trop grisâtre, trop sombre ou pas assez contrastée, la représentation RGB n'est pas la plus aisée à utiliser.

Plusieurs modèles de couleurs ont été proposés, parmi lesquels le modèle HSV. Chaque point est décomposé en:

  • Hue qui représente la couleur, comme une valeur entre 0 et 360
  • Saturation qui indique la "densité" de la couleur
  • Value qui représente l'intensité
La couleur est représentée par un angle, les rouges étant dans la partie 0-60 et 300-360 (environ), les verts dans la partie 60-180, les bleus dans la partie 180, 300.
  • Voici trois exemples de couleurs:

La différence entre la saturation et l'intensité n'est pas intuitive. Prenons un exemple sur un rouge bordeaux:
  • en changeant la saturation, la teinte perçue restera la même (rouge bordeaux) mais sera soit atténuée vers les gris, soit plus "dense"

  • en changeant l'intensité, nous balayerons la plage du noir complet à un rouge étincelant



Mathématiquement, les choses sont plus simples. Il s'agit d'un simple changement de coordonnées en 3D:
  • l'intensité est la plus grande des valeurs Red, Green et Blue:

         Max(r, g, b)

  • la saturation est obtenue par:

         (Max(r, g, b)- Min(r, g, b)) / Max(r, g, b)

  • la couleur est l'angle du point avec l'axe RGB le plus proche du point (si Rouge est la plus grande valeur, c'est la valeur par rapport à l'axe rouge, etc)


Voici d'ailleurs la fonction de conversion que nous avons utilisée:

function f_rgb_to_hsv(CONST p_k_rgb_float_triplet_RGB_float_triple): t_hsv_triple;
    // -- hue= 0 to 360 (corresponding to 0..360 degrees around hexcone)
    // -- saturation= 0 (shade of gray) to 1 (pure color)
    // -- intensity= 0 (black) to max {white)
  VAR l_intensity_mint_float;
      l_intensity_ranget_float;
  BEGIN
    WITH p_k_rgb_float_tripleResult DO
    BEGIN
      // -- intensity:= Max(red, green, blue)
      m_intensity:= f_float_max(m_float_redf_float_max(m_float_greenm_float_blue));

      if m_intensity= 0
        then begin
            // -- saturation is 0 if r, g and b are all 0
            m_saturation:= 0;

            // -- achromatic: When saturation = 0, color is undefined
            // -- but arbitrarily assigned the value 0
            m_color:= 0;
          end
        else begin
            l_intensity_min:= f_float_min(m_float_redf_float_min(m_float_greenm_float_blue));
            l_intensity_range:= m_intensityl_intensity_min;

            // -- get the saturation as a percentage  range / max
            m_saturation:= l_intensity_range / m_intensity;

            if m_saturation= 0
              then begin
                  m_color:= 0;
                end
              else begin
                  // -- chromatic

                  // -- find the color with the maximum value
                  // -- and compute the spread with the other two colors
                  if m_float_redm_intensity
                    then // -- the red is the greatest value
                         // -- this value becomes the intensity
                         // -- degrees between yellow and magenta
                         m_color:= (m_float_greenm_float_blue)* 60.0 / l_intensity_range
                    else
                      IF m_float_greenm_intensity
                        THEN // -- degrees between cyan and yellow
                             m_color:= 120.0+ (m_float_bluem_float_red)* 60.0/ l_intensity_range
                        else
                          if m_float_bluem_intensity
                            THEN // -- degrees between magenta and cyan
                                 m_color:= 240.0+ (m_float_redm_float_green)* 60.0/ l_intensity_range
                            else display_bug_halt('no_rgb_max');
                end;
          end;

      IF m_color< 0
        THEN begin
            // -- wrap around
            m_color:= m_color+ 360.0;
          end;

      // -- normalize itensity to 1
      m_intensity:= m_intensity / 255;
    END// WITH p_k_rgb_float_triple, Result
  END// f_rgb_to_hsv



Schématiquement, nous pouvons représenter le domaine des couleurs:

  • soit comme un cube dont les 3 axes correspondent aux valeurs des octets de rouge, de vert et de bleu:

  • soit comme un cône:
    • le plan xOy représente les couleurs sur 360 degrés
    • la verticale représente l'intensité
    • l'intérieur du cône représente la saturation


2.6 - Les transformations

Nous pouvons appliquer n'importe quelle fonction pour remplacer les valeurs des composantes (RGB ou HSV) d'un point par d'autres valeurs:
  • une translation (ajout d'une valeur constante)
  • une homothétie (multiplier la valeur par une constante)
  • des transformations non linéaires: le carré, la racine carrée
Dans le projet qui suit, nous avons essentiellement mis en oeuvre:
  • les translations
  • les puissances
En fait, le plus de souplesse est apporté en laissant l'utilisateur dessiner une courbe de réponse qui serait appliquée à chaque composante de chaque point. Pour cela, nous avons crée un éditeur de courbe, avec interpolation linéaire ou Bézier.



2.7 - L'analyze de l'image

Après avoir chargé une image, nous pouvons:
  • modifier les composantes RGB (les rouges, les verts, les bleus)
  • modifier les composantes HSV
Mais à part un effet visuel immédiat, nous n'avons aucune idée de ce qu'il "faudrait" faire.

Pour cela, nous avons essayé d'ajouter des outils d'analyze:

  • des histogrammes qui représente le nombre de points RGB, ou HSV. A chaque changement de l'image, les histogrammes sont recalculés, nous donnant une idée plus quantitative du changement
  • une représentation de la gamme des couleurs (la même que dans tColorDialog), avec:
    • un affichage de la couleur sous la souris lorsque nous déplaçons la souris sur l'image
    • un affichage dans une autre bitmap de tous les autres points ayant une couleur voisine de la couleur sous la souris

3 - Le programme Delphi

3.1 - Le chargement de l'image

L'image est chargée en sélectionnant le fichier dans une tFileListbox. Si nous chargeons une .BMP usuelle, elle sera représentée en mémoire avec le nombre de bit par couleur défini par le fichier. Or pour effectuer nos décompositions, nous préférons forcer une représentations avec 8 bits pour chaque couleur. Pour cela:
  • nous chargeons la tBitmap dans une tImage
  • nous créons une tBitmap en forçant son nombre de bits de couleurs à 8 bit pour chaque couleur
  • nous copions l'image de la tImage dans notre tBitmap
Voici la mécanique:

procedure load_bitmap;
  begin
    Form1.Image1.Picture.LoadFromFile(g_pathg_file_name);

    with g_c_bitmap do
    begin
      // -- create our bitmap with the 24 bit colors
      m_c_bitmap.Free;
      m_c_bitmap:= tBitmap.Create;

      m_c_bitmap.width:= Form1.Image1.picture.graphic.width;
      m_c_bitmap.height:= Form1.Image1.picture.graphic.height;
      m_c_bitmap.pixelformat:= pf24bit;

      // -- copy the image from the tImage
      m_c_bitmap.canvas.draw(0, 0, Form1.Image1.picture.graphic);
    end// with g_c_bitmap
  end// load_bitmap



3.2 - Chargement de JPEG

Si notre fichier est un .JPG ou .JPEG, Delphi le convertit automatiquement en .BMP mémoire (DIB: Device Independent Bitmap), du moins pour la version Delphi 6 que nous utilisons (sinon utilisez Google pour trouver un quelconque logiciel de conversion Delphi, ou même convertissez vous-même en utilisant PaintBrush).



3.3 - Chargement de .PNG

Pour les fichiers .PNG que nous utilisons pour notre site (toutes nos illustrations générées par EdFig sont en .PNG), nous utilisons une librairie d'importation de LibPng.DLL. Le chargement / sauvegarde des .PNG utilisant cette librairie d'importation ont été décrits en grand détail pour notre lecteur de transparents.

Pour éviter d'inclure cette DLL dans le .ZIP, nous avons mis en commentaire les utilisations de cette unité dans le .ZIP du correcteur de couleur. Si vous souhaitez ajouter cette possibilité, téléchargez le .ZIP du lecteur de transparents et installez-le comme indiqué dans le paragraphe téléchargement



3.4 - Modifications RGB

La forme comporte trois TrackBar permettant d'ajuster chaque couleur
  • à chaque déplacement, la correction indiquée par le curseur de chaque couleur est appliquée et la bitmap est ajustée. La transformation est réalisée depuis une copie de l'original, comme expliqué ci-dessus. La valeur médiane correspond à la valeur de chargement, le déplacement vers la droite ajoute une valeur (à concurrence de 64) et le déplacement vers la gauche soustrait une valeur (à concurrence de -64)
  • pour revenir à la situation initiale, cliquez sur le tPanel de la couleur (ou rechargez l'image)
  • la checkbox situé à côté de la tTrackBar permet d'utiliser une correction multiplicative: pour une valeur à droite de la médiane, chaque couleur sera multipliée par un puissance proportionnelle au déplacement (un courbe en cloche qui va donc tasser les points vers les valeur hautes). Pour une valeur à gauche de la médiane, la puissance est négative, et les points seront donc tassés vers les valeurs faibles.


3.5 - Les histogrammes

Les couleurs et leurs transformations dont analysées par 3 histogrammes. Ces histogrammes répartissent en 255 catégories les points de l'image. Si les valeurs à analyser sont des entiers entre 0 et 255, les bins correspondent exactement aux valeurs à analyser. Sinon des règles de trois sont effectuées.

La classe est définie ainsi:

c_histogram_255class(c_basic_object)
                    m_histogramarray[0..255] of Integer;
                    m_c_canvastCanvas;
                    m_pen_colorInteger;
                    m_maxInteger;
                    // -- draw min max limits
                    m_x_1m_x_2Integer;

                    Constructor create_histogram_255(p_nameString;
                        p_c_canvastCanvasp_pen_colorInteger);

                    procedure reset_histogram;
                    procedure display_histogram;
                    function f_total_countInteger;
                    procedure compute_max;
                    procedure rescale_histogram(p_y_maxInteger);
                    procedure draw_histogram(p_y_minp_y_maxInteger);
                    procedure draw_histogram_with_border(p_y_offset
                        p_fatInteger);
                    procedure draw_vertical(p_xp_y_offset
                        p_fatp_pen_colorInteger);

                    Destructor DestroyOverride;
                  end// c_histogram_255

Outre le tableau de statistiques, la classe permet surtout de redimensionner les valeurs, et de dessiner les valeurs dans un intervalle y donné, ce qui nous permet de dessiner les courbes superposées.

Voici un exemple pour notre image:



Si nous ajoutons du rouge à toutes les couleurs, nous obtenons:



En cliquant la tCheckBox, nous pouvons comprimer les rouges vers le haut:

ou vers le bas:



Passons au mode HSV. Voici la situation de départ:



Voici un exemple de rotation des couleurs:



Pour la saturation, voici une translation de la saturation:

Notez que la partie blanche de l'histogramme indique les valeur ayant dépassé le maximum de 1 à cause de la translation.



En comprimant la saturation dans la plage haute, nous obtenons:



A l'inverse, en saturant vers le bas, nous virons au gris:



Pour l'intensité, la compression haute fournit l'image que nous avons retenue (voir en début d'article). Pour tester notre modulation sur mesure, en utilisant les courbes de Bézier, nous avons effectuée deux tests:

Un premier consiste à essayer d'étaler les valeurs de l'histogramme: les petites valeurs sont réduites, les grandes agrandies. En gros une courbe en cloche

Et une courbe en creux rassemblerait les points de l'histogramme:



3.6 - La couleur d'un point

Lorsque nous déplaçons la souris au dessus de l'image, un cercle indique la valeur de la couleur sur une carte. Par exemple, lorsque nous passons au-dessus des fleurs violettes situées au milieu à droite, nous obtenons:



Et lorsque nous déplaçons la souris sur cette carte d'image, une tBitmap affiche quels points ont cette couleur. En nous plaçant au-dessus des rouges, nous obtenons:



Finalement nous pouvons utiliser un rectangle de sélection au-dessus de l'image pour analyser la composition des points contenus dans cette image. Ici nous avons sélectionné (en tirant / glissant la souris) un rectangle au milieu du divan:



Voici une vue globale du projet:




4 - Télécharger les Sources

Vous pouvez télécharger:
  • rgb_hsv_color_correction.zip : le correcteur d'image (97 K)

  • Librairie .PNG : lecture / écriture de fichiers .PNG. Pour pouvoir utiliser des .PNG
    • enlevez dans le programme principal les commentaires entourant les appels à la librairie .PNG (USES, lecture et écriture)
    • téléchargez le .ZIP de lecture de transparents
    • dézippez
    • placez l'unité u_png_unit et u_png_dll_import dans xxx\colibri_utilities\graphicpng
    • placez LibPng.Dll dans xxx\graphic\exe (le répertoire contenant l'EXE de ce projet. ATTENTION: si la version de LibPng.DLL ne convient pas, ou que vous ne placez pas la .DLL avec l'EXE, vous aurez une erreur d'exécution au lancement du programme.

  • vous pouvez aussi télécharger les images .BMP non comprimées de deux grands classiques de la correction d'image:
    • parrots (193 K)
    • mandrill (890 K)


Les .ZIP qui comprennent:
  • le .DPR, la forme principale, les formes annexes eventuelles
  • les fichiers de paramètres (le schéma et le batch de création)
  • dans chaque .ZIP, toutes les librairies nécessaires à chaque projet (chaque .ZIP est autonaume)
Ces .ZIP contiennent des chemins RELATIFS. Par conséquent:
  • créez un répertoire n'importe où sur votre machine
  • placez le .ZIP dans ce répertoire
  • dézippez et les sous-répertoires nécessaires seront créés
  • compilez et exécutez
Ces .ZIP ne modifient pas votre PC (pas de changement de la Base de Registre, de DLL ou autre). Pour supprimer le projet, effacez le répertoire.



Comme d'habitude:

  • nous vous remercions de nous signaler toute erreur, inexactitude ou problème de téléchargement en envoyant un e-mail à jcolibri@jcolibri.com. Les corrections qui en résulteront pourront aider les prochains lecteurs
  • tous vos commentaires, remarques, questions, critiques, suggestion d'article, ou mentions d'autres sources sur le même sujet seront de même les bienvenus à jcolibri@jcolibri.com.
  • plus simplement, vous pouvez taper (anonymement ou en fournissant votre e-mail pour une réponse) vos commentaires ci-dessus et nous les envoyer en cliquant "envoyer" :
    Nom :
    E-mail :
    Commentaires * :
     

  • et si vous avez apprécié cet article, faites connaître notre site, ajoutez un lien dans vos listes de liens ou citez-nous dans vos blogs ou réponses sur les messageries. C'est très simple: plus nous aurons de visiteurs et de références Google, plus nous écrirons d'articles.



5 - L'auteur

John COLIBRI est passionné par le développement Delphi et les applications de Bases de Données. Il a écrit de nombreux livres et articles, et partage son temps entre le développement de projets (nouveaux projets, maintenance, audit, migration BDE, migration Xe_n, refactoring) pour ses clients, le conseil (composants, architecture, test) et la formation. Son site contient des articles avec code source, ainsi que le programme et le calendrier des stages de formation Delphi, base de données, programmation objet, Services Web, Tcp/Ip et UML qu'il anime personellement tous les mois, à Paris, en province ou sur site client.
Créé: avr-05. Maj: aou-15  148 articles, 471 sources .ZIP, 2.021 figures
Contact : John COLIBRI - Tel: 01.42.83.69.36 / 06.87.88.23.91 - email:jcolibri@jcolibri.com
Copyright © J.Colibri   http://www.jcolibri.com - 2001 - 2015
Retour:  Home  Articles  Formations  Développement Delphi  Livres  Pascalissime  Liens  Download
l'Institut Pascal

John COLIBRI

+ Home
  + articles_avec_sources
    + bases_de_donnees
    + web_internet_sockets
    + prog_objet_composants
    + office_com_automation
    + colibri_utilities
    + uml_design_patterns
    + graphique
      – rectangle_de_selection
      – editeur_de_points
      – courbes_de_bezier
      – correction_couleur
    + delphi
    + outils
    + firemonkey
    + vcl_rtl
    + colibri_helpers
    + colibri_skelettons
  + formations
  + developpement_delphi
  + présentations
  + pascalissime
  + livres
  + entre_nous
  – télécharger

contacts
plan_du_site
– chercher :

RSS feed  
Blog

Formation Mise à Niveau Consolider la programmation objet, l'écriture de composants, l'accès aux bases de données, le multi-tâche - 3 jours