c# - online - paleta de colores para combinar
¿Cómo puedo generar una paleta de colores prominentes de una imagen? (6)
Estoy tratando de averiguar cómo muestrear todos los píxeles de una imagen y generar una paleta de colores a partir de ella, algo como this o this . No tengo ni idea de por dónde empezar. ¿Alguien puede señalarme en la dirección correcta?
__EDIT: __
Esto es con lo que he terminado hasta ahora:
Utilicé esta función de Pixelate para obtener grandes secciones de bloques, como sugirió joe_coolish. Funciona a la perfección y me da una buena muestra de colores para trabajar (esto es de la imagen de jelly fish de muestra de Windows):
Ahora, si alguien pudiera ayudarme a obtener los 5 colores más distintos (azul oscuro, azul claro, naranja, gris y melocotón (?)), Te amaría para siempre. Realmente no entiendo cómo promediar o agregar colores juntos. Tampoco puedo averiguar cómo saber si un color es programáticamente similar, hay muchos números y variables en tus explicaciones que me pierdo al tratar de averiguar qué le hace qué a quién.
Comencé aquí:
System.Drawing.Image img = System.Drawing.Bitmap.FromFile("file");
System.Drawing.Imaging.ColorPalette palette = img.Palette;
foreach (Color color in palette.Entries)
{
//...
}
El algoritmo de agrupamiento de K-Means funciona bien para este problema. Hace un gran trabajo al extraer los centroides de los grupos de colores de imagen, pero tenga en cuenta que su comportamiento no determinista hace que determinar la prominencia real de cada grupo sea difícil.
Es probable que en cualquier imagen rica la mayoría de sus colores sean únicos de alguna manera. Seguiría, entonces, que obtener distintos colores probablemente no te ayude a lograr tu objetivo.
Recomiendo inspeccionar los valores de HSV para cada píxel en su imagen. Le dejaré un sinnúmero de ejemplos en línea de recuperación de imágenes como matrices de valores de HSV.
Con sus valores de HSV, puede calcular grupos de tonos prominentes creando una matriz entera de 256 conteos de tonos, calculando un histograma de tonos en sus datos de imagen. Puede determinar los tonos prominentes al encontrar grupos de 4 a 6 tonos secuenciales con una suma alta de conteo.
Después de seleccionar varios tonos prominentes, subdivida los píxeles de esos tonos en otro histograma que mide la saturación y seleccione grupos prominentes, y así sucesivamente.
Ejemplo tosco
El código a continuación hace un intento de ayudar a identificar los tonos prominentes. Probablemente hay otras formas impresionantes de hacer esto; Sin embargo, esto puede proporcionar algunas ideas.
Primero, obtengo todos los colores de la imagen como una matriz de objetos de Color
, como por ejemplo:
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;
}
}
Podría considerar validar que obtuve el orden de RGB en el método Color.FromArgb
call en el orden correcto.
A continuación, guardo un método de utilidad para convertir a HSV. En mi ejemplo, solo trabajaré con tonos, pero aquí hay un ejemplo completo de la conversión:
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);
}
Finalmente, construyo el histograma de matiz, defino un ancho de tonalidades (por ejemplo, 9 tonalidades) en el que se agregan las cuentas y luego informo las cuentas a la consola.
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]);
}
}
No he hecho ningún intento de filtrar hacia abajo a qué tonos elegir. Es posible que deba considerar algunas heurísticas en lugar de seleccionar ciegamente los mejores conteos, porque probablemente desee elegir tonos que estén algo separados en el espectro de colores. No tengo tiempo de seguir explorando esto, pero espero que proporcione información sobre una estrategia que pueda considerar.
Las respuestas relacionadas con el código le muestran cómo obtener la paleta completa. Si desea obtener los colores promedio, como en los sitios web que publicó, así es como lo haría.
Fuente de imagen:
Primero, promediaría los colores aplicando un filtro de paso bajo (algo así como un desenfoque gaussiano)
De esa manera estás limitando la paleta total. Desde allí dividiría la pantalla en N bloques (N es el número total de píxeles que desea en su paleta)
Desde allí, apunte a cada bloque e itere sobre cada píxel y obtenga el píxel promedio para ese bloque y agréguelo a su índice de paleta. El resultado es algo como esto:
De esta forma, su paleta es limitada y obtendrá los colores promedio de las diferentes regiones. Puedes hacer todo eso en código, y si quieres ayuda con eso, avísame y publicaré algo. Esto es solo el Nivel Alto "lo que yo haría".
Voy a describir el mejor enfoque a un nivel muy alto.
Primero construyes un histograma de colores en la imagen y su frecuencia.
Al terminar con una lista de todos los colores en la imagen, puede usar la agrupación de datos para encontrar colores candidatos para combinar. Los colores que se fusionan en un promedio ponderado según la frecuencia de los colores originales.
De esta manera, puede disminuir gradualmente la paleta a una fidelidad deseada, al tiempo que conserva un alto contraste pero detalles finos y solo pierde fidelidad cuando los gradientes son mucho más sutiles.
Una vez que tenga la paleta reducida, puede volver a colorear la imagen utilizando el color adyacente más cercano que se encuentra en la paleta.
Primero, tome los píxeles de la imagen: (asume que using System.Drawing.Imaging;
y que 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);
Entonces puedes crear tu paleta:
var distinctColors = arr.Distinct();
Opcionalmente: elimine los colores similares hasta que tenga el tamaño de paleta que prefiera. A continuación le indicamos cómo puede hacerlo (aunque definitivamente no es la forma más eficiente o precisa, solo la más sencilla):
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