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

所以我试图在我的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”结果(帧之间的颜色闪烁)。

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

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

  1. 转换为15位rgb

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

  2. 做直方图

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

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

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

  3. 重新排序数组,使得his[]中的零位于数组的末尾

    也计算his[]非零项并将其hists

  4. (index)sort 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>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; } 

所有者图像类包含:

 // 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包含源32bit图像
  • pyx1[],bmp1是用于编码的临时8位图像

这是重新着色的方式:

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

这里有一些输出示例:

这是VCL / GDI色彩还原,我的方法和原始图像之间的比较)

GDI对比这个算法

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

这里真彩照片:

原始照片

并减少到256种颜色:

减少颜色

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

[笔记]

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

  1. 降低最大编码字典大小
  2. 使用索引表进行字典或三种结构加速搜索
  3. 可以改变直方图冒泡排序,以更快地排序算法(但这部分代码远非关键)
  4. 编码序列你可以使用单个调色板(如果场景没有太多的颜色变化)
  5. 如果你想要更高的速度,那么创建静态调色板并使用抖动代替所有这些

这里是一个RT捕获video的例子(源是50fps,所以我降低分辨率以匹配速度):

捕获示例

你可以使用Gif89Encoder

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

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

或Java动画GIF库

我已经使用Animated GIF库来获得良好的Java效果

在这里……我写了这个并且它的工作速度比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 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)); } } } } 

原始图片:
原版的

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

BitSplit量化(0.100s):
BitSplit量化

原始图片:
原始图像

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

BitSplit量化(0.213s):
BitSplit量化