Arrotondare i valori dei colors al più vicino di un piccolo insieme di colors

Preambolo

Come parte di un progetto a cui sto lavorando, sto cercando di fornire un modo conveniente per cercare immagini nel nostro sistema. Al momento forniamo ricerche per vari tipi di metadati aggiunti dall’utente (ad esempio titolo, descrizione, parole chiave) e vari metadati che estrapiamo (ad esempio EXIF, IPTC, XMP, ecc.). Vorrei anche aggiungere una “ricerca colore” simile a ciò che puoi vedere nella ricerca di immagini di Google.

Il progetto utilizza PHP e possiamo usare l’estensione Imagemagick per segmentare e quantizzare l’immagine ed estrarre i colors più “significativi” dall’immagine; Non sono del tutto certo dei risultati che sto ottenendo qui, ma sembrano ragionevolmente accurati e sicuramente migliori di niente.

Il problema

Il punto su cui sto avendo difficoltà è la conversione di questi colors significativi in ​​un insieme di colors rappresentativi, ad esempio quando guardi la ricerca di immagini di Google c’è un set di 12 colors. Mi piacerebbe matematicamente “arrotondare” il valore del mio colore al colore rappresentativo più vicino, in modo da poter indicizzare l’immagine con i colors che ho rilevato e quindi sfocare i risultati della ricerca in questo modo.

Eventuali suggerimenti?

Il primo passo sarebbe definire i colors che vuoi confrontare.

Il secondo passo è trovare la distanza più piccola dal tuo colore a uno dei colors che hai scelto nel passaggio precedente. Per poter misurare questa distanza è necessario uno spazio euclideo in cui modellare i colors.

Ovviamente la scelta più semplice sarebbe lo spazio RGB

alt text

E la distanza tra due colors C 1 (r 1 , g 1 , b 1 ) e C 2 (r 2 , g 2 , b 2 ) sarebbe

sqrt ((r 1 – r 2 ) 2 + (g 1 – g 2 ) 2 + (b 1 – b 2 ) 2 ) .

Ma se hai bisogno di più precisione, sarebbe meglio usare lo spazio bicono Hue-Chroma-Lightness, un derivato del cilindro HSL.

alt text

Nello spazio RGB le cose erano dirette come R, G e B dove ciascuna su un asse separato. In HCL abbiamo bisogno di calcolare le coordinate su ciascun asse.

Prima di tutto calcoliamo il chroma (che è leggermente diverso dalla saturazione) come:

Chroma = max (rosso, verde, blu) – min (rosso, verde, blu)

Quindi normalizziamo il nostro valore H, C e L in modo che H passi da 0 a 2 (per coprire un cerchio se moltiplichiamo per PI e prendiamo radianti come unità), C va da 0 a 1 (il raggio del cerchio trigonometrico) e L passa da -1 (nero) a 1 (bianco).

Quindi prendiamo z = L senza alcuna trasformazione in quanto è chiaro dall’immagine che segue l’asse verticale.

Possiamo facilmente osservare che per un colore, Chroma è la distanza dall’asse z e Hue è l’angolo. Quindi otteniamo

x = C * cos (H * PI) e
y = C * sin (H * PI)

A questo punto x, y e z saranno tutti in [-1, 1] e la distanza tra due colors sarà, usando la stessa formula di cui sopra,

sqrt ((x 1 – x 2 ) 2 + (y 1 – y 2 ) 2 + (z 1 – z 2 ) 2 ) .


Per ottenere una precisione ancora maggiore e trovare il colore più vicino in base alla percezione dei colors umani, è ansible utilizzare lo spazio di modellazione CIE-L * ab e calcolare la distanza con uno di questi algoritmi . I principi sono gli stessi dei due casi presentati sopra, solo gli algoritmi sono più complessi.


Aggiornamento (7 anni dopo)

Finalmente xkcd ha presentato un fumetto che posso usare in questo post!

inserisci la descrizione dell'immagine qui

https://xkcd.com/1882/

Questa è solo un’idea approssimativa – dovrai adattarla alle tue esigenze.

Fondamentalmente, ho pensato che, come i colors sono registrati come RGB, sia come una stringa esadecimale “# 000000” a “#ffffff”, o come un set RGB “rgb (0,0,0)” a “rgb (255,255,255)” e questi sono intercambiabili / traducibili, questo è un semplice problema di arrotondamento matematico.

Nell’intera gamma di colors ci sarebbe (16 * 16) * (16 * 16) * (16 * 16) = 256 * 256 * 256 = 16,777,216 possibili colors.

Arrotondando i colors al loro carattere singolo più vicino, il valore esadecimale riduce quello a 16 * 16 * 16 = 4,096 possibili colors. Ancora troppe, ma avvicinandosi.

Arrotondando i colors a un singolo valore di carattere, ma poi limitando che oltre a essere uno dei 4 (0,3,7, f) lo si riduce a 4 * 4 * 4 = 32. Abbastanza vicino per me.

Quindi, ho creato una funzione PHP molto semplice per cercare di ottenere ciò:

function coloround( $incolor ){ $inR = hexdec( $incolor{0}.$incolor{1} )+1; $inG = hexdec( $incolor{2}.$incolor{3} )+1; $inB = hexdec( $incolor{4}.$incolor{5} )+1; # Round from 256 values to 16 $outR = round( $outR/16 ); $outG = round( $outG/16 ); $outB = round( $outB/16 ); # Round from 16 to 4 $outR = round( $outR/4 ); $outG = round( $outG/4 ); $outB = round( $outB/4 ); # Translate to Hex $outR = dechex( max( 0 , $outR*4-1 ) ); $outG = dechex( max( 0 , $outG*4-1 ) ); $outB = dechex( max( 0 , $outB*4-1 ) ); # Output echo sprintf( ' > %s has been rounded to %s
' , $incolor , $outR.$outG.$outB , $incolor , $outR.$outG.$outB ); }

Questa funzione, una volta passata una stringa esadecimale, fa eco a un campione del colore originale ea un campione del colore abbreviato.

Questo è solo un proof-of-concept di base, in quanto non conosco il formato in cui Imagemagick restituisce i colors, ma potresti essere in grado di utilizzare questa logica per crearne di tuoi.

Da questi 32 colors, quindi, è ansible raggruppare il simile (ci sarebbero probabilmente circa 8 sfumature di grigio) e nominare il resto per permettere agli utenti di cercare da loro.

 function getSimilarColors (color) { var base_colors=["660000","990000","cc0000","cc3333","ea4c88","993399","663399","333399","0066cc","0099cc","66cccc","77cc33","669900","336600","666600","999900","cccc33","ffff00","ffcc33","ff9900","ff6600","cc6633","996633","663300","000000","999999","cccccc","ffffff"]; //Convert to RGB, then R, G, B var color_rgb = hex2rgb(color); var color_r = color_rgb.split(',')[0]; var color_g = color_rgb.split(',')[1]; var color_b = color_rgb.split(',')[2]; //Create an emtyp array for the difference betwwen the colors var differenceArray=[]; //Function to find the smallest value in an array Array.min = function( array ){ return Math.min.apply( Math, array ); }; //Convert the HEX color in the array to RGB colors, split them up to RGB, then find out the difference between the "color" and the colors in the array $.each(base_colors, function(index, value) { var base_color_rgb = hex2rgb(value); var base_colors_r = base_color_rgb.split(',')[0]; var base_colors_g = base_color_rgb.split(',')[1]; var base_colors_b = base_color_rgb.split(',')[2]; //Add the difference to the differenceArray differenceArray.push(Math.sqrt((color_r-base_colors_r)*(color_r-base_colors_r)+(color_g-base_colors_g)*(color_g-base_colors_g)+(color_b-base_colors_b)*(color_b-base_colors_b))); }); //Get the lowest number from the differenceArray var lowest = Array.min(differenceArray); //Get the index for that lowest number var index = differenceArray.indexOf(lowest); //Function to convert HEX to RGB function hex2rgb( colour ) { var r,g,b; if ( colour.charAt(0) == '#' ) { colour = colour.substr(1); } r = colour.charAt(0) + colour.charAt(1); g = colour.charAt(2) + colour.charAt(3); b = colour.charAt(4) + colour.charAt(5); r = parseInt( r,16 ); g = parseInt( g,16 ); b = parseInt( b ,16); return r+','+g+','+b; } //Return the HEX code return base_colors[index]; } 

A seconda del numero di colors che stai cercando, perché non provare a utilizzare operatori bit a bit (di riferimento PHP qui , dato che l’hai menzionato nella domanda) per ridurre il numero di cifre significative? È ansible arrotondare i valori RGB prima di spostarli per aumentare la precisione.