¿Cuantificación efectiva de gif / imágenes en color?

Así que estoy tratando de codificar algunos archivos gif animados en mi aplicación Java. He estado usando algunas clases / algoritmos encontrados en línea, pero ninguno parece estar funcionando lo suficientemente bien.

En este momento estoy usando esta clase de cuantización para reducir los colores de una imagen a 256: http://www.java2s.com/Code/Java/2D-Graphics-GUI/Anefficientcolorquantizationalgorithm.htm

El problema es que no parece ser muy “inteligente”.

Si paso una imagen con más de 256 colores, reduce el número de color, pero no muy bien. (Los rojos se vuelven azules, etc. – errores muy obvios como este).

¿Hay otros algoritmos / bibliotecas para la cuantificación del color en Java que pueda recomendar?


Nota: Conozco a Neuquant, utilizado en este algoritmo: http://www.java2s.com/Code/Java/2D-Graphics-GUI/AnimatedGifEncoder.htm

Es muy lento y produce resultados “eh” (colores que parpadean entre fotogtwigs).

puedes buscar otros algoritmos como corte mediano, población, k-medias, etc.

Recientemente también necesitaba esto, pero tenía que ser bonito y rápido también (lo necesito para la captura de video en tiempo real) así que logré hacer algo como esto:

  1. convertir a rgb de 15 bits

    si ya tiene RGB, puede omitir este paso pero aplicar shift / y hacer coincidir 5 bits por canal. Puede usar también el esquema 5: 6: 5

  2. hacer un histogtwig

    El rgb de 15 bits lleva a 32768 entradas, así que crea 2 matrices

    • his[32768] es el conteo de píxeles (histogtwig)
    • idx[32768] es índice = valor de color

    Mientras se contabilizan los colores, asegúrese de que los contadores no se desborden si utiliza un número de bits bajo o altas resoluciones

  3. reordenar matrices para que los ceros en his[] estén al final de la matriz

    también contar las entradas que no sean cero en his[] y llamarlo hists

  4. (índice) ordenar hist[],idx[] por lo que hist[] se ordena descendiendo;

  5. crear la paleta de N colores

    Tome el color idx[i] ( i={ 0,1,2,3,...,hists-1 } ) y observe si en su paleta no hay un color similar. si se ignora este color (configúralo como el más cercano encontrado) de lo contrario, agrégalo a la paleta. si llegas a la parada de N colores

  6. crear mapeo de color

    Así que tome cada color y encuentre el color más cercano en la paleta (esto se puede hacer parcialmente en el paso 5). A esta tabla la llamo de nuevo recolor[32][32][32]

  7. cambiar el color de la imagen

Esta es la fuente de C ++:

 BYTE db,*p; AnsiString code; int e,b,bits,adr; int x0,x1,y0,y1,x,y,c; DWORD ix,cc,cm,i0,i,mask; union { DWORD dd; BYTE db[4]; } c0,c1; DWORD r,g,b; int a,aa,hists; DWORD his[32768]; DWORD idx[32768]; // 15bit histogram for (x=0;x<32768;x++) { his[x]=0; idx[x]=x; } for (y=0;y>3)&0x1F)|((cc>>6)&0x3E0)|((cc>>9)&0x7C00); if (his[cc]<0xFFFFFFFF) his[cc]++; } // remove zeroes for (x=0,y=0;y<32768;y++) { his[x]=his[y]; idx[x]=idx[y]; if (his[x]) x++; } hists=x; // sort by hist for (i=1;i;) for (i=0,x=0,y=1;y> 5)&31; r=(cc>>10)&31; c0.db[0]=b; c0.db[1]=g; c0.db[2]=r; c0.dd=(c0.dd<<3)&0x00F8F8F8; // skip if similar color already in lcolor[] for (a=0,i=0;i=DWORD(lcolors)) { x++; break; } } } // i0 = new color table size for (;x> 5)&31; r=(cc>>10)&31; c0.db[0]=b; c0.db[1]=g; c0.db[2]=r; c0.dd=(c0.dd<<3)&0x00F8F8F8; // find closest color int dc=-1; DWORD ii=0; for (a=0,i=0;ia)) { dc=a; ii=i; } } recolor[r][g][b]=ii; } 

Y la clase de imagen del propietario contiene esto:

 // image data Graphics::TBitmap *bmp,*bmp0,*bmp1; // actual and restre to 32bit frames,and 8bit input conversion frame int xs,ys; // resolution int *py; // interlace table DWORD **pyx,**pyx0; // ScanLine[] of bmp,bmp0 BYTE **pyx1; // colors (colors are computed from color_bits) DWORD gcolor[256]; //hdr DWORD lcolor[256]; //img BYTE recolor[32][32][32]; //encode reduce color table int scolors,scolor_bits; //hdr screen color depth int gcolors,gcolor_bits; //hdr global pallete int lcolors,lcolor_bits; //img/hdr local palette 
  • el pyx[],bmp contiene la imagen fuente de 32 bits
  • el pyx1[],bmp1 es una imagen temporal de 8 pyx1[],bmp1 para la encoding

Así es como se realiza el cambio de color:

  // recolor to lcolors for (y=0;y>3)&0x001F1F1F; b=c0.db[0]; g=c0.db[1]; r=c0.db[2]; i=recolor[r][g][b]; // pyx [y][x]=lcolor[i]; // 32 bit output (visual) pyx1[y][x]=i; // 8 bit output (encoding) } 

Aquí algunos ejemplos de resultados:

esta es una comparación entre la reducción de color VCL / GDI, mi enfoque y la imagen original)

GDI vs. este algo

En la parte superior está el dibujo de la paleta de colores (la imagen original contiene la paleta de la imagen del medio)

aquí foto en color verdadero:

foto original

y reducido a 256 colores:

colores reducidos

Esto requirió ~ 185 ms para codificar en GIF (reducción de color incluida). Estoy muy satisfecho con el resultado, pero como puede ver, las imágenes no son lo mismo. Los cúmulos de hierba verde son un poco diferentes después de cambiar el color (¿menos área / intensidad?)

[Notas]

El código aún no está optimizado, por lo que debería ser una forma de hacerlo más rápido. Puede boost la velocidad de encoding mediante:

  1. bajando el tamaño máximo del diccionario de encoding
  2. usando la tabla de índice para diccionario o estructura tres para acelerar la búsqueda
  3. puede cambiar la clasificación de burbujas del histogtwig a algo más rápido para la ordenación (pero esa parte del código está lejos de ser crítica)
  4. para codificar la secuencia, puede usar una sola paleta (si la escena no cambia mucho el color)
  5. Si desea aún más velocidad, cree una paleta estática y use el ttwigdo en lugar de todo esto

Aquí un ejemplo de video capturado RT (la fuente era 50 fps, así que reduzco la resolución para que coincida con la velocidad):

capturar ejemplo

podrías usar Gif89Encoder

Esta biblioteca de clases Java para codificar GIF abarca más funciones extendidas de GIF89a, incluidas animaciones y comentarios textuales incorporados, que cualquier otro codificador GIF Java libre.

o http://imagej.nih.gov/ij/

o una biblioteca animada GIF para Java

He utilizado una biblioteca GIF animada para Java con buenos resultados

Aquí … escribí esto y funciona un poco más rápido que Octree y parece dar mejores resultados en la mayoría de las imágenes (y fue muchísimo más fácil codificar jaja). Básicamente funciona como un octárbol, pero al revés … crea una lista inicial de colores y luego divide la lista con el mayor número de colores únicos por bits ordenados (con un bit # que baja posteriormente) según sea necesario hasta que tenga tantos listas como colores deseados. Luego devuelve una matriz que contiene el color promedio de cada lista …

 using System; using System.Collections.Generic; using System.Drawing; using System.Linq; namespace SeelWorks.Libraries.Imaging.Quantization { public static class BitSplitQuantizer { public static Color[] CreatePalette(IEnumerable sourceColors, int maxColors = 256) { var collections = new List(); collections.Add(new Collection()); foreach(var _ in sourceColors) collections[0].Add(_); var offset = 1; while(collections.Count < maxColors) { if(offset > collections.Count) { break; } else { collections = collections.OrderBy(_ => _.Colors.Count).ToList(); var split = collections[collections.Count - offset].Split(); if((split.Count == 1) || ((collections.Count + split.Count - 1) > maxColors)) { offset++; } else { offset = 1; collections.RemoveAt(collections.Count - 1); collections.AddRange(split); } } } return collections.Select(_ => _.GetAverageColor()).ToArray(); } private class Collection { public Dictionary Colors = new Dictionary(); public int Level = -1; public void Add(Color color) { if(!Colors.ContainsKey(color)) Colors.Add(color, 0); Colors[color]++; } public List Split() { var colors = Colors.OrderBy(_ => _.Value).Select(_ => _.Key).ToList(); var level = (7 - Level - 1); var indexes = new int[8] { -1, -1, -1, -1, -1, -1, -1, -1 }; var ret = new List(); foreach(var _ in colors) { var index_ = ((((_.R >> level) & 1) << 2) | (((_.G >> level) & 1) << 1) | ((_.B >> level) & 1)); if(indexes[index_] == -1) { ret.Add(new Collection()); indexes[index_] = (ret.Count - 1); ret[ret.Count - 1].Level = (Level + 1); } ret[indexes[index_]].Colors[_] = Colors[_]; } return ret; } public Color GetAverageColor() { var r = 0.0; var g = 0.0; var b = 0.0; var t = 0.0; foreach(var _ in Colors) { r += (_.Key.R * _.Value); g += (_.Key.G * _.Value); b += (_.Key.B * _.Value); t += _.Value; } return Color.FromArgb((int)Math.Round(r / t), (int)Math.Round(g / t), (int)Math.Round(b / t)); } } } } 

Imagen original:
Original

Octree cuantificado (0.145s):
Octree cuantificado

BitSplit cuantificado (0.100s):
BitSplit cuantificado

Imagen original:
Imagen original

Octree cuantificado (0.233s):
Octree cuantificado

BitSplit cuantificado (0.213s):
BitSplit cuantificado