我正在尝试使用C#将图像量化为10种颜色,绘制量化图像时遇到问题,我制作了映射表,并且它是正确的,我制作了原始图像的副本,并且正在更改基于映射表的像素,我正在使用以下代码:
bm = new Bitmap(pictureBox1.Image); Dictionaryhisto = new Dictionary (); 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 mostusedcolor = new List (); foreach (var entry in result1) { if (ind < 10) { mostusedcolor.Add(entry.Key); ind++; } else break; } Double temp_red,temp_green,temp_blue,temp; Dictionary dist = new Dictionary (); Dictionary mapping = new Dictionary (); 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;
TaW.. 5
您的代码有两个问题:
它非常慢
量化不是我所期望的。
这是原始图像,代码结果以及当要求减少为10种颜色时Photoshop的操作:
加快代码速度可以通过两个步骤完成:
摆脱最讨厌的时间浪费者
将GetPixel
和SetPixel
循环变成Lockbits
循环。
这是第一步的解决方案,可以将代码加速至少100倍:
Bitmap bm = (Bitmap)Bitmap.FromFile("d:\\ImgA_VGA.png"); pictureBox1.Image = bm; Dictionaryhisto = new Dictionary (); 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 dist = new Dictionary (); Dictionary mapping = new Dictionary (); 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;
请注意,如果我们只想订购颜色,则无需用毕达哥拉斯的全部力来计算距离。在曼哈顿距离会做得很好。
还要注意,我们已经有了lookup字典mapping
,该字典包含图像中的每种颜色作为其键,因此我们可以直接访问这些值。(这是迄今为止最糟糕的时间浪费。)
测试图像的处理时间约为1秒,因此我什至不去进行LockBits
修改。
但是,恐怕校正量化并非那么简单,而且imo超出了一个好的SO问题的范围。
但是,让我们看一下出了什么问题:看看结果,我们乍看之下几乎可以看到:天空很多,所有许多蓝色像素的色调都超过10,因此前10位的所有颜色清单是蓝色的。
因此,整个图像没有其他色调!
要变通解决此问题,您最好研究常用的量化算法 ..
修复代码的一种简单方法是丢弃/映射最常用列表中的所有颜色,这些颜色与您已经拥有的任何一种颜色都太近了。但是要找到最佳的最小距离,则需要进行体数据分析。
更新改进代码的另一种非常简单的方法是,将真实颜色遮盖一些较低的位,以将相似的颜色映射在一起。仅选择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);
我们得到:
仍然缺少所有黄色,橙色或棕色的色相,但是仅增加了一条附加线就可以很好地改善它。
您的代码有两个问题:
它非常慢
量化不是我所期望的。
这是原始图像,代码结果以及当要求减少为10种颜色时Photoshop的操作:
加快代码速度可以通过两个步骤完成:
摆脱最讨厌的时间浪费者
将GetPixel
和SetPixel
循环变成Lockbits
循环。
这是第一步的解决方案,可以将代码加速至少100倍:
Bitmap bm = (Bitmap)Bitmap.FromFile("d:\\ImgA_VGA.png"); pictureBox1.Image = bm; Dictionaryhisto = new Dictionary (); 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 dist = new Dictionary (); Dictionary mapping = new Dictionary (); 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;
请注意,如果我们只想订购颜色,则无需用毕达哥拉斯的全部力来计算距离。在曼哈顿距离会做得很好。
还要注意,我们已经有了lookup字典mapping
,该字典包含图像中的每种颜色作为其键,因此我们可以直接访问这些值。(这是迄今为止最糟糕的时间浪费。)
测试图像的处理时间约为1秒,因此我什至不去进行LockBits
修改。
但是,恐怕校正量化并非那么简单,而且imo超出了一个好的SO问题的范围。
但是,让我们看一下出了什么问题:看看结果,我们乍看之下几乎可以看到:天空很多,所有许多蓝色像素的色调都超过10,因此前10位的所有颜色清单是蓝色的。
因此,整个图像没有其他色调!
要变通解决此问题,您最好研究常用的量化算法 ..
修复代码的一种简单方法是丢弃/映射最常用列表中的所有颜色,这些颜色与您已经拥有的任何一种颜色都太近了。但是要找到最佳的最小距离,则需要进行体数据分析。
更新改进代码的另一种非常简单的方法是,将真实颜色遮盖一些较低的位,以将相似的颜色映射在一起。仅选择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);
我们得到:
仍然缺少所有黄色,橙色或棕色的色相,但是仅增加了一条附加线就可以很好地改善它。