Come generare automaticamente N colori "distinti"?


194

Ho scritto i due metodi seguenti per selezionare automaticamente N colori distinti. Funziona definendo una funzione lineare a tratti sul cubo RGB. Il vantaggio di questo è che puoi anche ottenere una scala progressiva se è quello che vuoi, ma quando N diventa grande i colori possono iniziare a sembrare simili. Posso anche immaginare di suddividere uniformemente il cubo RGB in un reticolo e quindi di disegnare punti. Qualcuno conosce altri metodi? Escludo la definizione di un elenco e poi lo sto semplicemente scorrendo. Dovrei anche dire che in genere non mi importa se si scontrano o non sembrano belle, devono solo essere visivamente distinti.

public static List<Color> pick(int num) {
    List<Color> colors = new ArrayList<Color>();
    if (num < 2)
        return colors;
    float dx = 1.0f / (float) (num - 1);
    for (int i = 0; i < num; i++) {
        colors.add(get(i * dx));
    }
    return colors;
}

public static Color get(float x) {
    float r = 0.0f;
    float g = 0.0f;
    float b = 1.0f;
    if (x >= 0.0f && x < 0.2f) {
        x = x / 0.2f;
        r = 0.0f;
        g = x;
        b = 1.0f;
    } else if (x >= 0.2f && x < 0.4f) {
        x = (x - 0.2f) / 0.2f;
        r = 0.0f;
        g = 1.0f;
        b = 1.0f - x;
    } else if (x >= 0.4f && x < 0.6f) {
        x = (x - 0.4f) / 0.2f;
        r = x;
        g = 1.0f;
        b = 0.0f;
    } else if (x >= 0.6f && x < 0.8f) {
        x = (x - 0.6f) / 0.2f;
        r = 1.0f;
        g = 1.0f - x;
        b = 0.0f;
    } else if (x >= 0.8f && x <= 1.0f) {
        x = (x - 0.8f) / 0.2f;
        r = 1.0f;
        g = 0.0f;
        b = x;
    }
    return new Color(r, g, b);
}

5
Domanda dei programmatori fortemente pertinente con risposte interessanti: " Generazione di combinazioni di colori - teoria e algoritmi ".
Alexey Popkov,

2
La percezione del colore umano non è lineare, sfortunatamente. Potrebbe essere necessario tenere conto del turno Bezold – Brücke se si utilizzano intensità diverse. Ci sono anche buone informazioni qui: vis4.net/blog/posts/avoid-equidistant-hsv-colors
spex

Risposte:


80

È possibile utilizzare il modello di colore HSL per creare i colori.

Se tutto ciò che desideri sono tonalità diverse (probabilmente) e lievi variazioni di luminosità o saturazione, puoi distribuire le tonalità in questo modo:

// assumes hue [0, 360), saturation [0, 100), lightness [0, 100)

for(i = 0; i < 360; i += 360 / num_colors) {
    HSLColor c;
    c.hue = i;
    c.saturation = 90 + randf() * 10;
    c.lightness = 50 + randf() * 10;

    addColor(c);
}

2
Questa tecnica è intelligente. Scommetto che otterrà più risultati estetici dei miei.
mqp

45
Ciò presuppone che i valori di tonalità equidistanziati siano ugualmente diversi percettivamente. Anche scontando varie forme di daltonismo, questo non è vero per la maggior parte delle persone: la differenza tra 120 ° (verde) e 135 ° (leggermente leggermente verde menta) è impercettibile, mentre la differenza tra 30 ° (arancione) e 45 ° (pesca) è abbastanza ovvio. Per ottenere risultati ottimali è necessaria una spaziatura non lineare lungo la tonalità.
Phrogz,

18
@mquander - Non è affatto intelligente. Non c'è nulla che impedisca a questo algoritmo di scegliere accidentalmente due colori quasi identici. La mia risposta è migliore e la risposta di Ohadsc è molto migliore.
Rocketmagnet,

1
Questo è sbagliato per i motivi già menzionati, ma anche perché non stai scegliendo in modo uniforme .
Sam Hocevar,

3
@strager qual è il valore atteso di randf ()
Killrawr

242

Questa domanda appare in parecchie discussioni SO:

Vengono proposte diverse soluzioni, ma nessuna è ottimale. Fortunatamente, la scienza viene in soccorso

Arbitrario N

Gli ultimi 2 saranno gratuiti tramite la maggior parte delle biblioteche / proxy universitari.

N è finito e relativamente piccolo

In questo caso, si potrebbe optare per una soluzione di elenco. Un articolo molto interessante sull'argomento è disponibile gratuitamente:

Esistono diversi elenchi di colori da considerare:

  • Elenco di 11 colori di Boynton che non sono quasi mai confusi (disponibile nel primo documento della sezione precedente)
  • 22 colori di massimo contrasto di Kelly (disponibili nella carta sopra)

Ho anche incontrato questa palette da uno studente del MIT. Infine, i seguenti collegamenti possono essere utili per la conversione tra diversi sistemi / coordinate di colore (alcuni colori negli articoli non sono specificati in RGB, ad esempio):

Per l'elenco di Kelly e Boynton, ho già effettuato la conversione in RGB (ad eccezione del bianco e del nero, che dovrebbe essere ovvio). Alcuni codici C #:

public static ReadOnlyCollection<Color> KellysMaxContrastSet
{
    get { return _kellysMaxContrastSet.AsReadOnly(); }
}

private static readonly List<Color> _kellysMaxContrastSet = new List<Color>
{
    UIntToColor(0xFFFFB300), //Vivid Yellow
    UIntToColor(0xFF803E75), //Strong Purple
    UIntToColor(0xFFFF6800), //Vivid Orange
    UIntToColor(0xFFA6BDD7), //Very Light Blue
    UIntToColor(0xFFC10020), //Vivid Red
    UIntToColor(0xFFCEA262), //Grayish Yellow
    UIntToColor(0xFF817066), //Medium Gray

    //The following will not be good for people with defective color vision
    UIntToColor(0xFF007D34), //Vivid Green
    UIntToColor(0xFFF6768E), //Strong Purplish Pink
    UIntToColor(0xFF00538A), //Strong Blue
    UIntToColor(0xFFFF7A5C), //Strong Yellowish Pink
    UIntToColor(0xFF53377A), //Strong Violet
    UIntToColor(0xFFFF8E00), //Vivid Orange Yellow
    UIntToColor(0xFFB32851), //Strong Purplish Red
    UIntToColor(0xFFF4C800), //Vivid Greenish Yellow
    UIntToColor(0xFF7F180D), //Strong Reddish Brown
    UIntToColor(0xFF93AA00), //Vivid Yellowish Green
    UIntToColor(0xFF593315), //Deep Yellowish Brown
    UIntToColor(0xFFF13A13), //Vivid Reddish Orange
    UIntToColor(0xFF232C16), //Dark Olive Green
};

public static ReadOnlyCollection<Color> BoyntonOptimized
{
    get { return _boyntonOptimized.AsReadOnly(); }
}

private static readonly List<Color> _boyntonOptimized = new List<Color>
{
    Color.FromArgb(0, 0, 255),      //Blue
    Color.FromArgb(255, 0, 0),      //Red
    Color.FromArgb(0, 255, 0),      //Green
    Color.FromArgb(255, 255, 0),    //Yellow
    Color.FromArgb(255, 0, 255),    //Magenta
    Color.FromArgb(255, 128, 128),  //Pink
    Color.FromArgb(128, 128, 128),  //Gray
    Color.FromArgb(128, 0, 0),      //Brown
    Color.FromArgb(255, 128, 0),    //Orange
};

static public Color UIntToColor(uint color)
{
    var a = (byte)(color >> 24);
    var r = (byte)(color >> 16);
    var g = (byte)(color >> 8);
    var b = (byte)(color >> 0);
    return Color.FromArgb(a, r, g, b);
}

Ed ecco i valori RGB nelle rappresentazioni esadecimali e 8 bit per canale:

kelly_colors_hex = [
    0xFFB300, # Vivid Yellow
    0x803E75, # Strong Purple
    0xFF6800, # Vivid Orange
    0xA6BDD7, # Very Light Blue
    0xC10020, # Vivid Red
    0xCEA262, # Grayish Yellow
    0x817066, # Medium Gray

    # The following don't work well for people with defective color vision
    0x007D34, # Vivid Green
    0xF6768E, # Strong Purplish Pink
    0x00538A, # Strong Blue
    0xFF7A5C, # Strong Yellowish Pink
    0x53377A, # Strong Violet
    0xFF8E00, # Vivid Orange Yellow
    0xB32851, # Strong Purplish Red
    0xF4C800, # Vivid Greenish Yellow
    0x7F180D, # Strong Reddish Brown
    0x93AA00, # Vivid Yellowish Green
    0x593315, # Deep Yellowish Brown
    0xF13A13, # Vivid Reddish Orange
    0x232C16, # Dark Olive Green
    ]

kelly_colors = dict(vivid_yellow=(255, 179, 0),
                    strong_purple=(128, 62, 117),
                    vivid_orange=(255, 104, 0),
                    very_light_blue=(166, 189, 215),
                    vivid_red=(193, 0, 32),
                    grayish_yellow=(206, 162, 98),
                    medium_gray=(129, 112, 102),

                    # these aren't good for people with defective color vision:
                    vivid_green=(0, 125, 52),
                    strong_purplish_pink=(246, 118, 142),
                    strong_blue=(0, 83, 138),
                    strong_yellowish_pink=(255, 122, 92),
                    strong_violet=(83, 55, 122),
                    vivid_orange_yellow=(255, 142, 0),
                    strong_purplish_red=(179, 40, 81),
                    vivid_greenish_yellow=(244, 200, 0),
                    strong_reddish_brown=(127, 24, 13),
                    vivid_yellowish_green=(147, 170, 0),
                    deep_yellowish_brown=(89, 51, 21),
                    vivid_reddish_orange=(241, 58, 19),
                    dark_olive_green=(35, 44, 22))

Per tutti voi sviluppatori Java, ecco i colori JavaFX:

// Don't forget to import javafx.scene.paint.Color;

private static final Color[] KELLY_COLORS = {
    Color.web("0xFFB300"),    // Vivid Yellow
    Color.web("0x803E75"),    // Strong Purple
    Color.web("0xFF6800"),    // Vivid Orange
    Color.web("0xA6BDD7"),    // Very Light Blue
    Color.web("0xC10020"),    // Vivid Red
    Color.web("0xCEA262"),    // Grayish Yellow
    Color.web("0x817066"),    // Medium Gray

    Color.web("0x007D34"),    // Vivid Green
    Color.web("0xF6768E"),    // Strong Purplish Pink
    Color.web("0x00538A"),    // Strong Blue
    Color.web("0xFF7A5C"),    // Strong Yellowish Pink
    Color.web("0x53377A"),    // Strong Violet
    Color.web("0xFF8E00"),    // Vivid Orange Yellow
    Color.web("0xB32851"),    // Strong Purplish Red
    Color.web("0xF4C800"),    // Vivid Greenish Yellow
    Color.web("0x7F180D"),    // Strong Reddish Brown
    Color.web("0x93AA00"),    // Vivid Yellowish Green
    Color.web("0x593315"),    // Deep Yellowish Brown
    Color.web("0xF13A13"),    // Vivid Reddish Orange
    Color.web("0x232C16"),    // Dark Olive Green
};

i seguenti sono i colori kelly non ordinati secondo l'ordine sopra.

colori kelly non ordinati

Di seguito sono riportati i colori Kelly ordinati in base alle tonalità (si noti che alcuni gialli non sono molto contrastanti)

 colori ordinati di kelly


+1 Grazie mille per questa grande risposta! A proposito il link colour-journal.org/2010/5/10 è morto, questo articolo è ancora disponibile su web.archive.org .
Alexey Popkov,


16
Ottima risposta, grazie! Mi sono preso la libertà di trasformare questi due set di colori in un comodo jsfiddle dove puoi vedere i colori in azione.
David Mills,

1
Ho appena notato che ci sono solo 20 e 9 colori in quegli elenchi, rispettivamente. Immagino sia perché il bianco e il nero sono stati omessi.
David Mills,

2
Il servizio web è ancora disponibile?
Janus Troelsen,

38

Come la risposta di Uri Cohen, ma è invece un generatore. Inizierà utilizzando i colori distanti. Deterministico.

Campione, lascia prima i colori: campione

#!/usr/bin/env python3.5
from typing import Iterable, Tuple
import colorsys
import itertools
from fractions import Fraction
from pprint import pprint

def zenos_dichotomy() -> Iterable[Fraction]:
    """
    http://en.wikipedia.org/wiki/1/2_%2B_1/4_%2B_1/8_%2B_1/16_%2B_%C2%B7_%C2%B7_%C2%B7
    """
    for k in itertools.count():
        yield Fraction(1,2**k)

def fracs() -> Iterable[Fraction]:
    """
    [Fraction(0, 1), Fraction(1, 2), Fraction(1, 4), Fraction(3, 4), Fraction(1, 8), Fraction(3, 8), Fraction(5, 8), Fraction(7, 8), Fraction(1, 16), Fraction(3, 16), ...]
    [0.0, 0.5, 0.25, 0.75, 0.125, 0.375, 0.625, 0.875, 0.0625, 0.1875, ...]
    """
    yield Fraction(0)
    for k in zenos_dichotomy():
        i = k.denominator # [1,2,4,8,16,...]
        for j in range(1,i,2):
            yield Fraction(j,i)

# can be used for the v in hsv to map linear values 0..1 to something that looks equidistant
# bias = lambda x: (math.sqrt(x/3)/Fraction(2,3)+Fraction(1,3))/Fraction(6,5)

HSVTuple = Tuple[Fraction, Fraction, Fraction]
RGBTuple = Tuple[float, float, float]

def hue_to_tones(h: Fraction) -> Iterable[HSVTuple]:
    for s in [Fraction(6,10)]: # optionally use range
        for v in [Fraction(8,10),Fraction(5,10)]: # could use range too
            yield (h, s, v) # use bias for v here if you use range

def hsv_to_rgb(x: HSVTuple) -> RGBTuple:
    return colorsys.hsv_to_rgb(*map(float, x))

flatten = itertools.chain.from_iterable

def hsvs() -> Iterable[HSVTuple]:
    return flatten(map(hue_to_tones, fracs()))

def rgbs() -> Iterable[RGBTuple]:
    return map(hsv_to_rgb, hsvs())

def rgb_to_css(x: RGBTuple) -> str:
    uint8tuple = map(lambda y: int(y*255), x)
    return "rgb({},{},{})".format(*uint8tuple)

def css_colors() -> Iterable[str]:
    return map(rgb_to_css, rgbs())

if __name__ == "__main__":
    # sample 100 colors in css format
    sample_colors = list(itertools.islice(css_colors(), 100))
    pprint(sample_colors)

+1 per il campione, molto bello, e mostra che anche lo schema è attraente. Le altre risposte qui sarebbero migliorate facendo lo stesso e quindi potrebbero essere prontamente confrontate.
Don Hatch,

3
La quantità di lambda è troppo dannatamente alta. La lambda è forte con questo.
Gyfis,

Sembra fantastico, ma rimane bloccato quando provo a eseguirlo su 2.7
Elad Weiss

33

Ecco un'idea. Immagina un cilindro HSV

Definire i limiti superiore e inferiore desiderati per Luminosità e Saturazione. Questo definisce un anello a sezione quadrata all'interno dello spazio.

Ora, disperde N punti casualmente in questo spazio.

Quindi applicare un algoritmo di repulsione iterativa su di essi, per un numero fisso di iterazioni o fino a quando i punti si stabilizzano.

Ora dovresti avere N punti che rappresentano N colori il più diversi possibile all'interno dello spazio colore che ti interessa.

Hugo


30

Per il bene delle generazioni future, aggiungo qui la risposta accettata in Python.

import numpy as np
import colorsys

def _get_colors(num_colors):
    colors=[]
    for i in np.arange(0., 360., 360. / num_colors):
        hue = i/360.
        lightness = (50 + np.random.rand() * 10)/100.
        saturation = (90 + np.random.rand() * 10)/100.
        colors.append(colorsys.hls_to_rgb(hue, lightness, saturation))
    return colors

18

Sembra che tutti abbiano perso l'esistenza dell'utilissimo spazio colore YUV progettato per rappresentare le differenze di colore percepite nel sistema visivo umano. Le distanze in YUV rappresentano differenze nella percezione umana. Avevo bisogno di questa funzionalità per MagicCube4D che implementa i cubi di Rubik in 4 dimensioni e un numero illimitato di altri rompicapo 4D con un numero arbitrario di facce.

La mia soluzione inizia selezionando i punti casuali in YUV e quindi rompendo iterativamente i due punti più vicini e convertendoli in RGB solo quando si restituisce il risultato. Il metodo è O (n ^ 3) ma non importa per i numeri piccoli o quelli che possono essere memorizzati nella cache. Può certamente essere reso più efficiente ma i risultati sembrano essere eccellenti.

La funzione consente la specifica opzionale delle soglie di luminosità in modo da non produrre colori in cui nessun componente è più luminoso o più scuro delle quantità indicate. Cioè potresti non voler valori vicini al bianco o al nero. Ciò è utile quando i colori risultanti verranno utilizzati come colori di base che vengono successivamente ombreggiati tramite illuminazione, stratificazione, trasparenza, ecc. E devono comunque apparire diversi dai loro colori di base.

import java.awt.Color;
import java.util.Random;

/**
 * Contains a method to generate N visually distinct colors and helper methods.
 * 
 * @author Melinda Green
 */
public class ColorUtils {
    private ColorUtils() {} // To disallow instantiation.
    private final static float
        U_OFF = .436f,
        V_OFF = .615f;
    private static final long RAND_SEED = 0;
    private static Random rand = new Random(RAND_SEED);    

    /*
     * Returns an array of ncolors RGB triplets such that each is as unique from the rest as possible
     * and each color has at least one component greater than minComponent and one less than maxComponent.
     * Use min == 1 and max == 0 to include the full RGB color range.
     * 
     * Warning: O N^2 algorithm blows up fast for more than 100 colors.
     */
    public static Color[] generateVisuallyDistinctColors(int ncolors, float minComponent, float maxComponent) {
        rand.setSeed(RAND_SEED); // So that we get consistent results for each combination of inputs

        float[][] yuv = new float[ncolors][3];

        // initialize array with random colors
        for(int got = 0; got < ncolors;) {
            System.arraycopy(randYUVinRGBRange(minComponent, maxComponent), 0, yuv[got++], 0, 3);
        }
        // continually break up the worst-fit color pair until we get tired of searching
        for(int c = 0; c < ncolors * 1000; c++) {
            float worst = 8888;
            int worstID = 0;
            for(int i = 1; i < yuv.length; i++) {
                for(int j = 0; j < i; j++) {
                    float dist = sqrdist(yuv[i], yuv[j]);
                    if(dist < worst) {
                        worst = dist;
                        worstID = i;
                    }
                }
            }
            float[] best = randYUVBetterThan(worst, minComponent, maxComponent, yuv);
            if(best == null)
                break;
            else
                yuv[worstID] = best;
        }

        Color[] rgbs = new Color[yuv.length];
        for(int i = 0; i < yuv.length; i++) {
            float[] rgb = new float[3];
            yuv2rgb(yuv[i][0], yuv[i][1], yuv[i][2], rgb);
            rgbs[i] = new Color(rgb[0], rgb[1], rgb[2]);
            //System.out.println(rgb[i][0] + "\t" + rgb[i][1] + "\t" + rgb[i][2]);
        }

        return rgbs;
    }

    public static void hsv2rgb(float h, float s, float v, float[] rgb) {
        // H is given on [0->6] or -1. S and V are given on [0->1]. 
        // RGB are each returned on [0->1]. 
        float m, n, f;
        int i;

        float[] hsv = new float[3];

        hsv[0] = h;
        hsv[1] = s;
        hsv[2] = v;
        System.out.println("H: " + h + " S: " + s + " V:" + v);
        if(hsv[0] == -1) {
            rgb[0] = rgb[1] = rgb[2] = hsv[2];
            return;
        }
        i = (int) (Math.floor(hsv[0]));
        f = hsv[0] - i;
        if(i % 2 == 0)
            f = 1 - f; // if i is even 
        m = hsv[2] * (1 - hsv[1]);
        n = hsv[2] * (1 - hsv[1] * f);
        switch(i) {
            case 6:
            case 0:
                rgb[0] = hsv[2];
                rgb[1] = n;
                rgb[2] = m;
                break;
            case 1:
                rgb[0] = n;
                rgb[1] = hsv[2];
                rgb[2] = m;
                break;
            case 2:
                rgb[0] = m;
                rgb[1] = hsv[2];
                rgb[2] = n;
                break;
            case 3:
                rgb[0] = m;
                rgb[1] = n;
                rgb[2] = hsv[2];
                break;
            case 4:
                rgb[0] = n;
                rgb[1] = m;
                rgb[2] = hsv[2];
                break;
            case 5:
                rgb[0] = hsv[2];
                rgb[1] = m;
                rgb[2] = n;
                break;
        }
    }


    // From http://en.wikipedia.org/wiki/YUV#Mathematical_derivations_and_formulas
    public static void yuv2rgb(float y, float u, float v, float[] rgb) {
        rgb[0] = 1 * y + 0 * u + 1.13983f * v;
        rgb[1] = 1 * y + -.39465f * u + -.58060f * v;
        rgb[2] = 1 * y + 2.03211f * u + 0 * v;
    }

    public static void rgb2yuv(float r, float g, float b, float[] yuv) {
        yuv[0] = .299f * r + .587f * g + .114f * b;
        yuv[1] = -.14713f * r + -.28886f * g + .436f * b;
        yuv[2] = .615f * r + -.51499f * g + -.10001f * b;
    }

    private static float[] randYUVinRGBRange(float minComponent, float maxComponent) {
        while(true) {
            float y = rand.nextFloat(); // * YFRAC + 1-YFRAC);
            float u = rand.nextFloat() * 2 * U_OFF - U_OFF;
            float v = rand.nextFloat() * 2 * V_OFF - V_OFF;
            float[] rgb = new float[3];
            yuv2rgb(y, u, v, rgb);
            float r = rgb[0], g = rgb[1], b = rgb[2];
            if(0 <= r && r <= 1 &&
                0 <= g && g <= 1 &&
                0 <= b && b <= 1 &&
                (r > minComponent || g > minComponent || b > minComponent) && // don't want all dark components
                (r < maxComponent || g < maxComponent || b < maxComponent)) // don't want all light components

                return new float[]{y, u, v};
        }
    }

    private static float sqrdist(float[] a, float[] b) {
        float sum = 0;
        for(int i = 0; i < a.length; i++) {
            float diff = a[i] - b[i];
            sum += diff * diff;
        }
        return sum;
    }

    private static double worstFit(Color[] colors) {
        float worst = 8888;
        float[] a = new float[3], b = new float[3];
        for(int i = 1; i < colors.length; i++) {
            colors[i].getColorComponents(a);
            for(int j = 0; j < i; j++) {
                colors[j].getColorComponents(b);
                float dist = sqrdist(a, b);
                if(dist < worst) {
                    worst = dist;
                }
            }
        }
        return Math.sqrt(worst);
    }

    private static float[] randYUVBetterThan(float bestDistSqrd, float minComponent, float maxComponent, float[][] in) {
        for(int attempt = 1; attempt < 100 * in.length; attempt++) {
            float[] candidate = randYUVinRGBRange(minComponent, maxComponent);
            boolean good = true;
            for(int i = 0; i < in.length; i++)
                if(sqrdist(candidate, in[i]) < bestDistSqrd)
                    good = false;
            if(good)
                return candidate;
        }
        return null; // after a bunch of passes, couldn't find a candidate that beat the best.
    }


    /**
     * Simple example program.
     */
    public static void main(String[] args) {
        final int ncolors = 10;
        Color[] colors = generateVisuallyDistinctColors(ncolors, .8f, .3f);
        for(int i = 0; i < colors.length; i++) {
            System.out.println(colors[i].toString());
        }
        System.out.println("Worst fit color = " + worstFit(colors));
    }

}

Esiste una versione C # di questo codice da qualche parte? Ho provato a convertirlo ed eseguirlo con gli stessi argomenti passati a generateVisuallyDistinctColors () e sembra funzionare molto lentamente. È previsto?
Chris Smith,

Ottieni gli stessi risultati? È abbastanza veloce per le mie esigenze ma, come ho detto, non ho tentato di ottimizzarlo, quindi se questo è il tuo unico problema, dovresti probabilmente prestare attenzione all'allocazione / deallocazione della memoria. Non so nulla sulla gestione della memoria in C #. Nel peggiore dei casi, potresti ridurre la costante di 1.000 loop esterni a qualcosa di più piccolo e la differenza di qualità potrebbe non essere nemmeno evidente.
Melinda Green,

1
La mia tavolozza deve contenere determinati colori ma volevo riempire gli extra. Mi piace il tuo metodo b / c Posso mettere prima i miei colori richiesti nel tuo array yuv e poi modificato "j = 0" per iniziare l'ottimizzazione dopo i miei colori richiesti. Vorrei che la rottura delle coppie peggiori fosse un po 'più intelligente, ma posso capire perché sia ​​difficile.
Ryan,

Penso che al tuo metodo yuv2rgb manchi il morsetto (0,255).
Ryan,

yuv2rgb è tutto float, non byte Ryan. Si prega di scrivere a melinda@superliminal.com per discutere.
Melinda Green,

8

Il modello di colore HSL può essere adatto per "ordinare" i colori, ma se stai cercando colori visivamente distinti hai invece bisogno del modello di colore Lab .

CIELAB è stato progettato per essere uniformemente percettivo rispetto alla visione dei colori umana, il che significa che la stessa quantità di variazione numerica in questi valori corrisponde a circa la stessa quantità di variazione visivamente percepita.

Una volta che lo sai, trovare il sottoinsieme ottimale di N colori da una vasta gamma di colori è ancora un problema (NP) difficile, un po 'simile al problema del commesso viaggiatore e tutte le soluzioni che usano algoritmi k-mean o qualcosa del genere non lo faranno davvero Aiuto.

Detto questo, se N non è troppo grande e se inizi con un set limitato di colori, troverai facilmente un ottimo sottoinsieme di colori distinti secondo una distanza Lab con una semplice funzione casuale.

Ho codificato uno strumento simile per il mio uso (puoi trovarlo qui: https://mokole.com/palette.html ), ecco cosa ho ottenuto per N = 7: inserisci qui la descrizione dell'immagine

È tutto javascript, quindi sentiti libero di dare un'occhiata alla fonte della pagina e adattarla alle tue esigenze.


1
Riguardo alla stessa quantità di variazione numerica [...] stessa quantità di variazione visivamente percepita «. Ho giocato con un selettore di colori CIE Lab e non ho potuto confermarlo affatto. Io denotare colori laboratorio utilizzando gli intervalli Lda 0 a 128 ed ae bda -128 a 128. ¶ ho iniziato con L= 0, a= -128, b= -128 che è un blu brillante. Poi sono aumentato atre volte. ❶ Il grande cambiamento (+128) a= 50 si traduce in un blu solo leggermente più scuro. ❷ (+85) a= 85 risultati ancora in blu. ❸ Tuttavia, il cambiamento relativamente piccolo (+43) a= 128 cambia completamente il colore in fucsia.
Socowi,

Questo è molto utile per me. Sarebbe l'ideale se i risultati fossero facili da copiare e incollare, però.
Mitchell van Zuylen, il

5

Ecco una soluzione per gestire il tuo problema "distinto", che è del tutto esagerato:

Crea una sfera unitaria e rilascia punti su di essa con cariche repellenti. Esegui un sistema di particelle fino a quando non si muovono più (o il delta è "abbastanza piccolo"). A questo punto, ciascuno dei punti è il più lontano possibile l'uno dall'altro. Converti (x, y, z) in rgb.

Lo menziono perché per alcune classi di problemi, questo tipo di soluzione può funzionare meglio della forza bruta.

Inizialmente ho visto questo approccio qui per tassellare una sfera.

Ancora una volta, le soluzioni più ovvie di attraversare lo spazio HSL o lo spazio RGB probabilmente funzioneranno bene.


1
È una buona idea, ma probabilmente ha senso usare un cubo, piuttosto che una sfera.
Rocketmagnet,

1
Questo è ciò che fa la mia soluzione basata su YUV ma per una scatola 3D (non cubo).
Melinda Green,

3

Vorrei provare a fissare la saturazione e la luminosità al massimo e concentrarmi solo sulla tonalità. A mio modo di vedere, H può andare da 0 a 255 e quindi va in giro. Ora, se volessi due colori contrastanti, prenderesti i lati opposti di questo anello, cioè 0 e 128. Se volessi 4 colori, ne prenderei alcuni separati da 1/4 della lunghezza 256 del cerchio, cioè 0, 64.128.192. E, naturalmente, come altri hanno suggerito quando hai bisogno di N colori, puoi semplicemente separarli di 256 / N.

Ciò che aggiungerei a questa idea è usare una rappresentazione invertita di un numero binario per formare questa sequenza. Guarda questo:

0 = 00000000  after reversal is 00000000 = 0
1 = 00000001  after reversal is 10000000 = 128
2 = 00000010  after reversal is 01000000 = 64
3 = 00000011  after reversal is 11000000 = 192

... in questo modo se hai bisogno di N colori diversi potresti semplicemente prendere i primi N numeri, invertirli e ottenere il maggior numero possibile di punti distanti (per N che è la potenza di due) preservando allo stesso tempo ogni prefisso del la sequenza differisce molto.

Questo era un obiettivo importante nel mio caso d'uso, poiché avevo un grafico in cui i colori erano ordinati per area coperta da questo colore. Volevo che le aree più grandi del grafico avessero un grande contrasto, e stavo bene con alcune piccole aree che avevano colori simili a quelli della top 10, come era ovvio per il lettore quale fosse semplicemente osservando l'area.


Questo è quello che ho fatto nella mia risposta, anche se un po 'più " matematico ". Vedere la funzione getfracs. Ma il vostro approccio è veloce e "semplice" in linguaggi di basso livello: bit di inversione in C .
Janus Troelsen,

Ho appena notato che anche Ridiculous Fish l'ha fatto
Janus Troelsen il

1

Se N è abbastanza grande, otterrai colori simili. Ce ne sono solo così tanti al mondo.

Perché non distribuirli uniformemente attraverso lo spettro, in questo modo:

IEnumerable<Color> CreateUniqueColors(int nColors)
{
    int subdivision = (int)Math.Floor(Math.Pow(nColors, 1/3d));
    for(int r = 0; r < 255; r += subdivision)
        for(int g = 0; g < 255; g += subdivision)
            for(int b = 0; b < 255; b += subdivision)
                yield return Color.FromArgb(r, g, b);
}

Se vuoi mescolare la sequenza in modo che colori simili non siano uno accanto all'altro, potresti forse mescolare l'elenco risultante.

Sto pensando a questo?


2
Sì, lo stai pensando male. La percezione del colore umano non è lineare, sfortunatamente. Potrebbe essere necessario tenere conto del turno Bezold – Brücke se si utilizzano intensità diverse. Ci sono anche buone informazioni qui: vis4.net/blog/posts/avoid-equidistant-hsv-colors
spex

1

Questo è banale in MATLAB (c'è un comando hsv):

cmap = hsv(number_of_colors)

1

Ho scritto un pacchetto per R chiamato qualpalr che è progettato specificamente per questo scopo. Ti consiglio di dare un'occhiata alla vignetta per scoprire come funziona, ma cercherò di riassumere i punti principali.

qualpalr prende una specifica di colori nello spazio colore HSL (che è stato descritto in precedenza in questo thread), lo proietta nello spazio colore DIN99d (che è percettivamente uniforme) e trova quello nche massimizza la distanza minima tra qualsiasi di essi.

# Create a palette of 4 colors of hues from 0 to 360, saturations between
# 0.1 and 0.5, and lightness from 0.6 to 0.85
pal <- qualpal(n = 4, list(h = c(0, 360), s = c(0.1, 0.5), l = c(0.6, 0.85)))

# Look at the colors in hex format
pal$hex
#> [1] "#6F75CE" "#CC6B76" "#CAC16A" "#76D0D0"

# Create a palette using one of the predefined color subspaces
pal2 <- qualpal(n = 4, colorspace = "pretty")

# Distance matrix of the DIN99d color differences
pal2$de_DIN99d
#>        #69A3CC #6ECC6E #CA6BC4
#> 6ECC6E      22                
#> CA6BC4      21      30        
#> CD976B      24      21      21

plot(pal2)

inserisci qui la descrizione dell'immagine


1

Penso che questo semplice algoritmo ricorsivo integri la risposta accettata, al fine di generare valori di tonalità distinti. L'ho realizzato per hsv, ma può essere utilizzato anche per altri spazi colore.

Genera tonalità in cicli, il più separati possibile l'uno dall'altro in ciascun ciclo.

/**
 * 1st cycle: 0, 120, 240
 * 2nd cycle (+60): 60, 180, 300
 * 3th cycle (+30): 30, 150, 270, 90, 210, 330
 * 4th cycle (+15): 15, 135, 255, 75, 195, 315, 45, 165, 285, 105, 225, 345
 */
public static float recursiveHue(int n) {
    // if 3: alternates red, green, blue variations
    float firstCycle = 3;

    // First cycle
    if (n < firstCycle) {
        return n * 360f / firstCycle;
    }
    // Each cycle has as much values as all previous cycles summed (powers of 2)
    else {
        // floor of log base 2
        int numCycles = (int)Math.floor(Math.log(n / firstCycle) / Math.log(2));
        // divDown stores the larger power of 2 that is still lower than n
        int divDown = (int)(firstCycle * Math.pow(2, numCycles));
        // same hues than previous cycle, but summing an offset (half than previous cycle)
        return recursiveHue(n % divDown) + 180f / divDown;
    }
}

Non sono stato in grado di trovare questo tipo di algoritmo qui. Spero sia di aiuto, è il mio primo post qui.


0

Questa funzione OpenCV utilizza il modello di colore HSV per generare ncolori distribuiti uniformemente intorno a 0 <= H <= 360º con S = 1.0 e V = 1.0 massimi. La funzione emette i colori BGR in bgr_mat:

void distributed_colors (int n, cv::Mat_<cv::Vec3f> & bgr_mat) {
  cv::Mat_<cv::Vec3f> hsv_mat(n,CV_32F,cv::Vec3f(0.0,1.0,1.0));
  double step = 360.0/n;
  double h= 0.0;
  cv::Vec3f value;
  for (int i=0;i<n;i++,h+=step) {
    value = hsv_mat.at<cv::Vec3f>(i);
    hsv_mat.at<cv::Vec3f>(i)[0] = h;
  }
  cv::cvtColor(hsv_mat, bgr_mat, CV_HSV2BGR);
  bgr_mat *= 255;
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.