paint-brush
So erstellen Sie eine Anwendung zum Bestimmen der Palette und der vorherrschenden Farben eines Bildesvon@denissvinarchuk
406 Lesungen
406 Lesungen

So erstellen Sie eine Anwendung zum Bestimmen der Palette und der vorherrschenden Farben eines Bildes

von Denis Svinarchuk18m2024/03/10
Read on Terminal Reader

Zu lang; Lesen

Eine Schlüsseltechnik in Bildbearbeitungsanwendungen ist die Bestimmung der dominanten Farben. Die Identifizierung dieser dominanten Farben ist für die Erstellung einer Bildpalette von entscheidender Bedeutung, die wiederum die Wirkung ausgewählter Werkzeuge und die Ergebnisse der Bearbeitung genau widerspiegelt. In diesem Artikel erfahren Sie, wie Sie die Palette eines Bildes innerhalb eines eingeschränkten Farbraums bestimmen, die dominanten Farben eines Bildes identifizieren und zwischen einer Palette und dominanten Farben unterscheiden.
featured image - So erstellen Sie eine Anwendung zum Bestimmen der Palette und der vorherrschenden Farben eines Bildes
Denis Svinarchuk HackerNoon profile picture

Eine Schlüsseltechnik in Bildbearbeitungsanwendungen ist die Bestimmung der dominanten Farben. Die Identifizierung dieser dominanten Farben ist für die Erstellung einer Bildpalette von entscheidender Bedeutung, die wiederum die Wirkung ausgewählter Werkzeuge und die Ergebnisse der Bearbeitung genau widerspiegelt.


In diesem Artikel erfahren Sie, wie Sie die Palette eines Bildes innerhalb eines eingeschränkten Farbraums bestimmen, die dominanten Farben eines Bildes identifizieren und zwischen einer Palette und dominanten Farben unterscheiden.

Palette

Die Bildpalette ist normalerweise ein Verweis auf alle im Originalbild vorhandenen Farben. Im Wesentlichen erfasst es das gesamte Spektrum an Farbtönen, basierend auf der numerischen Skala der Farbreizsignalwerte.


Wenn wir uns darauf einigen, ein Signal mit einem bestimmten Genauigkeitsgrad zu modellieren, dann stellt der Bereich dieser Signalwerte unsere verfügbare Farbpalette dar.


Jede einzigartige Darstellung eines Bildes und die Zuordnung der Farben eines Bildes zu diesem Raum wird eine Teilmenge davon sein. In der digitalen Signalverarbeitung (ein Bild ist auch ein Signal) verstehen wir oft eine Vielzahl von Größen durch deren diskrete Darstellung.


Somit kann unsere Bildpalette als Teilmenge aller Farben im Bild betrachtet werden, die durch eine diskrete Karte dargestellt werden. Jede Farbe kann in einem der Farbräume indiziert und einem bestimmten Wert zugewiesen werden. Für unsere Zwecke verwenden wir das RGB-Farbmodell.


Eine große Herausforderung bei der Darstellung der Bildpalette auf diese Weise ist die Weite der menschlichen Sichtbarkeit. Es ist unwahrscheinlich, dass wir die gesamte Bildpalette manuell analysieren können, und es macht oft keinen Sinn, ein Bild zu speichern, das alle Originalfarben enthält. Stattdessen reduzieren wir ihre Zahl auf ein angemessenes Maß.


Bei diesem als Farbquantisierung bezeichneten Prozess wird die Anzahl der Farben aus der gesamten Teilmenge, die in einem Bild dargestellt werden kann, auf eine kleinere reduziert. Beispielsweise können 14-Bit-Rohfarbdaten in 8-Bit-JPEG oder 16-Bit-TIFF, konvertiert in 8-Bit-PNG, dargestellt werden.


Eine einfache Lösung zur Bestimmung der Primärfarben eines Bildes besteht darin, sich den Prozess der Farbquantisierung auf einen sehr begrenzten Satz, beispielsweise 8 oder sogar 3 Farben, vorzustellen. Dies gibt uns Einblick in die Primärfarben im Bild und macht sie auffälliger und einprägsamer.


Idealerweise sollten die Farben im endgültigen Bild harmonisch sein und den von Johannes Itten oder Matjuschin dargelegten Prinzipien folgen.


Indem wir die dominanten Farben eines Bildes identifizieren, können wir ein Werkzeug erstellen, mit dem wir die „Harmonie“ des Bildes visualisieren können. Dies kann auch für die Harmonie des Bildes nach der Filterung oder „synthetische Harmonisierung“ gelten.


Im Folgenden werden wir versuchen, ein solches Tool zu entwickeln.

Würfelhistogramm der Palettenquelle

Medianschnitt

Ein weit verbreiteter und qualitativ hochwertiger Algorithmus zum Komprimieren der Palette eines Bildes ist der Median Cut-Algorithmus. Es ist in die meisten großen verlustbehafteten Bildkomprimierungsalgorithmen wie JPEG integriert, wenn auch mit einigen Modifikationen.


Das Grundprinzip des Median Cut-Algorithmus besteht darin, das kubische Histogramm des Bildes sequentiell in Mediane zu unterteilen. Jeder resultierende Unterwürfel enthält ungefähr die gleiche Anzahl von Bins oder Pixeln derselben Farbe.


Sobald der Teilungsprozess eine vorgegebene Anzahl von Unterwürfeln erreicht, berechnen wir die durchschnittliche Farbe jedes Würfels und ordnen sie den entsprechenden Farben des Originalbilds zu. Durch diesen Vorgang wird die Anzahl der Farben im Originalbild effektiv reduziert, sodass wir das Bild auf eine kleinere Dateigröße komprimieren können.


Auf den ersten Blick könnte es so aussehen, als ob der erste Teil dieses Algorithmus unser Problem lösen könnte, die primären oder dominanten Farben eines Bildes zu identifizieren. Ein potenzielles Problem entsteht jedoch, wenn die Anzahl der Farben, in die wir das Bild unterteilen, zu gering ist und wir nur eine kleine Anzahl von Farben analysieren.


Es kann sein, dass wir Farben identifizieren, die im Bild gar nicht vorkommen, weil die Farben übermäßig gemittelt sind. Wir könnten aus jedem Würfel die Farbe mit der größten Klasse auswählen und sie als dominant kennzeichnen, aber dann würde sie keine Palette mehr darstellen.


Daher behalten wir uns diesen Ansatz der Bestimmung einer komprimierten Version der Bildpalette vor, die auch als Werkzeug zur visuellen Analyse des Bildes im Hinblick auf seinen „harmonischen Nutzen“ verwendet werden kann. Um die vorherrschenden Farben zu identifizieren, verwenden wir eine statistische Analyse: Wir suchen nach lokalen Maxima im selben kubischen Histogramm.

Lokale Maxima

Um die lokalen Maxima zu identifizieren, werden wir einen spezifischen Code implementieren. Der Autor, der auch ein erfahrener Künstler ist, liefert eine hervorragende Beschreibung des Algorithmus. Im Wesentlichen sammeln wir zunächst die Bildstatistiken in demselben dreidimensionalen RGB-Histogramm, das im Median Cut-Algorithmus verwendet wird.

Jede Zelle des kubischen Histogramms enthält Farbklassen und die Summe aller Werte jeder in der Zelle enthaltenen Farbe. Angesichts der begrenzten Histogrammdimension auf eine Auflösung von 32 x 32 x 32 (ursprünglich 30 x 30 x 30) vereinfacht die Akkumulation der Summe die Berechnung der durchschnittlichen Zellenfarbe.

Anschließend suchen wir nach dem lokalen Maximum, indem wir den gesamten Raum gründlich erkunden und mit benachbarten Zellen vergleichen.

Anschließend reduzieren wir iterativ die Anzahl der lokalen Maxima auf die erforderliche Menge und verwerfen ähnliche Farben mit geringerem Gewicht. Für alle verbleibenden lokalen Maxima berechnen wir die durchschnittlichen Farben aller in der Liste enthaltenen Werte.


Da die Farbdichte in lokalen Maxima höher ist und der Unterschied zwischen Pixelwerten kleiner ist als in Würfeln aus dem Median Cut, ähneln die Farben eher denen im Hauptbild und stellen dessen vorherrschende Farben genauer dar.


Dies zeigt den Hauptunterschied zwischen den beiden Modellen: die Erfassung einer „komprimierten Palette“ im Vergleich zur Suche nach lokalen Maxima oder dominanten Farben . Indem wir das primäre Bild mit der „komprimierten Palette“ zuordnen, erstellen wir ein neues , das die gleiche Farbbalance wie das Hauptbild beibehält , wenn auch in stark verkürzter Form.


Dominante Farben hingegen beschreiben nur die Zusammensetzung der im Bild hauptsächlich vorhandenen Farben. Mit einer geeigneten Farbbalance lassen sie sich nicht in etwas Neues verwandeln.

Implementierung

Am Beispiel dieser Aufgabe zeigen wir, wie einfach es ist, mit dem IMProcessing Framework eine gebrauchsfertige Anwendung zur Bildanalyse und -manipulation zu entwickeln. Wir beginnen mit Funktionalitäten, die in anderen Engines fehlen.


Das Framework kann beispielsweise Adobe .cube- Dateien mit bereits vorhandenen CLUTs lesen und in Echtzeit ein dreidimensionales kubisches Histogramm aus einem Bild extrahieren.


Auf dieser Grundlage möchten wir eine Anwendung erstellen, die Folgendes kann:

  1. Laden Sie Dateien im JFIF-Format (jpeg) hoch.


  2. „Normalisieren“ Sie das Originalbild.


  3. Steuern Sie die Intensität des „Normalisierers“.


  4. Integrieren Sie eine beliebige LUT aus einer Adobe .cube-Datei in die Verarbeitung.


  5. Verwalten Sie die Intensität der CLUT-Auswirkungen.


  6. Zeigt ein lineares Histogramm des Bildes an.


  7. Zeigen Sie die „komprimierte Palette“ und die dominanten Farben des Bildes sowie deren numerische Darstellung in Form eines Tripletts (r,g,b) an.


Das Endprodukt unserer Konstruktion wird diesem interaktiven Spielzeug ähneln:

Hier wird deutlich, wie sich die einfache „Normalisierung“ des Bildes positiv auf die Vielfalt der endgültigen Palette auswirkt und dominante Farben in ein vielfältigeres und harmonischeres Set umverteilt.

„Normalisierer“

Wir werden einen Filter aus zwei bereits vorhandenen zusammensetzen:

  1. IMPContrastFilter – ermöglicht die Streckung des Bildhistogramms auf bestimmte Grenzen


  2. IMPAutoWBFilter – führt eine automatische Weißabgleichkorrektur basierend auf der Suche nach der Durchschnittsfarbe und der Korrektur von Störtönen im Bild durch. Dies ist im Wesentlichen eine leichte Modifikation einer Idee, die Andrey Zhuravlevs Blog entlehnt ist.


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


Palettenlöser

Das IMProcessing Framework hebt sich von vielen ähnlichen Plattformen durch seine einzigartige Organisation der Berechnungen ab. Es verwendet eine spezielle Gruppe von Filtern, bei denen es sich im Wesentlichen nicht um Verarbeitungsfilter handelt.


Die Objekte dieser Klassen verändern das Bild im homogenen und räumlichen Bereich nicht. Stattdessen führen sie bestimmte Berechnungen und Darstellungen von Metriken zur Analyse in speziellen Expandern durch, um spezifische Probleme zu lösen.


Beispielsweise kann ein Objekt der IMPHistogramAnalyzer-Klasse gleichzeitig mehrere Solver hinzufügen, die die durchschnittliche Farbe des Bildes, den Lichtbereich, die Zoneneinteilung usw. berechnen.


Wir verwenden den Solver, um die Analyse von IMPHistogramCubeAnalyzer zu erweitern, um die Palette und die Liste der dominanten Farben zu berechnen. Die Berechnungsergebnisse werden dann in einem aktualisierten NSTableView angezeigt.


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


Wir montieren alle Komponenten im View Controller

Der Controller benötigt den Hauptanwendungsfilter, den wir IMPTestFilter nennen, einen CLUT-Filter namens IMPLutFilter, den gebrauchsfertigen IMPHistogramView zum Anzeigen eines „normalen“ Histogramms, den IMPHistogramCubeAnalyzer, einen kubischen Histogrammanalysator, den wir anhängen werden unser Solver, der IMPPaletteSolver.


Schließlich verwenden wir IMPImageView als Hauptfenster zum Anzeigen des Bildes, und der gemeinsame IMPContext ist die Schlüsselklasse, die von allen Konstruktoren des Frameworks verwendet wird.


 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() }) } } } ....


Wie Sie sehen, wird die Fotobearbeitung immer einfacher und für Durchschnittsanwender zugänglicher, sodass praktisch jeder diesen Prozess beherrschen kann.


Das gesamte Projekt kann aus dem ImageMetalling- Projekt- Repository heruntergeladen, zusammengestellt und getestet werden: ImageMetalling-08 . Für eine ordnungsgemäße Assemblierung muss die Megabibliothek für die Arbeit mit JPEG-Dateien (JFIF), libjpeg-turbo , lokal installiert sein.


Derzeit ist dies die beste Implementierung der Unterstützung für dieses Format.