量化(减少图像颜色)

时间:2016-01-01 15:35:56

标签: c# quantization

我正在尝试将图像量化为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;

1 个答案:

答案 0 :(得分:4)

您的代码有两个问题:

  • 非常慢
  • 量化不是我所期望的。

这是一张原始图片,代码的结果以及Photoshop在被要求缩减为10种颜色时的作用:

enter image description here

  • 加快代码可分两步完成:

    • 摆脱最令人讨厌的浪费时间
    • 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色调,所以前十名单上的所有颜色都是蓝色。

    • 因此整个图片没有其他色调

    • 要解决这个问题,您最好学习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);

我们得到:

enter image description here

仍然缺少所有的黄色,橙色或棕色色调,但只有一条额外的线条,这是一个很好的改进。