量化(减少图像的颜色)

Posted

技术标签:

【中文标题】量化(减少图像的颜色)【英文标题】:quantization (Reduction of colors of image) 【发布时间】:2016-04-06 01:52:36 【问题描述】:

我正在尝试在 C# 中将图像量化为 10 种颜色,但在绘制量化图像时遇到问题,我已经制作了映射表并且它是正确的,我已经制作了原始图像的副本并且我正在更改基于映射表的像素颜色,我使用以下代码:

bm = new Bitmap(pictureBox1.Image);
        Dictionary<Color, int> histo = new Dictionary<Color, int>();
        for (int x = 0; x < bm.Size.Width; x++)
            for (int y = 0; y < bm.Size.Height; y++)
            
                Color c = bm.GetPixel(x, y);
                if (histo.ContainsKey(c))
                    histo[c] = histo[c] + 1;
                else
                    histo.Add(c, 1);
            
        var result1 = histo.OrderByDescending(a => a.Value);
                  int ind = 0;
        List<Color> mostusedcolor = new List<Color>();
        foreach (var entry in result1)
        
            if (ind < 10)
            
                mostusedcolor.Add(entry.Key);
                ind++;
            
            else
                break;
        
        Double temp_red,temp_green,temp_blue,temp;
        Dictionary<Color, Double> dist = new Dictionary<Color, double>();
        Dictionary<Color, Color> mapping = new Dictionary<Color, Color>();
        foreach (var p in result1)
        
            dist.Clear();
            foreach (Color pp in mostusedcolor)
            
                temp_red = Math.Pow((Convert.ToDouble(p.Key.R) - Convert.ToDouble(pp.R)), 2.0);
                temp_green = Math.Pow((Convert.ToDouble(p.Key.G) - Convert.ToDouble(pp.G)), 2.0);
                temp_blue = Math.Pow((Convert.ToDouble(p.Key.B) - Convert.ToDouble(pp.B)), 2.0);
                temp = Math.Sqrt((temp_red + temp_green + temp_blue));
                dist.Add(pp, temp);
            
            var min = dist.OrderBy(k=>k.Value).FirstOrDefault();
            mapping.Add(p.Key, min.Key);
        
  Bitmap copy = new Bitmap(bm);

        for (int x = 0; x < copy.Size.Width; x++)
            for (int y = 0; y < copy.Size.Height; y++)
            
                Color c = copy.GetPixel(x, y);
                Boolean flag = false;
                foreach (var entry3 in mapping)
                
                    if (c.R == entry3.Key.R && c.G == entry3.Key.G && c.B == entry3.Key.B)
                    
                        copy.SetPixel(x, y, entry3.Value);
                        flag = true;
                    
                    if (flag == true)
                        break;

                
            
pictureBox2.Image=copy;

【问题讨论】:

我刚试过这个,效果很好。究竟是什么问题?图像没有被渲染?我注意到您的帖子中没有实际呈现图像的代码,例如 OnPaint 事件中的e.Graphics.DrawImage(copy, new Point(0, 0)) 问题出在@BrettWolfington 的结果上,当我计算结果图像的颜色时,它应该是 10 种颜色,但事实并非如此,我该如何进行渲染? 您之前的评论被截断了,但如果问题是颜色比应有的多或少,则问题可能出在量化算法上,而不是映射代码。你能在输入映射算法之前发布那个代码和映射字典的内容吗? 我已经在@BrettWolfington 上面发布了完整的代码 我刚刚在 100x100 图像上测试了这个。它正确地将颜色数量从 15,273 减少到 10。您可以发布您正在使用的输入图像吗? 【参考方案1】:

你的代码有两个问题:

太慢了 量化不是我所期望的。

这是一个原始图像,是您的代码的结果以及 Photoshop 在要求减少到 10 种颜色时所做的:

加速代码可以分两步完成:

摆脱最讨厌的浪费时间 将GetPixelSetPixel 循环转换为Lockbits 循环。

这是第一步的解决方案,它可以将代码速度至少提高 100 倍:

Bitmap bm = (Bitmap)Bitmap.FromFile("d:\\ImgA_VGA.png");
pictureBox1.Image = bm;

Dictionary<Color, int> histo = new Dictionary<Color, int>();
for (int x = 0; x < bm.Size.Width; x++)
    for (int y = 0; y < bm.Size.Height; y++)
    
        Color c = bm.GetPixel(x, y);   // **1**
        if (histo.ContainsKey(c))  histo[c] = histo[c] + 1;
        else histo.Add(c, 1);
    
var result1 = histo.OrderByDescending(a => a.Value);
int number = 10;
var mostusedcolor = result1.Select(x => x.Key).Take(number).ToList();

Double temp;
Dictionary<Color, Double> dist = new Dictionary<Color, double>();
Dictionary<Color, Color> mapping = new Dictionary<Color, Color>();
foreach (var p in result1)

    dist.Clear();
    foreach (Color pp in mostusedcolor)
    
        temp = Math.Abs(p.Key.R - pp.R) + 
               Math.Abs(p.Key.R - pp.R) + 
               Math.Abs(p.Key.R - pp.R);
        dist.Add(pp, temp);
    
    var min = dist.OrderBy(k => k.Value).FirstOrDefault();
    mapping.Add(p.Key, min.Key);

Bitmap copy = new Bitmap(bm);

for (int x = 0; x < copy.Size.Width; x++)
    for (int y = 0; y < copy.Size.Height; y++)
    
        Color c = copy.GetPixel(x, y);   // **2**
        copy.SetPixel(x, y, mapping[c]);
    
pictureBox2.Image = copy;

请注意,如果我们只想对颜色进行排序,则无需使用毕达哥拉斯的全部力量来计算距离。 Manhattan distance 就可以了。

还请注意,我们已经有了查找字典mapping,其中包含图像中的每种颜色作为其键,因此我们可以直接访问这些值。 (这是迄今为止最糟糕的浪费时间..)

测试图像在 ~1s 内处理,所以我什至不去修改LockBits..

但是纠正量化并不是那么简单,恐怕而且 imo 超出了一个好的 SO 问题的范围。

但是让我们看看出了什么问题:查看结果,我们一眼就能看出:有很多天空,所有这些许多蓝色像素都有超过 10 种色调等等前 10 名中的颜色是蓝色。

所以整个图像没有其他色调了

要解决这个问题,您最好研究一下common quantization algorithms..

修复代码的一种简单方法是丢弃/映射最常用列表中与您已有的任何一种颜色太接近的所有颜色。但是找到最佳的最小距离需要体细胞数据分析..

更新 改进代码的另一种非常简单的方法是用一些低位掩盖真实颜色,以将相似的颜色映射在一起。仅选择 10 种颜色仍然太少,但改进非常明显,即使对于此测试图像:

Color cutOff(Color c, byte mask)
  return Color.FromArgb(255, c.R & mask, c.G & mask, c.B & mask );   

在此处插入 (1):

byte mask = (byte)255 << 5 & 0xff;  // values of 3-5 worked best
Color c = cutOff(bm.GetPixel(x, y), mask);

这里(2):

Color c = cutOff(copy.GetPixel(x, y), mask);

我们得到:

仍然缺少所有黄色、橙色或棕色的色调,但只增加了一条线,这是一个很好的改进..

【讨论】:

非常感谢您的帮助我非常感谢您的帮助,但是结果只有十种颜色吗? 根据 IrfanView 它只有 7 种不同的颜色。 Photoshop 直方图显示 9 种颜色。也不知道为什么会这样..如果您有疑问,您可以随时在其上运行一段代码..顺便说一句,这将是一个很好的机会来分解Dictionary&lt;Color, in&gt; getHistgramm(Bitmap) 函数;-) 再次感谢您的回答,但是我不明白您上面提到的功能如何使用? 我已经标记了这两行以替换为 1/2。它清除了一些例如每个颜色通道的低 5 位.. 你说“如果你有疑问,你总是可以在它上面运行一段代码..顺便说一句,这将是一个很好的机会来分解 Dictionary getHistgramm(Bitmap) 函数”我不明白你所说的 gethitgramm(Bitmap) 函数是什么意思?

以上是关于量化(减少图像的颜色)的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 OpenCV 减少图像中的颜色数量?

图像的准确颜色量化以最小化调色板

“颜色量化”是 OpenCV 中颜色量化的正确名称吗?

图像量化与量子算法问题

Python学习笔记 - 使用Python进行图像颜色量化

ARCGIS图像识别(矢量化)-栅格转面实际应用