Эффективное gif/ цветное квантование изображения?
Поэтому я пытаюсь закодировать некоторые анимированные GIF-файлы в моем приложении Java. Я использовал некоторые классы / алгоритмы, найденные в сети, но ни один из них не работает достаточно хорошо.
Прямо сейчас я использую этот класс квантования, чтобы уменьшить цвета изображения до 256: http://www.java2s.com/Code/Java/2D-Graphics-GUI/Anefficientcolorquantizationalgorithm.htm
Проблема в том, что это не кажется очень "умным".
Если я передаю изображение с более чем 256 цветами, оно уменьшает количество цветов, но не очень хорошо. (Красные становятся синими и т. Д. - очень очевидные ошибки, подобные этой).
Существуют ли другие алгоритмы / библиотеки для цветового квантования в Java, которые вы можете порекомендовать?
Примечание: я знаю о Neuquant, используемом в этом алгоритме: http://www.java2s.com/Code/Java/2D-Graphics-GUI/AnimatedGifEncoder.htm
Он очень медленный и дает "а" результаты (цвета, мерцающие между кадрами).
3 ответа
Вы можете использовать Google для других алгоритмов, таких как медиана, популяция,k-средних и т. д.
Недавно я тоже нуждался в этом, но должен был быть красивым и быстрым (мне это нужно для захвата видео в реальном времени), поэтому мне удалось сделать что-то вроде этого:
конвертировать в 15-битный RGB
если у вас уже есть RGB, вы можете пропустить этот шаг, но примените shift/ и для соответствия 5-битному на канал. Вы можете использовать также схему 5:6:5
сделать гистограмму
15-битный RGB приводит к 32768 записей, поэтому создайте 2 массива
his[32768]
это количество пикселей (гистограмма)idx[32768]
индекс = значение цвета
При подсчете цветов убедитесь, что счетчики не переполняются, если используется низкий битовый счет или высокое разрешение
переупорядочить массивы так нули в
his[]
находятся в конце массиватакже считать ненулевые записи в
his[]
и назовите этоhists
(индекс) сортировка
hist[],idx[]
такhist[]
упорядочен по убыванию;создать N-цветовую палитру
Взять цвет
idx[i]
(i={ 0,1,2,3,...,hists-1 }
) и посмотрите, если в вашей палитре нет аналогичного цвета. Если игнорировать этот цвет (установите его как наиболее близкий найденный), в противном случае добавьте его в палитру. если вы достигнете N цветов остановитьсоздать цветовое отображение
Поэтому возьмите каждый цвет и найдите наиболее близкий цвет в палитре (это можно частично сделать на шаге 5). Я называю эту таблицу
recolor[32][32][32]
перекрасить изображение
Это источник 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<ys;y++)
for (x=0;x<xs;x++)
{
cc=pyx[y][x];
cc=((cc>>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<hists;x++,y++)
if (his[x]<his[y])
{
i=his[x]; his[x]=his[y]; his[y]=i;
i=idx[x]; idx[x]=idx[y]; idx[y]=i; i=1;
}
// set lcolor color palete
for (i0=0,x=0;x<hists;x++) // main colors
{
cc=idx[x];
b= cc &31;
g=(cc>> 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<i0;i++)
{
c1.dd=lcolor[i];
aa=int(BYTE(c1.db[0]))-int(BYTE(c0.db[0])); if (aa<=0) aa=-aa; a =aa;
aa=int(BYTE(c1.db[1]))-int(BYTE(c0.db[1])); if (aa<=0) aa=-aa; a+=aa;
aa=int(BYTE(c1.db[2]))-int(BYTE(c0.db[2])); if (aa<=0) aa=-aa; a+=aa;
if (a<=16) { a=1; break; } a=0; // *** treshold ***
}
if (a) recolor[r][g][b]=i;
else{
recolor[r][g][b]=i0;
lcolor[i0]=c0.dd; i0++;
if (i0>=DWORD(lcolors)) { x++; break; }
}
} // i0 = new color table size
for (;x<hists;x++) // minor colors
{
cc=idx[x];
b= cc &31;
g=(cc>> 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;i<i0;i++)
{
c1.dd=lcolor[i];
aa=int(BYTE(c1.db[0]))-int(BYTE(c0.db[0])); if (aa<=0) aa=-aa; a =aa;
aa=int(BYTE(c1.db[1]))-int(BYTE(c0.db[1])); if (aa<=0) aa=-aa; a+=aa;
aa=int(BYTE(c1.db[2]))-int(BYTE(c0.db[2])); if (aa<=0) aa=-aa; a+=aa;
if ((dc<0)||(dc>a)) { dc=a; ii=i; }
}
recolor[r][g][b]=ii;
}
И класс изображения владельца содержит это:
// image data
Graphics::TBitmap *bmp,*bmp0,*bmp1; // actual and restore 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
-
pyx[],bmp
содержит исходное 32-битное изображение -
pyx1[],bmp1
временное 8-битное изображение для кодирования
Вот как делается перекраска:
// recolor to lcolors
for (y=0;y<ys;y++)
for (x=0;x<xs;x++)
{
int r,g,b;
c0.dd=(pyx[y][x]>>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)
}
Вот несколько примеров вывода:
это сравнение между уменьшением цвета VCL/GDI, моим подходом и оригинальным изображением)
В верхней части находится цветовая палитра рисования (исходное изображение содержит палитру из среднего изображения)
вот верное цветное фото:
и уменьшено до 256 цветов:
Для кодирования в GIF потребовалось ~185 мс (с уменьшением цвета). Я очень доволен результатом, но, как вы можете видеть, изображения не совпадают. Скопления зеленой травы немного отличаются после перекраски (меньше площадь / интенсивность?)
[Заметки]
Код еще не оптимизирован, поэтому это должен быть способ сделать его более быстрым. Вы можете повысить скорость кодирования:
- снижение максимального размера словаря кодирования
- используя индексную таблицу для словаря или три структуры, чтобы ускорить поиск
- может изменить пузырьковую сортировку гистограммы на более быструю сортировку алгоритма (но эта часть кода далека от критической)
- для кодирования последовательности вы можете использовать одну палитру (если сцена не слишком сильно меняет цвет)
- Если вы хотите еще большей скорости, создайте статическую палитру и используйте вместо этого дизеринг.
Вот пример RT-захваченного видео (источник был 50fps, поэтому я уменьшил разрешение, чтобы соответствовать скорости):
Вот... Я написал это, и он работает немного быстрее, чем Octree, и, кажется, дает лучшие результаты на большинстве изображений (и это было чертовски легко кодировать). Он в основном работает как Octree, но в противоположность... он создает начальный список цветов, а затем разбивает список с наибольшим количеством уникальных цветов по упорядоченным битам (с последующим понижением бита #) по мере необходимости, пока не будет столько списки желаемых цветов. Затем он возвращает массив, содержащий средний цвет из каждого списка...
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<Color> sourceColors, int maxColors = 256) {
var collections = new List<Collection>();
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<Color, int> Colors = new Dictionary<Color, int>();
public int Level = -1;
public void Add(Color color) {
if(!Colors.ContainsKey(color)) Colors.Add(color, 0);
Colors[color]++;
}
public List<Collection> 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<Collection>();
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));
}
}
}
}
Вы могли бы использовать Gif89Encoder
Эта библиотека классов Java для кодирования GIF-файлов охватывает больше расширенного набора функций GIF89a, включая анимацию и встроенные текстовые комментарии, чем любой другой бесплатный кодер Java GIF.
или библиотека анимированных GIF для Java
Я использовал библиотеку Animated GIF для Java с хорошими результатами