paint-brush
Comment créer une application pour déterminer la palette et les couleurs dominantes d'une imagepar@denissvinarchuk
406 lectures
406 lectures

Comment créer une application pour déterminer la palette et les couleurs dominantes d'une image

par Denis Svinarchuk18m2024/03/10
Read on Terminal Reader

Trop long; Pour lire

Une technique clé dans les applications d’édition d’images consiste à déterminer les couleurs dominantes. L'identification de ces couleurs dominantes est essentielle pour créer la palette d'une image qui, à son tour, reflète avec précision l'effet des outils sélectionnés et les résultats de l'édition. Cet article explique comment déterminer la palette d'une image dans un espace colorimétrique restreint, identifier les couleurs dominantes d'une image et faire la différence entre une palette et des couleurs dominantes.
featured image - Comment créer une application pour déterminer la palette et les couleurs dominantes d'une image
Denis Svinarchuk HackerNoon profile picture

Une technique clé dans les applications d’édition d’images consiste à déterminer les couleurs dominantes. L'identification de ces couleurs dominantes est essentielle pour créer la palette d'une image qui, à son tour, reflète avec précision l'effet des outils sélectionnés et les résultats de l'édition.


Cet article explique comment déterminer la palette d'une image dans un espace colorimétrique restreint, identifier les couleurs dominantes d'une image et faire la différence entre une palette et des couleurs dominantes.

Palette

La palette d'images est généralement une référence à toutes les couleurs présentes dans l'image d'origine. Essentiellement, il capture toute la gamme de nuances de couleurs, sur la base de l’échelle numérique des valeurs des signaux de stimulation de couleur.


Si nous convenons de modéliser un signal avec un certain niveau de précision, alors la plage de valeurs de ce signal représentera notre palette de couleurs disponible.


Chaque représentation unique d'une image et le mappage des couleurs d'une image sur cet espace en seront un sous-ensemble. En traitement du signal numérique (une image est aussi un signal), nous comprenons souvent diverses quantités à travers sa représentation discrète.


Ainsi, notre palette d'images peut être considérée comme un sous-ensemble de toutes les couleurs de l'image représentée par une carte discrète. Chaque couleur peut être indexée et affectée d'une valeur spécifique dans l'un des espaces colorimétriques. Pour nos besoins, nous utiliserons le modèle de couleur RVB.


Un défi important dans la présentation de la palette d’images de cette manière est l’immensité de la visibilité humaine. Il est peu probable que nous puissions analyser manuellement l'intégralité de la palette d'images, et cela n'a souvent aucun sens de stocker une image représentée par toutes les couleurs d'origine. Au lieu de cela, nous réduisons leur nombre à une limite raisonnable.


Ce processus, connu sous le nom de quantification des couleurs, consiste à réduire le nombre de couleurs du sous-ensemble complet pouvant être représenté dans une image à un nombre plus petit. Par exemple, les données couleur brutes 14 bits peuvent être représentées au format JPEG 8 bits ou TIFF 16 bits converties en PNG 8 bits.


Une solution simple pour déterminer les couleurs primaires d'une image consiste à envisager le processus de quantification des couleurs sur un ensemble très limité, comme 8 ou même 3 couleurs. Cela nous donne un aperçu des couleurs primaires de l’image, les rendant plus visibles et mémorables.


Idéalement, les couleurs de l'image finale devraient être harmonieuses, selon les principes énoncés par Johannes Itten ou Matyushin .


En identifiant les couleurs dominantes d'une image, nous pouvons créer un outil qui nous permet de visualiser « l'harmonie » de l'image. Cela peut également s'appliquer à l'harmonie de l'image après filtrage, ou « harmonisation synthétique ».


Ci-dessous, nous tenterons de développer un tel outil.

Histogramme du cube source de la palette

Coupe médiane

L'algorithme Median Cut est un algorithme largement utilisé et de haute qualité pour compresser la palette d'une image. Il est intégré à la plupart des principaux algorithmes de compression d'images avec perte, tels que JPEG, avec quelques modifications.


Le principe fondamental de l'algorithme Median Cut est de diviser séquentiellement l'histogramme cubique de l'image en médianes. Chaque sous-cube résultant contient à peu près un nombre égal de cases ou de pixels de la même couleur.


Une fois que le processus de division atteint un nombre prédéterminé de sous-cubes, nous calculons la couleur moyenne de chaque cube et la mappons aux couleurs correspondantes de l'image originale. Ce processus réduit efficacement le nombre de couleurs dans l’image originale, nous permettant ainsi de compresser l’image dans une taille de fichier plus petite.


Il peut sembler à première vue que la première partie de cet algorithme pourrait résoudre notre problème d'identification des couleurs primaires ou dominantes d'une image. Cependant, un problème potentiel survient lorsque le nombre de couleurs dans lesquelles nous divisons l’image est trop petit et que nous analysons uniquement un petit nombre de couleurs.


Nous pourrions finir par identifier des couleurs qui n'existent pas réellement dans l'image, car la moyenne des couleurs est excessive. Nous pourrions choisir la couleur avec le bac maximum de chaque cube et la qualifier de dominante, mais elle ne constituerait alors plus une palette.


Nous réserverons donc cette approche à la détermination d'une version compressée de la palette de l'image, qui peut également être utilisée comme outil d'analyse visuelle de l'image en termes de son « utilité harmonieuse ». Pour identifier les couleurs dominantes, nous utiliserons l'analyse statistique : recherche de maxima locaux dans le même histogramme cubique.

Maximales locales

Pour identifier les maxima locaux, nous implémenterons un code spécifique. L’auteur, qui est également un artiste talentueux, fournit une excellente description de l’algorithme. Essentiellement, nous rassemblons d’abord les statistiques de l’image dans le même histogramme RVB tridimensionnel utilisé dans l’algorithme Median Cut.

Chaque cellule de l'histogramme cubique contiendra des groupes de couleurs et la somme de toutes les valeurs de chaque couleur incluses dans la cellule. Étant donné la dimension limitée de l'histogramme à une résolution de 32x32x32 (à l'origine 30x30x30), l'accumulation de la somme simplifie le calcul de la couleur moyenne des cellules.

Nous recherchons ensuite le maximum local en explorant minutieusement tout l'espace et en le comparant avec les cellules voisines.

Ensuite, nous réduisons de manière itérative le nombre de maxima locaux à la quantité requise, en éliminant les couleurs similaires avec moins de poids. Pour tous les maxima locaux restants, nous calculons les couleurs moyennes de toutes les valeurs incluses dans la liste.


Étant donné que la densité de couleur dans les maxima locaux est plus élevée et que la différence entre les valeurs des pixels est plus petite que dans les cubes de la coupe médiane, les couleurs seront plus proches de celles présentes dans l'image principale et représenteront plus précisément ses couleurs dominantes.


Cela révèle la principale différence entre les deux modèles : l'acquisition d'une « palette compressée » versus la recherche de maxima locaux ou de couleurs dominantes . En mappant l'image principale de la « palette compressée », nous en créerons une nouvelle qui conservera la même balance des couleurs que l'image principale , bien que sous une forme considérablement tronquée.


En revanche, les couleurs dominantes ne décrivent que la composition des couleurs principalement présentes dans l'image . Ils ne peuvent pas être transformés en quelque chose de nouveau avec une balance des couleurs appropriée.

Mise en œuvre

En utilisant cette tâche comme exemple, nous démontrerons à quel point il est simple de développer une application prête à l'emploi pour l'analyse et la manipulation d'images à l'aide du framework IMProcessing . Nous commencerons par des fonctionnalités absentes dans les autres moteurs.


Par exemple, le framework a la capacité de lire des fichiers Adobe .cube avec des CLUT préexistants et peut extraire un histogramme cubique tridimensionnel à partir d'une image en temps réel.


En capitalisant sur cela, nous visons à créer une application capable de :

  1. Téléchargez des fichiers au format JFIF (jpeg).


  2. « Normaliser » l'image originale.


  3. Contrôlez l’intensité du « normalisateur ».


  4. Incorporez une LUT arbitraire à partir d’un fichier Adobe .cube dans le traitement.


  5. Gérer l’intensité de l’impact de CLUT.


  6. Afficher un histogramme linéaire de l'image.


  7. Exposer la « palette compressée » et les couleurs dominantes de l'image ainsi que leur représentation numérique sous forme de triplet (r,g,b)


Le produit final de notre construction ressemblera à ce jouet interactif :

Ici, il est clair comment la simple « normalisation » de l'image influence positivement la diversité de la palette finale, redistribuant les couleurs dominantes dans un ensemble plus varié et harmonieux.

"Normalisateur"

Nous allons assembler un filtre à partir de deux préexistants :

  1. IMPContrastFilter - permet l'étirement de l'histogramme de l'image jusqu'à des limites spécifiées


  2. IMPAutoWBFilter - effectue une correction automatique de la balance des blancs en fonction de la recherche de la couleur moyenne et de la correction des tons parasites dans l'image. Il s'agit essentiellement d'une légère modification d'une idée empruntée au blog d'Andrey Zhuravlev .


 import IMProcessing /// Image filter public class IMPTestFilter:IMPFilter { /// We will use a contrast control filter through histogram stretching var contrastFilter:IMPContrastFilter! /// Auto white balance filter var awbFilter:IMPAutoWBFilter! /// Image Linear Histogram Analyzer var sourceAnalyzer:IMPHistogramAnalyzer! /// Solver of the histogram analyzer for calculating the lightness boundaries of the image let rangeSolver = IMPHistogramRangeSolver() public required init(context: IMPContext) { super.init(context: context) // Initialize filters in context contrastFilter = IMPContrastFilter(context: context) awbFilter = IMPAutoWBFilter(context: context) // Add filters to the stack addFilter(contrastFilter) addFilter(awbFilter) // Initialize the histogram analyzer sourceAnalyzer = IMPHistogramAnalyzer(context: self.context) // Add a light boundary search solver to the analyzer sourceAnalyzer.addSolver(rangeSolver) // Add an observing handler to the filter for // to pass the current image frame to the analyzer addSourceObserver { (source) - Void in self.sourceAnalyzer.source = source } // Add an observing handler for updating analysis calculations to the analyzer sourceAnalyzer.addUpdateObserver({ (histogram) - Void in // set the lightness boundaries in the contrast filter each time the image changes self.contrastFilter.adjustment.minimum = self.rangeSolver.minimum self.contrastFilter.adjustment.maximum = self.rangeSolver.maximum }) } }


Solveur de palettes

L'IMProcessing Framework se distingue de nombreuses plateformes similaires par son organisation unique des calculs. Il utilise un groupe dédié de filtres qui ne sont essentiellement pas des filtres de traitement.


Les objets de ces classes ne modifient pas l'image dans les domaines homogène et spatial. Au lieu de cela, ils effectuent certains calculs et représentations de métriques à analyser dans des extensions spéciales pour résoudre des problèmes spécifiques.


Par exemple, un objet de la classe IMPHistogramAnalyzer peut ajouter simultanément plusieurs solveurs qui calculent la couleur moyenne de l'image, la plage de lumière, la division zonale, etc.


Nous utilisons le solveur pour étendre l'analyse d'IMPHistogramCubeAnalyzer afin de calculer la palette et la liste des couleurs dominantes. Les résultats du calcul sont ensuite affichés dans un NSTableView mis à jour.


 import IMProcessing /// Types of distribution of image color accents /// /// - palette: palette for quantizing image colors. /// calculated using the median-cut transformation scheme: /// http://www.leptonica.com/papers/mediancut.pdf /// - dominants: calculation of dominant colors of an image by searching for local maxima /// color distribution density functions: /// https://github.com/pixelogik/ColorCube /// public enum IMPPaletteType{ case palette case dominants } /// Solver of the cubic color histogram analyzer IMPHistogramCubeAnalyzer public class IMPPaletteSolver: IMPHistogramCubeSolver { /// Maximum number of palette colors for analysis public var maxColors = Int(8) /// List of found colors public var colors = [IMPColor]() /// Palette type public var type = IMPPaletteType.dominants /// Solver handler handler /// - parameter analyzer: link to the analyzer /// - parameter histogram: cubic histogram of the image /// - parameter imageSize: image size public func analizerDidUpdate(analizer: IMPHistogramCubeAnalyzer, histogram: IMPHistogramCube, imageSize: CGSize) { var p = [float3]() if type == .palette{ p = histogram.cube.palette(count: maxColors) } else if type == .dominants { p = histogram.cube.dominantColors(count: maxColors) } colors.removeAll() for c in p { colors.append(IMPColor(color: float4(rgb: c, a: 1))) } } }


Nous assemblons tous les composants dans le contrôleur de vue

Le contrôleur aura besoin du filtre d'application principal, que nous appellerons IMPTestFilter , d'un filtre CLUT nommé IMPLutFilter, du IMPHistogramView prêt à l'emploi pour afficher un histogramme "normal", du IMPHistogramCubeAnalyzer qui est un analyseur d'histogramme cubique auquel nous nous attacherons. notre solveur, le IMPPaletteSolver.


Enfin, nous utiliserons IMPImageView comme fenêtre principale pour afficher l'image, et le IMPContext commun est la classe clé utilisée par tous les constructeurs du framework.


 class ViewController: NSViewController { // // Processing context // let context = IMPContext() // // Window for presenting the loaded image // var imageView:IMPImageView! var pannelScrollView = NSScrollView() // // Window for displaying the image histogram // var histogramView:IMPHistogramView! // // NSTableView - views of a list of colors from the palette // var paletteView:IMPPaletteListView! // // Main filter // var filter:IMPTestFilter! // // CLUT filter from Adobe Cube files // var lutFilter:IMPLutFilter? // // Analyzer of a cubic histogram of an image in RGB space // var histograCube:IMPHistogramCubeAnalyzer! // // Our solver for finding colors // var paletteSolver = IMPPaletteSolver() var paletteTypeChooser:NSSegmentedControl! override func viewDidLoad() { super.viewDidLoad() configurePannel() // // Initialize the objects we need // filter = IMPTestFilter(context: context) histograCube = IMPHistogramCubeAnalyzer(context: context) histograCube.addSolver(paletteSolver) imageView = IMPImageView(context: context, frame: view.bounds) imageView.filter = filter imageView.backgroundColor = IMPColor(color: IMPPrefs.colors.background) // // Add another handler to monitor the original image // (another one was added in the main filter IMPTestFilter) // filter.addSourceObserver { (source) -> Void in // // to minimize calculations, the analyzer will compress the image to 1000px on the wide side // if let size = source.texture?.size { let scale = 1000/max(size.width,size.height) self.histograCube.downScaleFactor = scale.float } } // Add an observer to the filter to process the filtering results // filter.addDestinationObserver { (destination) -> Void in // pass the image to the histogram indicator self.histogramView.source = destination // pass the result to the cubic histogram analyzer self.histograCube.source = destination } // // The results of updating the analyzer calculation are displayed in the color list window // histograCube.addUpdateObserver { (histogram) -> Void in self.asyncChanges({ () -> Void in self.paletteView.colorList = self.paletteSolver.colors }) } view.addSubview(imageView) .... IMPDocument.sharedInstance.addDocumentObserver { (file, type) -> Void in if type == .Image { do{ // // Load the file and associate it with the filter source // self.imageView.source = try IMPImageProvider(context: self.imageView.context, file: file) self.asyncChanges({ () -> Void in self.zoomFit() }) } catch let error as NSError { self.asyncChanges({ () -> Void in let alert = NSAlert(error: error) alert.runModal() }) } } else if type == .LUT { do { // // Initialize the CLUT descriptor // var description = IMPImageProvider.LutDescription() // // Load CLUT // let lutProvider = try IMPImageProvider(context: self.context, cubeFile: file, description: &description) if let lut = self.lutFilter{ // // If a CLUT filter has been added, update its LUT table from the file with the received descriptor // lut.update(lutProvider, description:description) } else{ // // Create a new LUT filter // self.lutFilter = IMPLutFilter(context: self.context, lut: lutProvider, description: description) } // // Add a LUT filter, if this filter has already been added nothing happens // self.filter.addFilter(self.lutFilter!) } catch let error as NSError { self.asyncChanges({ () -> Void in let alert = NSAlert(error: error) alert.runModal() }) } } } ....


Comme vous pouvez le constater, le traitement des photos devient plus simple et plus accessible aux utilisateurs moyens, permettant à pratiquement tout le monde de maîtriser ce processus.


L'ensemble du projet peut être téléchargé, assemblé et testé à partir du référentiel du projet ImageMetalling : ImageMetalling-08 . Pour un assemblage correct, la méga-bibliothèque permettant de travailler avec des fichiers JPEG (JFIF), libjpeg-turbo , doit être installée localement.


Actuellement, il s’agit de la meilleure implémentation de prise en charge de ce format.