有效的gif /图像颜色量化?

时间:2015-03-24 22:46:04

标签: java algorithm gif animated-gif quantization

所以我试图在我的Java应用程序中编码一些动画gif文件。我一直在使用在线发现的一些类/算法,但似乎没有一个能够运行得很好。

现在我正在使用此量化类将图像的颜色减少到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

它非常慢并且产生" eh"结果(帧之间的颜色闪烁)。

3 个答案:

答案 0 :(得分:9)

你可以谷歌搜索其他算法,如中位数,人口,k均值等。

我最近也需要这个,但也必须看起来很漂亮(我需要这个用于实时视频捕捉)所以我设法做了这样的事情:

  1. 转换为15位rgb

    如果您已经有RGB,则可以跳过此步骤但应用shift /并匹配每个通道5位。您也可以使用5:6:5方案

  2. 执行直方图

    15位rgb导致32768个条目,因此创建2个数组

    • his[32768]是像素数(直方图)
    • idx[32768]是index =颜色值

    计数颜色时确保计数器在使用低位数或高分辨率时不会溢出

  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. 重新着色图片

  8. 这是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 vs. this algo

    在上半部分是调色板绘图(原始图像包含中间图像的调色板)

    这里是真彩色照片:

    orig photo

    并减少到256色:

    reduced colors

    这需要约185ms来编码成GIF(包括颜色减少)。我对结果非常满意,但你可以看到图像不一样。重新着色后绿草簇有点不同(面积/强度较小?)

    <强> [注释]

    代码尚未优化,因此它应该是一种使其更快的方法。您可以通过以下方式提高编码速度:

    1. 降低最大编码字典大小
    2. 使用索引表进行字典或三种结构以加快搜索速度
    3. 可以将直方图冒泡排序更改为更快的排序算法(但这部分代码远非关键)
    4. 编码序列,您可以使用单个调色板(如果场景没有太多变色)
    5. 如果你想要更高的速度,那么创建静态调色板并使用抖动代替所有这些
    6. 这里是RT捕获视频的一个示例(源是50fps所以我降低分辨率以匹配速度):

      capture example

答案 1 :(得分:2)

您可以使用Gif89Encoder

这个用于编码GIF的Java类库,它涵盖了比任何其他免费Java GIF编码器更多的扩展GIF89a功能集,包括动画和嵌入式文本注释。

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

Animated GIF library for Java

我使用了动画GIF库来获得更好的结果

答案 2 :(得分:0)

在这里......我写了这个并且它的工作速度比Octree快一点,并且似乎在大多数图像上都能产生更好的效果(并且很容易编码lol)。它基本上像八叉树一样工作但相反......它创建了一个初始的颜色列表,然后根据需要按照有序位(随后降低位#)分割具有最多数量的唯一颜色的列表,直到它具有尽可能多的颜色列出所需的颜色。然后它返回一个包含每个列表中的平均颜色的数组......

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));
            }
        }

    }

}

原始图片:
Original

八叉树量化(0.145s):
Octree Quantized

BitSplit量化(0.100s):
BitSplit Quantized

原始图片:
Original Image

八叉树量化(0.233s):
Octree Quantized

BitSplit量化(0.213s):
BitSplit Quantized