Как я могу создать палитру ярких цветов из изображения?

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

__EDIT: __

Вот что у меня получилось до сих пор:

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

Теперь, если бы кто-то мог помочь мне получить 5 самых ярких цветов (самый темный синий, самый светлый синий, оранжевый, серый и персиковый (?)), Я бы любил тебя вечно. Я действительно не понимаю, как усреднить или сложить цвета вместе. Я также не могу понять, как определить, похож ли цвет программно, в ваших объяснениях есть несколько чисел и переменных, и я теряюсь, пытаясь понять, что с кем делает.


person scottm    schedule 28.04.2011    source источник
comment
Подобные вопросы и ответы заставляют меня гордиться тем, что я являюсь частью этого сообщества.   -  person Adriano Carneiro    schedule 29.04.2011
comment
Я вижу, вы добавили еще к вопросу. вам все еще нужна помощь в добавлении и усреднении цветов?   -  person joe_coolish    schedule 15.05.2011
comment
Поиск палитры цветов, которая лучше всего соответствует изображению с истинными цветами, - это старая проблема, которую пытаются решить несколько алгоритмов. Вы можете начать со страницы Википедии о цветном квантовании и проверить это список алгоритмов.   -  person wip    schedule 03.12.2013


Ответы (6)


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

Исходное изображение:

Источник

Сначала я усреднял цвета, применяя фильтр нижних частот (что-то вроде размытия по Гауссу).

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

Таким образом вы ограничиваете общую палитру. Оттуда я бы разделил экран на N блоков (N - общее количество пикселей, которое вы хотите в своей палитре).

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

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

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

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

person joe_coolish    schedule 28.04.2011
comment
Это определенно кажется наиболее разумным. Я могу сделать большую часть этого, часть, которую я не знаю, как это сделать, - это получить средний пиксель для региона. Тот самый, который встречается чаще всего? - person scottm; 28.04.2011
comment
Есть разные техники. Допустим, вы работаете с блоками 24x24, а ваше изображение имеет размер 6x4 (144x96 пикселей). Для блока 8 (индекс 1,1) я бы определил пиксели сглаженного изображения, сложил все значения цвета вместе и взял среднее значение. В этом примере это будут пиксели 24-48 x 24-48. Я бы также избегал RGB и использовал HSI или HSL, а затем усреднял эти три значения. Это даст вам лучший результат - person joe_coolish; 29.04.2011
comment
Просто ради интереса, зачем нужен шаг размытия? Может ли усреднение для каждого блока работать так же хорошо? - person Thomas Bratt; 25.06.2012
comment
Фильтр нижних частот (размытие) используется для сглаживания изображения и удаления артефактов. Вы можете усреднить все цвета в кубах и все будет в порядке, но иногда в кубе появляются артефакты. Например. самолет, летящий в первом кубе, приведет к усреднению случайных красных, зеленых и синих оттенков с небом. Размытие сгладит эти резкие изменения цвета для более предсказуемого результата. В любом случае это хорошо. - person joe_coolish; 25.06.2012
comment
Это неизбежно удалит основные моменты, которые вы хотите сохранить. Там много голубого, потому что там много морской акватории, но на самом деле вам не нужны все эти многие его оттенки. Тем временем вы теряете весь желтый пляжный цвет. - person Nyerguds; 24.04.2017
comment
Это не помогает найти заметные цвета на изображении. Он находит средние значения различных ярких цветов, что почти гарантирует, что тестируемые цвета действительно присутствуют на изображении. Вместо белого для облаков и синего для неба вы получите много голубых и серых тонов. - person Cris Luengo; 18.05.2018
comment
@CrisLuengo Да, если бы я сделал это снова сегодня (это ответ семилетней давности. Черт возьми, это было так долго?), Я бы использовал некоторые известные алгоритмы машинного обучения для извлечения основных, второстепенных цветов переднего плана и фона. Я бы не стал делать это вручную. ML прошел ДОЛГОЙ путь! - person joe_coolish; 23.05.2018
comment
Я даже не говорю о машинном обучении. Эта проблема была решена, когда вы опубликовали этот ответ несколько десятилетий назад. Компьютерные алгоритмы квантования цвета на растровых изображениях изучаются с 1970-х годов. (цитирует Википедию). Извините за то, что так негативно отношусь к вашему решению, я просто очень удивлен, что оно набирает столько голосов ... :) - person Cris Luengo; 23.05.2018
comment
@CrisLuengo Я просто очень удивлен, что он получил столько голосов ... :) На самом деле, я тоже! Я еще учился в колледже, когда написал это, и долгое время это был мой самый популярный комментарий. Как я уже сказал, сейчас я бы сделал это совсем по-другому. - person joe_coolish; 05.06.2018

Сначала возьмите пиксели на картинке: (предполагается using System.Drawing.Imaging; и using System.Runtime.InteropServices)

Bitmap b = new Bitmap(myImage);
BitmapData bd = b.LockBits(new Rectangle(0, 0, b.Width, b.Height), ImageLockMode.ReadOnly, ImageFormat.Format32Bpp);
int[] arr = new int[bd.Width * bd.Height - 1];
Marshal.Copy(bd.Scan0, arr, 0, arr.Length);
b.UnlockBits(bd);

Затем вы можете создать свою палитру:

var distinctColors = arr.Distinct();

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

var dc = distinctColors.toArray(); // int dc[] = distinctColors.toArray() is what it used to be
int cmIndex1 = -1;
int cmIndex2 = -1;
int cmDiff = -1;
for (int i = 0; i < dc.length; i++) {
    Color c1 = Color.FromArgb(dc[i]);
    for (int j = i + 1; j < dc.length; j++) {
        Color c2 = Color.FromArgb(dc[j]);
        // Note: you might want to include alpha below
        int diff = Math.Abs(c1.R - c2.R) + Math.Abs(c1.G - c2.G) + Math.Abs(c1.B - c2.B);
        if (cmDiff < 0 || diff < cmDiff) {
            cmIndex1 = i;
            cmIndex2 = j;
            cmDiff = diff;
        }
    }
}
// Remove the colors, replace with average, repeat until you have the desired number of colors
person Ry-♦    schedule 28.04.2011
comment
Есть ли хороший способ избавиться от похожих цветов? Я знал, что могу просто перебирать пиксели, чтобы получить все отдельные цвета, но я хочу попытаться удалить похожие, пока не останется только 4 или 5 - person scottm; 28.04.2011
comment
Да, есть много способов. Я думаю, что самым простым было бы: пока есть более 5 цветов, переберите все цвета в цикле, а затем внутри этого цикла переберите все цвета, сравнивая (A) RGB-сумму цвета с цветом во внешнем цикле. Сохраните 3 переменные для ближайших совпадений (разница, совпадение 1, совпадение 2). Удалите два самых близких и замените их средним цветом. Извините, если это немного сбивает с толку: P - person Ry-♦; 28.04.2011

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

Я рекомендую проверять значения HSV для каждого пикселя вашего изображения. Я оставлю вам бесчисленное количество онлайн-примеров получения изображений в виде массивов значений HSV.

С вашими значениями HSV вы можете рассчитать кластеры ярких оттенков, создав целочисленный массив из 256 значений оттенков, вычислив гистограмму оттенков в ваших данных изображения. Вы можете определить заметные оттенки, найдя группы из 4-6 последовательных оттенков с большой суммой счета.

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

Примерный пример

В приведенном ниже коде сделана попытка помочь идентифицировать заметные оттенки. Скорее всего, есть и другие замечательные способы сделать это; однако это может дать некоторые идеи.

Сначала я получаю все цвета изображения в виде массива из Color объектов, например:

private static Color[] GetImageData(Image image)
{
    using (var b = new Bitmap(image))
    {
        var bd = b.LockBits(new Rectangle(0, 0, b.Width, b.Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
        byte[] arr = new byte[bd.Width * bd.Height * 3];
        Color[] colors = new Color[bd.Width * bd.Height];
        Marshal.Copy(bd.Scan0, arr, 0, arr.Length);
        b.UnlockBits(bd);

        for (int i = 0; i < colors.Length; i++)
        {
            var start = i*3;
            colors[i] = Color.FromArgb(arr[start], arr[start + 1], arr[start + 2]);
        }

        return colors;
    }
}

Вы можете проверить, что я получил порядок RGB в вызове метода Color.FromArgb в правильном порядке.

Затем я припрятал служебный метод для преобразования в HSV. В моем примере я буду работать только с оттенками, но вот полный рабочий пример преобразования:

private static void ColorToHSV(Color color, out int hue, out int saturation, out int value)
{
    int max = Math.Max(color.R, Math.Max(color.G, color.B));
    int min = Math.Min(color.R, Math.Min(color.G, color.B));

    hue = (int)(color.GetHue() * 256f / 360f);
    saturation = (max == 0) ? 0 : (int)(1d - (1d * min / max));
    value = (int)(max / 255d);
}

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

private static void ProcessImage(Color[] imagecolors)
{
    var hues = new int[256];
    var hueclusters = new int[256];
    int hue, saturation, value;

    // build hue histogram.
    foreach (var color in imagecolors) {
        ColorToHSV(color, out hue, out saturation, out value);
        hues[hue]++;
    }

    // calculate counts for clusters of colors.
    for (int i = 0; i < 256; i++) {
        int huecluster = 0;
        for (int count = 0, j = i; count < 9; count++, j++) {
            huecluster += hues[j % 256];
        }

        hueclusters[(i + 5) % 256] = huecluster;
    }

    // Print clusters on the console
    for (int i = 0; i < 256; i++) {
        Console.WriteLine("Hue {0}, Score {1}.", i, hueclusters[i]);
    }
}

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

person kbrimington    schedule 28.04.2011
comment
Это должно быть new byte[bd.Height * bd.Stride] - строки всегда выравниваются по 4 байтам, поэтому при 3 байтах на цвет длина строки редко будет совпадать с width * 3 точно. Это также означает, что вам нужно специально перебирать y и x, чтобы учитывать эти байты заполнения. - person Nyerguds; 17.05.2018

Я начал здесь:

System.Drawing.Image img = System.Drawing.Bitmap.FromFile("file");
System.Drawing.Imaging.ColorPalette palette = img.Palette;
foreach (Color color in palette.Entries)
{
  //...
}
person Gary Kindel    schedule 28.04.2011
comment
Это будет работать только с растровыми изображениями, которые имеют 256 цветов или меньше, в противном случае свойство img.Palette.Entries не будет содержать никаких записей (пиксели сопоставляются со значением цвета RGB вместо цветовой палитры) - person Chris; 15.10.2013
comment
Весь этот вопрос не имеет значения, если у изображения уже есть палитра, поэтому я почти уверен, что это не так ... - person Nyerguds; 10.01.2018

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

Сначала вы строите гистограмму цветов на картинке и их частотности.

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

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

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

person SteamyThePunk    schedule 09.02.2018
comment
Интересно, что кому-то понадобилось 7 лет, чтобы найти этот вопрос, наполненный любительскими ответами, и, наконец, опубликовать правильное, устоявшееся решение. - person Cris Luengo; 18.05.2018
comment
См. этот ответ на другой вопрос для краткого описания некоторых алгоритмов, фактически используемых в отрасли для этой цели. - person Cris Luengo; 18.05.2018
comment
Отлично. Большое спасибо, Крис - person SteamyThePunk; 19.05.2018

Кластеризация K-средних алгоритм хорошо работает для этой задачи. Он отлично справляется с извлечением центроидов цветовых кластеров изображения, но имейте в виду, что его недетерминированное поведение затрудняет определение фактического выступа каждого кластера.

person snort    schedule 07.07.2014