Как я могу создать палитру ярких цветов из изображения?
Я пытаюсь выяснить, как сэмплировать все пиксели изображения и создать из него палитру цветов, что-то вроде этого или этого. Я понятия не имею, с чего начать. Может кто-то указать мне верное направление?
__РЕДАКТИРОВАТЬ: __
Это то, что я закончил до сих пор:
Я использовал эту функцию Pixelate, чтобы получить большие секции блоков, как предложено joe_coolish. Это работает отлично и дает мне довольно хороший образец цветов для работы (это из окна образца медузы):
Теперь, если бы кто-нибудь мог помочь мне получить 5 самых разных цветов (темно-синий, светло-синий, оранжевый, серый и персиковый (?)), Я бы любил тебя вечно. Я действительно не понимаю, как усреднить или добавить цвета вместе. Я также не могу понять, как определить, схожи ли цвета программно, есть много чисел и переменных в ваших объяснениях, которые я теряюсь, пытаясь выяснить, что и с кем делать.
6 ответов
Ответы с кодом показывают, как получить полную палитру. Если вы хотите получить средние цвета, как на сайтах, которые вы разместили, я бы так и сделал.
Исходное изображение:
Во-первых, я бы усреднил цвета, применяя фильтр нижних частот (что-то вроде размытия по Гауссу)
Таким образом, вы ограничиваете общую палитру. Оттуда я бы разделил экран на N блоков (N - общее количество пикселей, которое вы хотите в своей палитре)
Оттуда нацеливайтесь на каждый блок и итерируйте по каждому пикселю, и получите средний пиксель для этого блока и добавьте его в свой индекс палитры. Результат примерно такой:
Таким образом, ваша палитра ограничена, и вы получаете средние цвета из разных регионов. Вы можете сделать все это в коде, и если вам нужна помощь с этим, дайте мне знать, и я опубликую некоторые. Это просто высокий уровень "что бы я сделал".
Сначала возьмите пиксели на картинке: (предполагает 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
Скорее всего, в любом насыщенном изображении большинство ваших цветов будут в некотором роде уникальными. Из этого следует, что выбор разных цветов, скорее всего, не поможет вам достичь вашей цели.
Я рекомендую проверить значения 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]);
}
}
Я не пытался отфильтровать, какие оттенки выбрать. Возможно, вам придется подумать о некоторой эвристике, а не слепо выбирать верхнюю границу, потому что вы, вероятно, хотите выбрать оттенки, которые несколько различаются по цветовой гамме. У меня нет времени на дальнейшее изучение этого вопроса, но я надеюсь, что это даст некоторое представление о стратегии, которую вы можете рассмотреть.
Я начал здесь:
System.Drawing.Image img = System.Drawing.Bitmap.FromFile("file");
System.Drawing.Imaging.ColorPalette palette = img.Palette;
foreach (Color color in palette.Entries)
{
//...
}
Я собираюсь описать лучший подход на очень высоком уровне.
Сначала вы строите гистограмму цветов на картинке и их частоту.
Вы получите список всех цветов на изображении, вы можете использовать кластеризацию данных, чтобы найти подходящие цвета для слияния. Цвета, которые объединяются в средневзвешенное значение на основе частоты исходных цветов.
Таким образом, вы можете постепенно уменьшать палитру до желаемой верности, сохраняя при этом высокую контрастность, но мелкие детали и теряя верность только тогда, когда градиенты намного более тонкие.
Получив уменьшенную палитру, вы перекрашиваете изображение, используя цвет ближайшего соседа, который находится в палитре.
Алгоритм кластеризации K- средних хорошо подходит для этой проблемы. Он отлично справляется с извлечением центроидов цветовых кластеров изображения, но имейте в виду, что его недетерминированное поведение затрудняет определение фактической яркости каждого кластера.