有效的 gif/图像颜色量化?
Posted
技术标签:
【中文标题】有效的 gif/图像颜色量化?【英文标题】:Effective gif/image color quantization? 【发布时间】:2015-05-28 10:52:54 【问题描述】:所以我试图在我的 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”结果(帧之间的颜色闪烁)。
【问题讨论】:
要停止帧之间的闪烁,请构建一个包含所有单独帧的大图像,然后从中创建一个调色板以用于所有帧。 GIF 标准允许使用单个全局颜色表,而局部颜色表是可选的 - 它们可以省略。 @Jason 这是一种可能性。如果一切都失败了,我可能会调查一下。这似乎是一个非常技术性的事情(有效地编码动画 gif),所以我现在真的依赖其他专门的库,并且宁愿不必编写自己的解决方案。再加上每个动画的帧数可能在 2 到 100 到 1000 之间,不确定它是如何工作的。 只是让您知道,由于您要求我们查找图书馆,因此您的问题可能已关闭。然而,你有没有看过:web.cs.wpi.edu/~matt/courses/cs563/talks/color_quant/… 你试过k-means算法吗? @FTLRalph 终于完成了我的答案编辑...检查我的方法 【参考方案1】:你可以使用Gif89Encoder
这个用于编码 GIF 的 Java 类库与任何其他免费的 Java GIF 编码器相比,它涵盖了更多扩展的 GIF89a 功能集,包括动画和嵌入式文本 cmets。
或http://imagej.nih.gov/ij/
或Animated GIF library for Java
我使用了 Java 动画 GIF 库,效果很好
【讨论】:
嗯,感谢您的链接,感谢您的宝贵时间。我之前尝试过 Gif89Encoder。问题是它在编码之前将所有帧存储在内存中(非常有可能因分配的内存过多而崩溃)。 “Java 动画 GIF 库”似乎只是包装在新存储库中的“AnimatedGifEncoder”,但我可能会试一试,看看它是否不同。以我的经验,它速度较慢并且会产生更大的文件。第二个链接看起来像是一个程序而不是一个库?【参考方案2】:你可以用谷歌搜索其他算法,如中位数切割、人口、k-means 等。
我最近也需要这个,但必须好看又快(我需要这个来实时视频捕捉)所以我设法做了这样的事情:
转换为 15 位 rgb
如果您已经拥有 RGB,则可以跳过此步骤,但应用 shift/and 以匹配每个通道 5 位。您也可以使用 5:6:5 方案
做直方图
15 位 rgb 导致 32768 个条目,因此创建 2 个数组
his[32768]
是像素数(直方图)
idx[32768]
是索引 = 颜色值
在计数颜色时,确保计数器在使用低位计数或高分辨率时不会溢出
对数组重新排序,使 his[]
中的零位于数组末尾
还计算his[]
中的非零条目并将其命名为hists
(index) 对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 种颜色:
这需要大约 185 毫秒来编码为 GIF(包括颜色减少)。我对结果非常满意,但正如您所见,图像并不相同。重新着色后的绿草簇有点不同(面积/强度较小?)
[备注]
代码尚未优化,因此它应该是一种使其更快的方法。您可以通过以下方式提高编码速度:
-
降低最大编码字典大小
使用字典或三结构的索引表来加快搜索速度
可以将直方图冒泡排序更改为更快的排序算法(但那部分代码远非关键)
要编码序列,您可以使用单个调色板(如果场景没有太多颜色变化)
如果您想要更快的速度,请创建静态调色板并使用抖动来代替所有这些
这里是 RT 捕获视频的示例(源是 50fps,所以我降低了分辨率以匹配速度):
【讨论】:
如何用这个创建动画 gif?能否提供编码动画的来源? 您只需要添加 GIF LZW 压缩(量化后)。遗憾的是,不,我不能共享源代码,因为 GIF(尤其是 LZW 部分)编码/解码仍然是许可的,并且是非法的。相反,请参阅 ***.com/a/33604815/2521214 和所有嵌套的子链接,您会发现自己编写代码的所有信息(甚至是一些用于解析 GIF 块的代码,而这些代码是 hard 编码的)。但是,如果您想合法使用它,则需要获得许可证。更简单的选择是使用已获得许可的第 3 方插件。 感谢您的回答,您可以看这个问题(***.com/questions/33941767/…)吗?我试图实现 3rd 方库 @AntonShkurenko 刚刚将默认 VGA 调色板作为全局调色板 + dithering 添加到我的编码器中,速度非常棒......即使在动态变化的颜色视图中也能保留颜色 :) 你能解释更多吗?:) 我试过 web-palette,但那太糟糕了【参考方案3】:这里...我写了这个,它的运行速度比 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.145s):
BitSplit 量化(0.100s):
原图:
八叉树量化(0.233s):
BitSplit 量化(0.213s):
【讨论】:
非常有趣的方法 +1 ...它有一个明显的缺点,那就是如果 bin 包含太多不同的颜色,可能会导致颜色伪影...为避免这种情况,我设法将调色板拆分为对相似颜色进行分组(不是基于计数,而是基于颜色箱内颜色的相对距离)。它需要自适应阈值,但仍然稍微快一些。对于 ~1500 种不同颜色的图像,整个 GIF (512x512x24bpp) 编码与抖动,我的方法是 ~240ms,而你的方法是 220ms,但是你的方法结果在视觉上更差(不是太糟糕但仍然很明显)以上是关于有效的 gif/图像颜色量化?的主要内容,如果未能解决你的问题,请参考以下文章
使用 Image Magick 创建具有固定颜色图的 GIF 图像