@maximsemin23

Как найти максимально похожий цвет?

Признаюсь, в алгоритмах я не силен, поэтому задаю этот вопрос.
У меня есть список цветов, выглядит он примерно так:
/**
 * All colors and their names
 */
@Suppress("SpellCheckingInspection")
val names = mapOf(
    "000000" to "Black",
    "000080" to "Navy Blue",
    "0000C8" to "Dark Blue",
    "0000FF" to "Blue",
    "000741" to "Stratos",
    "001B1C" to "Swamp",
    "002387" to "Resolution Blue",
    "002900" to "Deep Fir",
    "002E20" to "Burnham",
    "002FA7" to "International Klein Blue",

То есть название есть не для каждого цвета, а только для некоторых. Дальше у меня есть такой метод:
object ColorHandler {

    /**
     * Color sample: 556A74
     */
    fun getColorName(color: String): String {
        if (color in names.keys)
            return names[color]!!

        val red = color.slice(R)
        val green = color.slice(G)
        val blue = color.slice(B)

       // what's here???
    }
}

val R = 0..1
val G = 2..3
val B = 4..5

Как реализовать поиск максимально похожего цвета?
Например, при 0000DD должен быть 0000C8
  • Вопрос задан
  • 664 просмотра
Решения вопроса 2
sergiks
@sergiks Куратор тега Алгоритмы
♬♬
Самое простое — считать сумму квадратов расстояний по каждому из компонентов: R, G и B.

Для пары 0000DD, 0000C8 «расстояние» будет такое:
(0x00 - 0x00)^2 + (0x00 - 0x00)^2 + (0xDD - 0xC8)^2 = 441
Так посчитать до каждого из определённых цветов, найти минимум.

Можно сравнивать в других цветовых моделях. Например, в HSV (оттенок, насыщенность, яркость) — если посчитаете, что насыщенный красный и тусклый красный точно того же оттенка «ближе», чем той же яркости оранжевый. Речь о возможно разных «весах» каналов в деле определения близости двух цветов.
Ответ написан
Комментировать
@maximsemin23 Автор вопроса
Как я в итоге сделал (фактически, решение описано Сергей Соколов ):
object ColorHandler {

    /**
     * Color sample: 556A74
     */
    fun getColorName(color: String): String {
        if (color in names.keys)
            return names[color]!!

        val r = color.slice(0..1).toInt(16)
        val g = color.slice(2..3).toInt(16)
        val b = color.slice(4..5).toInt(16)

        var currentMin: Pair<Int, String>? = null
        var range: Int

        names.forEach { (colorValue, name) ->
            range = (
                (r - colorValue.slice(0..1).toInt(16)).pow() +
                (g - colorValue.slice(2..3).toInt(16)).pow() +
                (b - colorValue.slice(4..5).toInt(16)).pow()
            )

            when {
                currentMin == null -> {
                    currentMin = range to name
                }
                
                currentMin!!.first > range -> {
                    currentMin = range to name
                }
                
                currentMin!!.first < range -> {
                    return@forEach
                }
            }
        }

        return currentMin!!.second
    }
}
Ответ написан
Комментировать
Пригласить эксперта
Ответы на вопрос 5
GavriKos
@GavriKos
Перевести в hsl и сравнивать h
Ответ написан
wataru
@wataru Куратор тега Алгоритмы
Разработчик на С++, экс-олимпиадник.
Если скорость не особо важна, то просто пройдитесь по всем именным цветам (лучше, чтобы это был какой-то списко или массив, а не мап) и считайте какую-то метрику (например, сумма квадратов разности по каждому каналу отдельно). Из полученных чисел ищите минимум и выдавайте тот цвет, что ее дал.

Если надо работать очень быстро, то надо представить ваши именные цвета как точки в трехмерном пространстве, построить kd-дерево или r-дерево и уже в нем искать ближайшую к запрошенной точку.
Ответ написан
vabka
@vabka
Токсичный шарпист
Раз уж вопрос в теге "алгоритмы", то опишу такой алгоритм:
1. Цвета в RGB кодируются тремя числами, каждое из которых обозначает интенсивность какого-то компонента.
Красного, Зелёного, и Синего соответственно.
2. В показанном шестнадцатеричном формате для этого используется 6 шестнадцатеричных цифр, где для каждого компонента в том же порядке используется по две цифры. При переводе в десятичную систему - это будет значение от 0 до 255.
3. Самый простой алгоритм - представить цвет как точку в трёхмерной системе координат, и находить ближайшую из заранее известных точек к данной.

Вот наивное решение с полным перебором и без учёта оттенков (псевдокод)
В теории, если цветов достаточно много, то найдёт действительно ближайший цвет, а не дичь какую-то.
struct Color(red: u8,  green: u8, blue: u8);
type ColorName = String;
let KNOWN_COLORS: Map<Color, ColorName> = Map {...};


fn get_nearest_known_color_name(color: Color) -> ColorName {
  if KNOWN_COLORS.has(color) {
    return KNOWN_COLORS.get(color);
  }
  var (nearest_color, nearest_color_name) = KNOWN_COLORS.first();
  var distance = calculate_distance(nearest_color, color);
  for (key, value) of KNOWN_COLORS.skip(1) {
    var distance = calculate_distance(key, color);
    if distance < distance_to_nearest {
      distance_to_nearest = distance;
      nearest_color = key;
      nearest_color_name = value;    
    }
  }
  return nearest_color_name;
}

fn calculate_distance(lhs: Color, rhs: Color) -> f64 {
    var r = lhs.red - rhs.red;
    var g = lhs.green - rhs.green;
    var b = lhs.blue - rhs.blue; // вычитаем один вектор из другого
    return sqrt(r^2+g^2+b^2); // считаем длину получившегося вектора по теореме пифагора
}


Всё что тебе остаётся - вместо строк использовать числа.

PS: об HSV даже и не подумал.
Ответ написан
@splxgf
Технически для критерия похожести цвета используют dE. Эта формула работает в пространстве Lab.
Можно для каждого значения RGB (предположим что это sRGB) посчитать значение Lab и различие цвета находится по уже готовым формулам.
www.easyrgb.com/en/math.php
К примеру sRGB-XYZ-Lab и разница между двумя цветами DeltaE CIE.
Ответ написан
Комментировать
leahch
@leahch
3D специалист. Dолго, Dорого, Dерьмово.
Переведите каналы из строк в числа, вычисляйте абсолютную разницу по всем каналам отдельно. Разницу всех плюсуйте в общую разницу, Цвет с наимейшей разницей и будет искомым.
abs(00 - 00) + abs(00 - 00) + abs(DD -. C8) = разница
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы