Как автоматически генерировать N «разных» цветов?


194

Я написал два метода ниже, чтобы автоматически выбрать N различных цветов. Он работает путем определения кусочно-линейной функции на кубе RGB. Преимущество этого состоит в том, что вы также можете получить прогрессивную шкалу, если вы этого хотите, но когда N становится большим, цвета могут начать выглядеть одинаково. Я также могу представить себе равномерное подразделение куба RGB на решетку и затем рисование точек. Кто-нибудь знает какие-либо другие методы? Я исключаю определение списка, а затем просто перебираю его. Я также должен сказать, что мне все равно, сталкиваются ли они или не выглядят красиво, они просто должны быть визуально различимы.

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
Сильно актуален вопрос программистов с интересными ответами: « Генерация цветовых схем - теория и алгоритмы ».
Алексей Попков

2
Человеческое восприятие цвета не линейно, к сожалению. Возможно, вам также придется учитывать сдвиг Безольда-Брюке, если вы используете различные интенсивности. Здесь также есть хорошая информация: vis4.net/blog/posts/avoid-equidistant-hsv-colors
spex

Ответы:


80

Вы можете использовать цветовую модель HSL для создания своих цветов.

Если все, что вам нужно, это разные оттенки (вероятнее всего) и небольшие колебания яркости или насыщенности, вы можете распределить оттенки следующим образом:

// 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
Эта техника умна. Могу поспорить, что это даст больше эстетических результатов, чем у меня.
Mqp

45
Это предполагает, что одинаково разнесенные значения оттенков одинаково воспринимаемы. Даже не принимая во внимание различные формы дальтонизма, это не так для большинства людей: разница между 120 ° (зеленый) и 135 ° (очень мятно-зеленый) незаметна, а разница между 30 ° (оранжевый) и 45 ° (персиковый) вполне очевидно. Вам нужен нелинейный интервал вдоль оттенка для достижения наилучших результатов.
Phrogz

18
@mquander - Это совсем не умно. Ничто не мешает этому алгоритму случайно выбрать два почти одинаковых цвета. Мой ответ лучше, а ответ ohadsc намного лучше.
Ракетный магнит

1
Это неправильно по причинам, уже упомянутым, но также и потому, что вы не выбираете равномерно .
Сам Hocevar

3
@strager какова ожидаемая ценность randf ()
Killrawr

242

Этот вопрос появляется в нескольких SO обсуждениях:

Предлагаются разные решения, но ни одно из них не является оптимальным. К счастью, наука приходит на помощь

Произвольный N

Последние 2 будут бесплатными через большинство университетских библиотек / прокси.

N конечно и относительно мало

В этом случае можно пойти на список решений. Очень интересная статья в теме находится в свободном доступе:

Есть несколько списков цветов для рассмотрения:

  • Список Бойнтона из 11 цветов, которые почти никогда не путаются (доступно в первой статье предыдущего раздела)
  • 22 цвета максимальной контрастности Келли (доступно в статье выше)

Я также столкнулся с этой Палитрой студентом MIT. Наконец, следующие ссылки могут быть полезны при преобразовании между различными системами цветов / координатами (например, некоторые цвета в статьях не указаны в RGB):

Что касается списка Келли и Бойнтона, я уже сделал преобразование в RGB (за исключением белого и черного, что должно быть очевидно). Некоторый код 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);
}

А вот значения RGB в шестнадцатеричном и 8-битном представлениях канала:

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

Для всех вас, разработчиков Java, вот цвета 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
};

Ниже приведены несортированные цвета Келли в порядке, указанном выше.

несортированные цвета келли

ниже приведены отсортированные цвета келли по оттенкам (обратите внимание, что некоторые желтые цвета не очень контрастны)

 сортированные цвета келли


+1 Большое спасибо за этот отличный ответ! Кстати, ссылка colour-journal.org/2010/5/10 не работает, эта статья по-прежнему доступна на web.archive.org .
Алексей Попков


16
Отличный ответ, спасибо! Я позволил себе
Дэвид Миллс

1
Просто заметил, что в этих списках только 20 и 9 цветов соответственно. Я предполагаю, что это потому, что белый и черный опущены.
Дэвид Миллс

2
Веб-сервис уже доступен?
Янус Троелсен

38

Как и ответ Ури Коэна, но вместо этого - генератор. Начнем с использования цветов далеко друг от друга. Детерминированный.

Образец, оставленные цвета сначала: образец

#!/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 за образец, очень красиво, и показывает, что схема тоже привлекательна. Другие ответы здесь будут улучшены, если сделать то же самое, и тогда их можно будет легко сравнить.
Дон Хэтч

3
Количество лямбд слишком чертовски велико. Лямбда сильна с этим.
Гифис

Выглядит отлично, но застревает, когда я пытаюсь запустить его на 2.7
Elad Weiss

33

Вот идея. Вообразите цилиндр HSV

Определите верхний и нижний пределы для яркости и насыщенности. Это определяет квадратное кольцо поперечного сечения в пространстве.

Теперь разбросим N точек случайно в этом пространстве.

Затем примените к ним алгоритм итеративного отталкивания либо для фиксированного числа итераций, либо до стабилизации точек.

Теперь у вас должно быть N точек, представляющих N цветов, которые настолько различны, насколько это возможно в цветовом пространстве, которое вас интересует.

Хьюго


30

Ради грядущих поколений добавляю здесь принятый ответ на 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

Кажется, что все упустили существование очень полезного цветового пространства YUV, которое было разработано, чтобы представлять воспринимаемые цветовые различия в зрительной системе человека. Расстояния в YUV представляют различия в человеческом восприятии. Мне нужна была эта функциональность для MagicCube4D, который реализует 4-мерные кубики Рубика и неограниченное количество других 4D извилистых головоломок с произвольным числом граней.

Мое решение начинается с выбора случайных точек в YUV, а затем итеративного разбиения ближайших двух точек и преобразования только в RGB при возврате результата. Метод O (n ^ 3), но это не имеет значения для небольших чисел или тех, которые могут быть кэшированы. Конечно, это можно сделать более эффективным, но результаты кажутся отличными.

Функция позволяет опционально задавать пороговые значения яркости, чтобы не создавать цвета, в которых ни один компонент не является более ярким или темным, чем заданные значения. То есть вы можете не захотеть значения, близкие к черному или белому. Это полезно, когда результирующие цвета будут использоваться в качестве базовых цветов, которые затем затеняются с помощью освещения, наслоения, прозрачности и т. Д. И все же должны отличаться от своих базовых цветов.

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

}

Есть ли где-нибудь версия этого кода на C #? Я попытался преобразовать его и запустить с теми же аргументами, которые вы передали функции generateVisuallyDistinctColors (), и, похоже, он работает очень медленно. Это ожидается?
Крис Смит

Вы получаете те же результаты? Это достаточно быстро для моих нужд, но, как я уже сказал, я не пытался оптимизировать его, поэтому, если это ваша единственная проблема, вам, вероятно, следует обратить внимание на распределение / освобождение памяти. Я ничего не знаю об управлении памятью в C #. В худшем случае вы можете уменьшить постоянную 1000 внешних контуров до чего-то меньшего, и разница в качестве может даже не быть заметной.
Мелинда Грин

1
Моя палитра должна содержать определенные цвета, но я хотел заполнить дополнения. Мне нравится ваш метод b / c. Я могу сначала поместить нужные цвета в ваш массив yuv, а затем изменить "j = 0", чтобы начать оптимизацию после требуемых цветов. Хотелось бы, чтобы расставание наихудших пар было немного умнее, но я понимаю, почему это сложно.
Райан

Я думаю, что ваш метод yuv2rgb отсутствует зажим (0,255).
Райан

yuv2rgb это все плавающие, а не байты Райана. Пожалуйста, пишите по адресу melinda@superliminal.com для обсуждения.
Мелинда Грин

8

Цветовая модель HSL может хорошо подходить для «сортировки» цветов, но если вы ищете визуально отличающиеся цвета, вам определенно нужна цветовая модель Lab .

CIELAB был спроектирован так, чтобы быть однородным по восприятию в отношении цветового зрения человека, что означает, что одинаковое количество численных изменений в этих значениях соответствует примерно одинаковому количеству визуально воспринимаемых изменений.

Как только вы узнаете, что поиск оптимального подмножества из N цветов из широкого диапазона цветов все еще остается трудной задачей (NP), она похожа на проблему коммивояжера и все решения, использующие алгоритмы k-среднего или что-то еще Помогите.

Тем не менее, если N не слишком велико, и если вы начнете с ограниченного набора цветов, вы легко найдете очень хорошее подмножество различий цветов в соответствии с расстоянием Lab с простой случайной функцией.

Я написал такой инструмент для собственного использования (вы можете найти его здесь: https://mokole.com/palette.html ), вот что я получил за N = 7: введите описание изображения здесь

Это все javascript, так что не стесняйтесь взглянуть на источник страницы и адаптировать его под свои нужды.


1
Относительно «того же количества числовых изменений [...] того же количества визуально воспринимаемых изменений» . Я играл с палитрой цветов CIE Lab и никак не мог это подтвердить. Я буду обозначать лаборатории цвета , используя диапазоны Lот 0 до 128 , а aи bот -128 до 128. ¶ Я начал с L= 0, a= -128, b= -128 , который является ярко - синим. Затем я увеличился в aтри раза. Change Большое изменение (+128) a= 50 приводит к получению лишь немного более темного синего цвета. ❷ (+85) a= 85 результаты по-прежнему синим цветом. ❸ Однако, относительно небольшое изменение (+43) a= 128 полностью меняет цвет на фуксию.
Socowi

Это очень полезно для меня. Было бы идеально, если бы результаты были легко скопировать-вставить, хотя.
Митчелл ван Зуйлен

5

Вот решение для решения вашей «отдельной» проблемы, которая полностью преувеличена:

Создайте единичную сферу и отбрасывайте на нее точки с отталкивающими зарядами. Запускайте систему частиц до тех пор, пока они не перестанут двигаться (или дельта не станет «достаточно маленькой»). В этот момент каждая из точек находится как можно дальше друг от друга. Преобразовать (x, y, z) в rgb.

Я упоминаю об этом, потому что для определенных классов проблем этот тип решения может работать лучше, чем грубая сила.

Я изначально видел этот подход здесь для тесселяции сферы.

Опять же, наиболее очевидные решения обхода пространства HSL или RGB, вероятно, будут работать просто отлично.


1
Это хорошая идея, но, вероятно, имеет смысл использовать куб, а не сферу.
Ракетный магнит

1
Это то, что делает мое решение на основе YUV, но для 3D-бокса (не куба).
Мелинда Грин

3

Я бы попытался установить насыщенность и яркость на максимум и сосредоточиться только на оттенке. Как я вижу, H может перейти от 0 до 255, а затем оборачивается. Теперь, если бы вы хотели два контрастных цвета, вы бы выбрали противоположные стороны этого кольца, то есть 0 и 128. Если бы вы хотели 4 цвета, вы бы взяли некоторые, разделенные на 1/4 длины круга 256, то есть 0, 64,128,192. И, конечно, как другие предлагали, когда вам нужно N цветов, вы можете просто разделить их на 256 / N.

Я хотел бы добавить к этой идее использование обратного представления двоичного числа для формирования этой последовательности. Посмотри на это:

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

... таким образом, если вам нужно N разных цветов, вы можете просто взять первые N чисел, поменять их местами, и вы получите как можно больше удаленных точек (для N степень двойки), сохраняя при этом каждый префикс Последовательность сильно отличается.

Это было важной целью в моем случае использования, так как у меня была диаграмма, где цвета были отсортированы по области, покрытой этим цветом. Я хотел, чтобы самые большие области диаграммы имели большой контраст, и я был в порядке с некоторыми небольшими областями, чтобы иметь цвета, похожие на цвета из топ-10, так как для читателя было очевидно, какой из них какой, просто наблюдая за областью.


Это то, что я сделал в своем ответе, хотя и немного более « математически ». Смотрите функцию getfracs. Но ваш подход быстро и «просто» на языках низкого уровня: бит реверсивный в C .
Янус Троелсен


1

Если N достаточно велик, вы получите несколько похожих цветов. В мире их так много.

Почему бы просто не распределить их равномерно по спектру, вот так:

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

Если вы хотите смешать последовательность так, чтобы похожие цвета не были рядом друг с другом, вы можете перемешать результирующий список.

Я думаю об этом?


2
Да, ты не думаешь об этом. Человеческое восприятие цвета не линейно, к сожалению. Возможно, вам также придется учитывать сдвиг Безольда-Брюке, если вы используете различные интенсивности. Здесь также есть хорошая информация: vis4.net/blog/posts/avoid-equidistant-hsv-colors
spex

1

Это тривиально в MATLAB (есть команда hsv):

cmap = hsv(number_of_colors)

1

Я написал пакет для R под названием qualpalr, который разработан специально для этой цели. Я рекомендую вам взглянуть на виньетку, чтобы узнать, как она работает, но я постараюсь обобщить основные моменты.

Qualpalr берет спецификацию цветов в цветовом пространстве HSL (которое было описано ранее в этой теме), проецирует его в цветовое пространство DIN99d (которое является перцепционно однородным) и находит то, nкоторое максимизирует минимальное расстояние между ними.

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

введите описание изображения здесь


1

Я думаю, что этот простой рекурсивный алгоритм дополняет принятый ответ, чтобы генерировать различные значения оттенка. Я сделал это для HSV, но можно использовать и для других цветовых пространств.

Он генерирует оттенки в циклах, максимально отдельных друг от друга в каждом цикле.

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

Я не смог найти такой алгоритм здесь. Надеюсь, это поможет, это мой первый пост здесь.


0

Эта функция OpenCV использует цветовую модель HSV для генерации nравномерно распределенных цветов вокруг 0 ​​<= H <= 360º с максимальным S = 1,0 и V = 1,0. Функция выводит цвета BGR в 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;
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.