Comment générer automatiquement N couleurs «distinctes»?


194

J'ai écrit les deux méthodes ci-dessous pour sélectionner automatiquement N couleurs distinctes. Il fonctionne en définissant une fonction linéaire par morceaux sur le cube RVB. L'avantage est que vous pouvez également obtenir une échelle progressive si c'est ce que vous voulez, mais lorsque N devient grand, les couleurs peuvent commencer à ressembler. Je peux également imaginer subdiviser uniformément le cube RVB en un treillis puis dessiner des points. Quelqu'un connaît-il d'autres méthodes? J'écarte la possibilité de définir une liste, puis de la parcourir. Je dois également dire que je ne m'inquiète généralement pas s'ils s'affrontent ou ne sont pas beaux, ils doivent simplement être visuellement distincts.

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

4
Question fortement pertinente des programmeurs avec des réponses intéressantes: " Génération de schémas de couleurs - théorie et algorithmes ."
Alexey Popkov

2
La perception des couleurs humaines n'est malheureusement pas linéaire. Vous devrez peut-être également tenir compte du décalage de Bezold – Brücke si vous utilisez des intensités variables. Il y a aussi de bonnes informations ici: vis4.net/blog/posts/avoid-equidistant-hsv-colors
spex

Réponses:


80

Vous pouvez utiliser le modèle de couleur HSL pour créer vos couleurs.

Si tout ce que vous voulez, ce sont des teintes différentes (probablement) et de légères variations sur la luminosité ou la saturation, vous pouvez répartir les teintes comme suit:

// 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
Cette technique est intelligente. Je parie qu'il obtiendra plus de résultats esthétiques que le mien.
mqp

45
Cela suppose que les valeurs de teinte également espacées sont également différentes sur le plan perceptuel. Même en excluant diverses formes de daltonisme, cela n'est pas vrai pour la plupart des gens: la différence entre 120 ° (vert) et 135 ° (très légèrement vert menthe) est imperceptible, tandis que la différence entre 30 ° (orange) et 45 ° (pêche) est assez évident. Vous avez besoin d'un espacement non linéaire le long de la teinte pour de meilleurs résultats.
Phrogz

18
@mquander - Ce n'est pas intelligent du tout. Rien n'empêche cet algorithme de choisir accidentellement deux couleurs presque identiques. Ma réponse est meilleure et celle d'Ohadsc est bien meilleure.
Rocketmagnet

1
C'est faux pour les raisons déjà mentionnées, mais aussi parce que vous ne choisissez pas uniformément .
sam hocevar

3
@strager quelle est la valeur attendue de randf ()
Killrawr

242

Cette question apparaît dans plusieurs discussions SO:

Différentes solutions sont proposées, mais aucune n'est optimale. Heureusement, la science vient à la rescousse

N arbitraire

Les 2 derniers seront gratuits via la plupart des bibliothèques / procurations universitaires.

N est fini et relativement petit

Dans ce cas, on pourrait opter pour une solution de liste. Un article très intéressant sur le sujet est disponible gratuitement:

Il y a plusieurs listes de couleurs à considérer:

  • La liste de Boynton de 11 couleurs qui ne sont presque jamais confondues (disponible dans le premier article de la section précédente)
  • Les 22 couleurs de contraste maximal de Kelly (disponibles dans l'article ci-dessus)

J'ai également rencontré cette palette par un étudiant du MIT. Enfin, les liens suivants peuvent être utiles dans la conversion entre différents systèmes de couleurs / coordonnées (certaines couleurs dans les articles ne sont pas spécifiées en RVB, par exemple):

Pour la liste de Kelly et Boynton, j'ai déjà fait la conversion en RVB (à l'exception du blanc et du noir, ce qui devrait être évident). Du code 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);
}

Et voici les valeurs RVB en représentations hexadécimales et 8 bits par canal:

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

Pour tous les développeurs Java, voici les couleurs 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
};

ce qui suit est les couleurs kelly non triées selon l'ordre ci-dessus.

couleurs kelly non triées

ce qui suit est les couleurs kelly triées selon les teintes (notez que certains jaunes ne sont pas très contrastés)

 couleurs kelly triées


+1 Merci beaucoup pour cette excellente réponse! BTW le lien colour-journal.org/2010/5/10 est mort, cet article est toujours disponible sur web.archive.org .
Alexey Popkov


16
Excellente réponse, merci! J'ai pris la liberté de transformer ces deux ensembles de couleurs en un jsfiddle pratique où vous pouvez voir les couleurs en action.
David Mills,

1
Je viens de remarquer qu'il n'y a que 20 et 9 couleurs dans ces listes, respectivement. Je suppose que c'est parce que le blanc et le noir sont omis.
David Mills

2
Le service Web est-il encore disponible?
Janus Troelsen

38

Comme la réponse d'Uri Cohen, mais c'est un générateur à la place. Commencera par utiliser des couleurs très éloignées. Déterministe.

Échantillon, couleurs à gauche en premier: échantillon

#!/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 pour l'échantillon, très agréable, et montre que le schéma est aussi attrayant. Les autres réponses ici seraient améliorées en faisant de même et pourraient alors être facilement comparées.
Don Hatch

3
La quantité de lambdas est trop élevée. Le lambda est fort avec celui-ci.
Gyfis

Ça a l'air bien, mais je reste bloqué quand j'essaye de le lancer sur 2.7
Elad Weiss

33

Voici une idée. Imaginez un cylindre HSV

Définissez les limites supérieure et inférieure souhaitées pour la luminosité et la saturation. Ceci définit un anneau de section carrée dans l'espace.

Maintenant, dispersez N points au hasard dans cet espace.

Appliquez ensuite un algorithme de répulsion itérative sur eux, soit pour un nombre fixe d'itérations, soit jusqu'à ce que les points se stabilisent.

Maintenant, vous devriez avoir N points représentant N couleurs qui sont à peu près aussi différentes que possible dans l'espace colorimétrique qui vous intéresse.

Hugo


30

Pour le bien des générations à venir, j'ajoute ici la réponse acceptée en 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

Tout le monde semble avoir manqué l'existence de l'espace colorimétrique YUV très utile qui a été conçu pour représenter les différences de couleurs perçues dans le système visuel humain. Les distances en YUV représentent des différences de perception humaine. J'avais besoin de cette fonctionnalité pour MagicCube4D qui implémente des cubes Rubik à 4 dimensions et un nombre illimité d'autres puzzles sinueux 4D ayant un nombre arbitraire de visages.

Ma solution commence par sélectionner des points aléatoires dans YUV puis en décomposant de manière itérative les deux points les plus proches, et en ne convertissant en RVB qu'au retour du résultat. La méthode est O (n ^ 3) mais cela n'a pas d'importance pour les petits nombres ou ceux qui peuvent être mis en cache. Il peut certes être rendu plus efficace, mais les résultats semblent excellents.

La fonction permet de spécifier en option des seuils de luminosité afin de ne pas produire de couleurs dans lesquelles aucun composant n'est plus clair ou plus sombre que les quantités données. IE vous pouvez ne pas vouloir des valeurs proches du noir ou du blanc. Ceci est utile lorsque les couleurs résultantes seront utilisées comme couleurs de base qui seront ensuite ombrées via l'éclairage, la superposition, la transparence, etc. et doivent toujours apparaître différentes de leurs couleurs de 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));
    }

}

Existe-t-il une version C # de ce code quelque part? J'ai essayé de le convertir et de l'exécuter avec les mêmes arguments que vous avez passés pour générerVisuallyDistinctColors () et cela semble fonctionner très lentement. Est-ce attendu?
Chris Smith

Obtenez-vous les mêmes résultats? C'est très rapide pour mes besoins mais comme je l'ai dit, je n'ai pas essayé de l'optimiser, donc si c'est votre seul problème, vous devriez probablement faire attention à l'allocation / désallocation de mémoire. Je ne connais rien à la gestion de la mémoire C #. Au pire, vous pouvez réduire la constante de boucle externe de 1 000 à quelque chose de plus petit et la différence de qualité peut même ne pas être perceptible.
Melinda Green

1
Ma palette doit contenir certaines couleurs mais je voulais compléter les extras. J'aime votre méthode b / c, je peux mettre mes couleurs requises en premier dans votre tableau yuv puis modifier "j = 0" pour commencer l'optimisation après mes couleurs requises. Je souhaite que la rupture des pires paires soit un peu plus intelligente, mais je peux comprendre pourquoi c'est difficile.
Ryan

Je pense que votre méthode yuv2rgb manque la pince (0,255).
Ryan

yuv2rgb est tous des flotteurs, pas des octets Ryan. Veuillez écrire à melinda@superliminal.com pour en discuter.
Melinda Green

6

Le modèle de couleurs HSL peut être bien adapté au "tri" des couleurs, mais si vous recherchez des couleurs visuellement distinctes, vous avez définitivement besoin d' un modèle de couleurs Lab à la place.

CIELAB a été conçu pour être perceptuellement uniforme par rapport à la vision des couleurs humaines, ce qui signifie que la même quantité de changement numérique dans ces valeurs correspond à environ la même quantité de changement perçu visuellement.

Une fois que vous savez cela, trouver le sous-ensemble optimal de N couleurs à partir d'une large gamme de couleurs reste un problème difficile (NP), un peu similaire au problème du voyageur de commerce et toutes les solutions utilisant des algorithmes k-mean ou quelque chose ne le seront pas vraiment Aidez-moi.

Cela dit, si N n'est pas trop grand et si vous commencez avec un ensemble limité de couleurs, vous trouverez facilement un très bon sous-ensemble de couleurs distinctes en fonction d'une distance Lab avec une simple fonction aléatoire.

J'ai codé un tel outil pour mon usage personnel (vous pouvez le trouver ici: https://mokole.com/palette.html ), voici ce que j'ai obtenu pour N = 7: entrez la description de l'image ici

Tout est javascript alors n'hésitez pas à jeter un œil à la source de la page et à l'adapter à vos besoins.


1
Concernant « même quantité de changement numérique [...] même quantité de changement perçu visuellement ». J'ai joué avec un sélecteur de couleurs CIE Lab et je n'ai rien pu confirmer du tout. Je noterai les couleurs laboratoire en utilisant les plages Lde 0 à 128 et aet bde -128 à 128. ¶ J'ai commencé avec L= 0, a= -128, b= -128 qui est un bleu lumineux. Puis j'ai augmenté atrois fois. ❶ Le grand changement (+128) a= 50 donne un bleu légèrement plus foncé. ❷ (+85) a= 85 résultats toujours en bleu. ❸ Cependant, le changement relativement faible (+43) a= 128 change complètement la couleur en fuchsia.
Socowi

C'est très utile pour moi. Ce serait idéal si les résultats étaient faciles à copier-coller.
Mitchell van Zuylen

5

Voici une solution pour gérer votre problème "distinct", qui est entièrement exagéré:

Créez une sphère unitaire et déposez-y des points avec des charges répulsives. Exécutez un système de particules jusqu'à ce qu'elles ne bougent plus (ou que le delta soit "assez petit"). À ce stade, chacun des points est aussi éloigné l'un de l'autre que possible. Convertissez (x, y, z) en rgb.

Je le mentionne car pour certaines classes de problèmes, ce type de solution peut mieux fonctionner que la force brute.

J'ai vu cette approche à l'origine ici pour tesseler une sphère.

Encore une fois, les solutions les plus évidentes de traverser l'espace HSL ou l'espace RVB fonctionneront probablement très bien.


1
C'est une bonne idée, mais il est probablement judicieux d'utiliser un cube plutôt qu'une sphère.
Rocketmagnet

1
C'est ce que fait ma solution basée sur YUV, mais pour une boîte 3D (pas un cube).
Melinda Green

3

J'essaierais de fixer la saturation et la lumination au maximum et de me concentrer uniquement sur la teinte. Comme je le vois, H peut passer de 0 à 255, puis s'enroule. Maintenant, si vous vouliez deux couleurs contrastées, vous prendriez les côtés opposés de cet anneau, c'est-à-dire 0 et 128. Si vous vouliez 4 couleurs, vous en prendriez séparées par 1/4 de la longueur 256 du cercle, soit 0, 64, 128, 192. Et bien sûr, comme d'autres l'ont suggéré lorsque vous avez besoin de N couleurs, vous pouvez simplement les séparer par 256 / N.

Ce que j'ajouterais à cette idée, c'est d'utiliser une représentation inversée d'un nombre binaire pour former cette séquence. Regarde ça:

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

... de cette façon, si vous avez besoin de N couleurs différentes, vous pouvez simplement prendre les premiers N nombres, les inverser, et vous obtenez autant de points éloignés que possible (pour N étant une puissance de deux) tout en préservant en même temps que chaque préfixe du la séquence diffère beaucoup.

C'était un objectif important dans mon cas d'utilisation, car j'avais un tableau où les couleurs étaient triées par zone couverte par cette couleur. Je voulais que les plus grandes zones du graphique aient un contraste important, et j'étais d'accord avec certaines petites zones pour avoir des couleurs similaires à celles du top 10, car il était évident pour le lecteur lequel est lequel en observant simplement la zone.


C'est ce que j'ai fait dans ma réponse, bien qu'un peu plus " mathématique ". Voir la fonction getfracs. Mais votre approche est rapide et « simple » dans les langages de bas niveau: peu de recul en C .
Janus Troelsen

Je viens de remarquer que Ridiculous Fish l'a fait aussi
Janus Troelsen

1

Si N est assez grand, vous obtiendrez des couleurs similaires. Il n'y en a que beaucoup dans le monde.

Pourquoi ne pas simplement les répartir uniformément à travers le spectre, comme ceci:

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

Si vous souhaitez mélanger la séquence afin que des couleurs similaires ne soient pas côte à côte, vous pouvez peut-être mélanger la liste résultante.

Suis-je en train de penser cela?


2
Oui, vous sous-estimez cela. La perception des couleurs humaines n'est malheureusement pas linéaire. Vous devrez peut-être également tenir compte du décalage de Bezold – Brücke si vous utilisez des intensités variables. Il y a aussi de bonnes informations ici: vis4.net/blog/posts/avoid-equidistant-hsv-colors
spex

1

C'est trivial dans MATLAB (il y a une commande hsv):

cmap = hsv(number_of_colors)

1

J'ai écrit un package pour R appelé qualpalr qui est spécialement conçu à cet effet. Je vous recommande de regarder la vignette pour savoir comment cela fonctionne, mais je vais essayer de résumer les principaux points.

qualpalr prend une spécification des couleurs dans l' espace colorimétrique HSL (qui a été décrit précédemment dans ce fil), la projette dans l'espace colorimétrique DIN99d (qui est perceptuellement uniforme) et trouve celle nqui maximise la distance minimale entre n'importe quelle d'entre elles.

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

entrez la description de l'image ici


1

Je pense que cet algorithme récursif simple complète la réponse acceptée, afin de générer des valeurs de teinte distinctes. Je l'ai fait pour hsv, mais peut également être utilisé pour d'autres espaces colorimétriques.

Il génère des teintes dans des cycles, aussi séparés que possible les uns des autres dans chaque cycle.

/**
 * 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;
    }
}

Je n'ai pas pu trouver ce type d'algorithme ici. J'espère que ça aide, c'est mon premier post ici.


0

Cette fonction OpenCV utilise le modèle de couleurs HSV pour générer ndes couleurs uniformément réparties autour de 0 <= H <= 360º avec un maximum S = 1.0 et V = 1.0. La fonction sort les couleurs BGR en 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;
}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.