วิธีสร้างสี 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
คำถามโปรแกรมเมอร์ที่เกี่ยวข้องอย่างยิ่งพร้อมคำตอบที่น่าสนใจ: "การสร้างโครงร่างสี - ทฤษฎีและอัลกอริธึม "
Alexey Popkov

2
การรับรู้สีของมนุษย์ไม่ได้เป็นเส้นตรง แต่น่าเสียดายที่ นอกจากนี้คุณยังอาจต้องใช้บัญชี Bezold – Brücke shift หากคุณใช้ความเข้มที่แตกต่างกัน นอกจากนี้ยังมีข้อมูลที่ดีที่นี่: 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 เป็นมากดีกว่า
Rocketmagnet

1
นี้เป็นธรรมด้วยเหตุผลที่กล่าวมาแล้ว แต่ยังเพราะคุณไม่ได้หยิบสม่ำเสมอ
sam hocevar

3
@strager ค่าคาดหวังของ randf ()
อะไร

242

คำถามนี้ปรากฏในการสนทนา SO ค่อนข้างน้อย:

มีการเสนอวิธีแก้ไขปัญหาที่แตกต่างกัน แต่ไม่มีวิธีใดเหมาะสมที่สุด โชคดีที่วิทยาศาสตร์เข้ามาช่วยเหลือ

โดยพลการ N

2 ล่าสุดจะเป็นอิสระผ่านห้องสมุดมหาวิทยาลัย / ผู้รับมอบฉันทะส่วนใหญ่

N มี จำกัด และค่อนข้างเล็ก

ในกรณีนี้ใครจะไปแก้ปัญหารายการ บทความที่น่าสนใจมาก ๆ ในหัวข้อนั้นมีให้เลือกอย่างอิสระ:

มีหลายรายการสีที่ควรพิจารณา:

  • รายการ 11 สีของ Boynton ที่แทบไม่เคยสับสน (มีอยู่ในกระดาษแผ่นแรกของหัวข้อก่อนหน้า)
  • ความคมชัดสูงสุด 22 สีของ Kelly (มีในกระดาษด้านบน)

ฉันวิ่งเข้าไปใน Palette นี้โดยนักเรียน MIT สุดท้ายลิงก์ต่อไปนี้อาจมีประโยชน์ในการแปลงระหว่างระบบ / พิกัดสีที่ต่างกัน (เช่นบางสีในบทความไม่ได้ระบุใน RGB เช่น):

สำหรับรายการของ Kelly และ Boynton ฉันได้ทำการแปลงเป็น 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 ขอบคุณมากสำหรับคำตอบที่ยอดเยี่ยมนี้! BTW ลิงค์colour-journal.org/2010/5/10ตายบทความนี้ยังคงมีอยู่ใน web.archive.org
Alexey Popkov


16
คำตอบที่ดีขอบคุณ! ฉันใช้เสรีภาพในการเปลี่ยนสีสองสีนี้ให้เป็นjsfiddle ที่สะดวกสบายซึ่งคุณสามารถมองเห็นสีของแอ็คชั่น
David Mills

1
เพิ่งสังเกตเห็นมีเพียง 20 และ 9 สีในรายการเหล่านั้นตามลำดับ ฉันเดาว่าเป็นเพราะสีขาวและดำถูกละไว้
David Mills

2
มีบริการเว็บหรือยัง
Janus Troelsen

38

ชอบคำตอบของ Uri Cohen แต่เป็นผู้สร้างแทน จะเริ่มจากการใช้สีห่างกัน กำหนด

ตัวอย่างสีที่เหลือก่อน: ตัวอย่าง

#!/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 สำหรับตัวอย่างดีมากและแสดงรูปแบบก็น่าสนใจเช่นกัน คำตอบอื่น ๆ ที่นี่จะได้รับการปรับปรุงโดยทำแบบเดียวกันและสามารถเปรียบเทียบได้อย่างง่ายดาย
Don Hatch

3
ปริมาณของลูกแกะนั้นสูงเกินไป แลมบ์ดาแข็งแรงด้วยอันนี้
Gyfis

ดูดี แต่ติดเมื่อฉันพยายามเรียกใช้บน 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 Twisty อื่น ๆ ที่มีจำนวนใบหน้าอย่างไม่ จำกัด

โซลูชันของฉันเริ่มต้นด้วยการเลือกจุดสุ่มใน YUV แล้วแบ่งจุดที่ใกล้เคียงที่สุดสองจุดซ้ำแล้วซ้ำอีกและแปลงเป็น RGB เมื่อส่งคืนผลลัพธ์เท่านั้น วิธีการคือ O (n ^ 3) แต่ไม่สำคัญว่าจะมีจำนวนน้อยหรือสามารถแคชได้ สามารถทำให้มีประสิทธิภาพมากขึ้นอย่างแน่นอน แต่ผลลัพธ์ที่ออกมานั้นยอดเยี่ยม

ฟังก์ชั่นนี้ช่วยในการกำหนดเกณฑ์ความสว่างเสริมที่เป็นตัวเลือกเพื่อไม่ให้เกิดสีที่ไม่มีองค์ประกอบใดที่สว่างกว่าหรือเข้มกว่าจำนวนที่กำหนด IE คุณอาจไม่ต้องการให้ค่าใกล้เคียงกับสีดำหรือสีขาว สิ่งนี้มีประโยชน์เมื่อสีที่ได้จะถูกนำมาใช้เป็นสีพื้นฐานซึ่งจะถูกแรเงาในภายหลังโดยใช้แสง, เลเยอร์, ​​ความโปร่งใส ฯลฯ และจะต้องปรากฏแตกต่างจากสีพื้นฐาน

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 () และดูเหมือนว่าจะทำงานช้ามาก คาดหวังหรือไม่
Chris Smith

คุณได้ผลลัพธ์เดียวกันหรือไม่ มันเร็วมากสำหรับความต้องการของฉัน แต่อย่างที่ฉันบอกว่าฉันไม่ได้พยายามปรับให้เหมาะสมดังนั้นถ้านั่นเป็นปัญหาของคุณคุณควรใส่ใจกับการจัดสรร / การจัดสรรคืนหน่วยความจำ ฉันไม่รู้เกี่ยวกับการจัดการหน่วยความจำ C # ที่แย่ที่สุดคุณสามารถลดค่าคงที่ลูปภายนอก 1,000 เป็นบางอย่างที่เล็กลงและความแตกต่างของคุณภาพอาจไม่เป็นที่สังเกต
Melinda Green

1
จานสีของฉันต้องมีบางสี แต่ฉันต้องการเติมความพิเศษ ฉันชอบวิธีการของคุณ b / c ฉันสามารถใส่สีที่ต้องการเป็นอันดับแรกในอาร์เรย์ yuv ของคุณแล้วปรับเปลี่ยน "j = 0" เพื่อเริ่มการปรับให้เหมาะสมหลังจากสีที่ฉันต้องการ ฉันหวังว่าการแยกตัวจากคู่ที่แย่ที่สุดนั้นฉลาดกว่านิดหน่อย แต่ฉันก็เข้าใจว่าทำไมมันถึงยาก
Ryan

ฉันคิดว่าวิธี yuv2rgb ของคุณไม่มีที่หนีบ (0,255)
Ryan

yuv2rgb เป็นโฟลตทั้งหมดไม่ใช่ไบต์ไรอัน กรุณาเขียนถึง melinda@superliminal.com เพื่อพูดคุย
Melinda Green

7

รุ่นสี HSL อาจจะเหมาะสำหรับ "การเรียงลำดับ" สี แต่ถ้าคุณกำลังมองหาสีที่แตกต่างกันทางสายตาที่คุณต้องแตกหักLabรุ่นสีแทน

CIELAB ได้รับการออกแบบให้มีรูปแบบการรับรู้ที่สอดคล้องกับการมองเห็นสีของมนุษย์ซึ่งหมายความว่าจำนวนการเปลี่ยนแปลงเชิงตัวเลขในค่าเหล่านี้สอดคล้องกับจำนวนการเปลี่ยนแปลงการรับรู้ทางสายตาที่เท่ากัน

เมื่อคุณรู้ว่าการหาเซตย่อยที่เหมาะสมที่สุดของสี N จากหลากหลายสีนั้นยังคงเป็นปัญหาที่ยาก (NP) ชนิดของปัญหาคล้ายกับปัญหาพนักงานขายเดินทางและวิธีแก้ปัญหาทั้งหมดที่ใช้อัลกอริทึม k-mean ช่วยด้วย.

ที่กล่าวว่าถ้า N ไม่ใหญ่เกินไปและถ้าคุณเริ่มด้วยชุดสีที่ จำกัด คุณจะสามารถหาเซตย่อยของสีที่แตกต่างได้อย่างดีตามระยะทางของ Lab ด้วยฟังก์ชันสุ่มแบบง่าย

ฉันเขียนรหัสเครื่องมือดังกล่าวสำหรับการใช้งานของฉันเอง (คุณสามารถค้นหาได้ที่นี่: https://mokole.com/palette.html ) นี่คือสิ่งที่ฉันได้รับสำหรับ N = 7: ป้อนคำอธิบายรูปภาพที่นี่

มันเป็นจาวาสคริปต์ทั้งหมดดังนั้นอย่าลังเลที่จะดูแหล่งที่มาของหน้าและปรับให้เข้ากับความต้องการของคุณเอง


1
เกี่ยวกับการเปลี่ยนแปลงเชิงตัวเลขในจำนวนเดียวกัน [... ] การเปลี่ยนแปลงการรับรู้ด้วยสายตาในจำนวนเดียวกัน « ฉันเล่นกับเครื่องมือเลือกสี CIE Lab และไม่สามารถยืนยันได้เลย ผมจะแสดงสีห้องปฏิบัติการโดยใช้ช่วงL0-128 และaและbจากการ -128 128 ¶ฉันเริ่มต้นด้วยL= 0 a= -128, b= -128 ซึ่งเป็นสีฟ้าสดใส จากนั้นฉันก็เพิ่มขึ้นaสามครั้ง ❶การเปลี่ยนแปลงครั้งใหญ่ (+128) a= 50 ผลลัพธ์เป็นสีน้ำเงินเข้มเพียงเล็กน้อยเท่านั้น ❷ (+85) a= 85 ผลลัพธ์ยังคงเป็นสีน้ำเงิน ❸อย่างไรก็ตามการเปลี่ยนแปลงที่ค่อนข้างเล็ก (+43) a= 128 จะเปลี่ยนสีเป็นสีแดงม่วงอย่างสมบูรณ์
Socowi

มันมีประโยชน์มากสำหรับฉัน มันจะเหมาะถ้าผลลัพธ์เป็นเรื่องง่ายที่จะคัดลอกวาง
Mitchell van Zuylen

5

นี่คือวิธีในการจัดการปัญหา "แตกต่าง" ของคุณซึ่ง overblown ทั้งหมด:

สร้างทรงกลมหน่วยและจุดวางที่มีค่าการต้านทาน เรียกใช้ระบบอนุภาคจนกว่าพวกเขาจะไม่ย้ายอีกต่อไป (หรือเดลต้าคือ "เล็กพอ") ณ จุดนี้จุดแต่ละจุดอยู่ห่างจากกันมากที่สุด แปลง (x, y, z) เป็น rgb

ฉันพูดถึงมันเพราะสำหรับปัญหาบางประเภทการแก้ปัญหาประเภทนี้สามารถทำงานได้ดีกว่ากำลังดุร้าย

ตอนแรกฉันเห็นวิธีการนี้ที่นี่เพื่อทำการทดลองทรงกลม

อีกครั้งโซลูชันที่ชัดเจนที่สุดของการข้ามพื้นที่ HSL หรือพื้นที่ RGB อาจทำงานได้ดี


1
นั่นเป็นความคิดที่ดี แต่มันก็สมเหตุสมผลที่จะใช้ลูกบาศก์แทนที่จะเป็นทรงกลม
Rocketmagnet

1
นั่นคือสิ่งที่โซลูชันที่ใช้ YUV ของฉันทำ แต่สำหรับกล่อง 3 มิติ (ไม่ใช่คิวบ์)
Melinda Green

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
Janus Troelsen


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
ใช่คุณไม่ได้คิดเรื่องนี้ การรับรู้สีของมนุษย์ไม่ได้เป็นเส้นตรง แต่น่าเสียดายที่ นอกจากนี้คุณยังอาจต้องใช้บัญชี Bezold – Brücke shift หากคุณใช้ความเข้มที่แตกต่างกัน นอกจากนี้ยังมีข้อมูลที่ดีที่นี่: vis4.net/blog/posts/avoid-equidistant-hsv-colors
spex


1

ฉันได้เขียนแพ็คเกจสำหรับ R เรียกว่าqualpalrที่ออกแบบมาโดยเฉพาะสำหรับจุดประสงค์นี้ ฉันแนะนำให้คุณดูบทความสั้น ๆเพื่อดูว่ามันทำงานอย่างไร แต่ฉันจะพยายามสรุปประเด็นหลัก

qualpalr ใช้ข้อมูลจำเพาะของสีในพื้นที่สี HSL (ซึ่งได้อธิบายไว้ก่อนหน้านี้ในหัวข้อนี้) นำไปใช้กับพื้นที่สี DIN99d (ซึ่งมีลักษณะเหมือนการรับรู้) และค้นหาnระยะห่างที่น้อยที่สุดระหว่าง oif

# 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;
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.