Эффективное 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-средних и т. д.

Недавно я тоже нуждался в этом, но должен был быть красивым и быстрым (мне это нужно для захвата видео в реальном времени), поэтому мне удалось сделать что-то вроде этого:

  1. конвертировать в 15-битный RGB

    если у вас уже есть RGB, вы можете пропустить этот шаг, но примените shift/ и для соответствия 5-битному на канал. Вы можете использовать также схему 5:6:5

  2. сделать гистограмму

    15-битный RGB приводит к 32768 записей, поэтому создайте 2 массива

    • his[32768] это количество пикселей (гистограмма)
    • idx[32768] индекс = значение цвета

    При подсчете цветов убедитесь, что счетчики не переполняются, если используется низкий битовый счет или высокое разрешение

  3. переупорядочить массивы так нули в his[] находятся в конце массива

    также считать ненулевые записи в his[] и назовите это hists

  4. (индекс) сортировка hist[],idx[] так hist[] упорядочен по убыванию;

  5. создать N-цветовую палитру

    Взять цвет idx[i] (i={ 0,1,2,3,...,hists-1 }) и посмотрите, если в вашей палитре нет аналогичного цвета. Если игнорировать этот цвет (установите его как наиболее близкий найденный), в противном случае добавьте его в палитру. если вы достигнете N цветов остановить

  6. создать цветовое отображение

    Поэтому возьмите каждый цвет и найдите наиболее близкий цвет в палитре (это можно частично сделать на шаге 5). Я называю эту таблицу recolor[32][32][32]

  7. перекрасить изображение

Это источник 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, моим подходом и оригинальным изображением)

GDI против этого алгоритма

В верхней части находится цветовая палитра рисования (исходное изображение содержит палитру из среднего изображения)

вот верное цветное фото:

оригинальное фото

и уменьшено до 256 цветов:

уменьшенные цвета

Для кодирования в GIF потребовалось ~185 мс (с уменьшением цвета). Я очень доволен результатом, но, как вы можете видеть, изображения не совпадают. Скопления зеленой травы немного отличаются после перекраски (меньше площадь / интенсивность?)

[Заметки]

Код еще не оптимизирован, поэтому это должен быть способ сделать его более быстрым. Вы можете повысить скорость кодирования:

  1. снижение максимального размера словаря кодирования
  2. используя индексную таблицу для словаря или три структуры, чтобы ускорить поиск
  3. может изменить пузырьковую сортировку гистограммы на более быструю сортировку алгоритма (но эта часть кода далека от критической)
  4. для кодирования последовательности вы можете использовать одну палитру (если сцена не слишком сильно меняет цвет)
  5. Если вы хотите еще большей скорости, создайте статическую палитру и используйте вместо этого дизеринг.

Вот пример 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));
            }
        }

    }

}

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

Октри квантованный (0,145 с):
Октри Квантованный

Квантованный BitSplit (0,100 с):
BitSplit квантованный

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

Октри квантованный (0,233 с):
Октри Квантованный

Квантованный BitSplit (0,213 с):
BitSplit квантованный

Вы могли бы использовать Gif89Encoder

Эта библиотека классов Java для кодирования GIF-файлов охватывает больше расширенного набора функций GIF89a, включая анимацию и встроенные текстовые комментарии, чем любой другой бесплатный кодер Java GIF.

или http://imagej.nih.gov/ij/

или библиотека анимированных GIF для Java

Я использовал библиотеку Animated GIF для Java с хорошими результатами

Другие вопросы по тегам