量化(减少图像的颜色)
quantization (Reduction of colors of image)
我试图在 C# 中将图像量化为 10 种颜色,但我在绘制量化图像时遇到问题,我已经制作了映射 table 并且它是正确的,我复制了原始图像,我正在根据映射 table 更改像素的颜色,我正在使用以下代码:
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;
您的代码有两个问题:
- 速度慢得要命
- 量化不是我所期望的。
这是一张原始图像、您的代码的结果以及 Photoshop 在要求减少到 10 种颜色时所做的操作:
加速代码可以分两步完成:
- 摆脱最讨厌的时间浪费者
- 将
GetPixel
和 SetPixel
循环变成 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
修改..
但是校正量化并不是那么简单,我恐怕超出了一个很好的 SO 问题的范围。
但让我们看看哪里出了问题:看结果我们一眼就能看出:有很多天空,所有那么多蓝色像素都超过 10色调,因此前 10 名列表中的所有颜色都是蓝色。
因此整个图像 没有其他色调!
要解决这个问题,您最好研究 common quantization algorithms..
修复代码的一种简单方法是 discard/map 将 most-used-list 中与您已有的颜色太接近的所有颜色放在一起。但是找到最佳的最小距离需要体细胞数据分析..
Update 改进代码的另一种非常简单的方法是通过一些较低的位来掩盖真实颜色,以将相似的颜色映射在一起。只选择 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);
我们得到:
仍然缺少所有黄色、橙色或棕色色调,但只增加了一条线,这是一个不错的改进..
我试图在 C# 中将图像量化为 10 种颜色,但我在绘制量化图像时遇到问题,我已经制作了映射 table 并且它是正确的,我复制了原始图像,我正在根据映射 table 更改像素的颜色,我正在使用以下代码:
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;
您的代码有两个问题:
- 速度慢得要命
- 量化不是我所期望的。
这是一张原始图像、您的代码的结果以及 Photoshop 在要求减少到 10 种颜色时所做的操作:
加速代码可以分两步完成:
- 摆脱最讨厌的时间浪费者
- 将
GetPixel
和SetPixel
循环变成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
修改..
但是校正量化并不是那么简单,我恐怕超出了一个很好的 SO 问题的范围。
但让我们看看哪里出了问题:看结果我们一眼就能看出:有很多天空,所有那么多蓝色像素都超过 10色调,因此前 10 名列表中的所有颜色都是蓝色。
因此整个图像 没有其他色调!
要解决这个问题,您最好研究 common quantization algorithms..
修复代码的一种简单方法是 discard/map 将 most-used-list 中与您已有的颜色太接近的所有颜色放在一起。但是找到最佳的最小距离需要体细胞数据分析..
Update 改进代码的另一种非常简单的方法是通过一些较低的位来掩盖真实颜色,以将相似的颜色映射在一起。只选择 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);
我们得到:
仍然缺少所有黄色、橙色或棕色色调,但只增加了一条线,这是一个不错的改进..