好的,所以我刚开始考虑如何为Paint.NET实现一个新的图形插件,我需要知道如何在2d整数数组中找到最常见的整数。是否有内置的C#方式来做到这一点?或者,有没有人有一个光滑的方式去做?
数组看起来像这样:
300 300 300 300 300 300 300
0 150 300 300 300 300 300
0 0 150 300 300 300 300
0 0 0 0 300 300 300
0 0 0 0 150 300 300
0 0 0 0 0 150 300
0 0 0 0 0 0 300
我需要知道300是数组中最常见的数字。如果没有“最常见”,那么只返回中心数字(数组减少将始终为奇数x奇数)0。
我将使用“强力”算法实现这一点,除非你的专家可以更快地拿出一些东西。
非常感谢任何帮助。
谢谢!
编辑:更多信息......
这些值几乎总是非常多样化(比我的示例数组更加多样化)。值将在0-360的范围内。根据算法的速度,阵列的大小将是5x5到大约17x17。对于大图像中的每个像素,结果将计算一次......因此更快更好。 ;)
答案 0 :(得分:6)
你切片的时间至少为O(n * m) - 你必须至少看一次每个细胞一次。节约的地方是在寻找最常见之前累积每个值的计数;如果你的整数在相对较小的范围内变化(它们是uint16,那么就说),那么你可以简单地使用平面数组而不是地图。
我猜你也可以保留当前排名第二和最接近“最常见”和早期候选人的 x , y 的运行计数你只剩下少于(n * m) - (xy)的细胞,因为那时亚军没有办法超过最佳候选人。
像这样的整数运算非常快;即使对于百万像素图像,强力算法也应该只需要几毫秒。
我注意到你已经编辑了你的原始问题,说像素值从0..255 - 在这种情况下,肯定是一个简单的平面阵列;它足够小,可以轻松放入l1 dcache中,并且可以快速查找平面阵列中的查找。
[编辑]:一旦你建立了直方图数组,处理“没有最常见的数字”的情况非常简单:所有你要做的就是通过它来找到“最常见的数字” “和”第二大“常见数字;如果它们同样频繁,那么根据定义,没有一个最常见的。
const int numLevels = 360; // you said each cell contains a number [0..360)
int levelFrequencyCounts[numLevels]; // assume this has been populated such that levelFrequencyCounts[i] = number of cells containing "i"
int mostCommon = 0, runnerUp = 0;
for (int i = 1 ; i < numLevels ; ++i)
{
if ( levelFrequencyCounts[i] > levelFrequencyCounts[mostCommon] )
{
runnnerUp = mostCommon;
mostCommon = i;
}
}
if ( levelFrequencyCounts[mostCommon] != levelFrequencyCounts[runnerUp] )
{
return mostCommon;
}
else
{
return CenterOfInputData; // (something like InputData[n/2][m/2])
}
答案 1 :(得分:3)
我如何在C#中做这样的事情?
这样的事情:
Dictionary<int, int> d = new Dictionary<int, int>();
foreach (int value in matrix)
{
if (!d.ContainsKey(value))
d.Add(value, 1);
else
d[value] = d[value] + 1;
}
KeyValuePair<int, int> biggest = null;
foreach (KeyValuePair<int, int> found in d)
{
if ((biggest == null) || (biggest.Value < found.Value))
biggest = found;
}
答案 2 :(得分:1)
一个选项是LINQ - 效率有点低,但对于非大型数组来说还可以:
var max = (from cell in data.Cast<int>()
group cell by cell into grp
select new { Key = grp.Key, Count = grp.Count() } into agg
orderby agg.Count descending
select agg).First();
Console.WriteLine(max.Key + ": " + max.Count);
或者是锯齿状阵列:
var max = (from row in data
from cell in row
group cell by cell into grp
select new {Key = grp.Key, Count = grp.Count()} into agg
orderby agg.Count descending
select agg).First();
Console.WriteLine(max.Key + ": " + max.Count);
实际上,我可能会使用字典/计数。这个例子没有LINQ,只是“因为”:
Dictionary<int, int> counts = new Dictionary<int, int>();
foreach (int value in data)
{
int count;
counts.TryGetValue(value, out count);
counts[value] = count + 1;
}
int maxCount = -1, maxValue = 0;
foreach (KeyValuePair<int, int> pair in counts)
{
if (pair.Value > maxCount)
{
maxCount = pair.Value;
maxValue = pair.Key;
}
}
Console.WriteLine(maxCount + ": " + maxValue);
答案 3 :(得分:1)
如果速度是您主要关心的问题,请不要使用字典。坚持使用一个字节数组。试试这个:
// stores hit counts (0-360)
short[] hitCounts = new short[361];
// iterate through 2d array and increment hit counts
for (int i = 0; i < toEvaluate.Length; i++)
{
for (int j = 0; j < toEvaluate[i].Length; j++)
hitCounts[toEvaluate[i][j]]++;
}
int greatestHitCount = 0; // the hit count of the current greatest value
int greatest = -1; // the current greatest valeu
// iterate through values (0-360) and evalute hit counts
for (int i = 0; i < hitCounts.Length; i++)
{
// the hit count of hitCounts[i] is higher than the current greatest hit count value
if (hitCounts[i] > greatestHitCount)
{
greatestHitCount = vals[i]; // store the new hit count
greatest = i; // store the greatest value
}
// there is already a value with the same hit count (which is the greatest)
else if (hitCounts[i] == greatestHitCount)
greatest = -1; // there are more than one value, we can't use this if it ends up being the greatest
}
if (greatest >= 0) // no greatest value found
return greatest;
// figure out the middle x and y value
int x = (toEvaluate.Length - 1) / 2 + 1;
int y = (toEvaluate[x].Length - 1) / 2 + 1;
// return the value at the center of the 2d array as the value
return toEvaluate[x][y];
当速度成为可读性问题时,最终必然会出现丑陋的代码。以上肯定会受益于重构(因此过度评论),但它应该快速运行。如果速度不够快,可以通过将其移动到非托管代码来获得更多优化。
答案 4 :(得分:1)
你的形象:
300+ 300+ 300+ 300 300 300 300
0+ 150+ 300+ 300 300 300 300
0+ 0+ 150+ 300 300 300 300
0 0 0 0 300 300 300
0 0 0 0 150 300 300
0 0 0 0 0 150 300
0 0 0 0 0 0 300
标记(+)数字是您的窗口。 w,h是你的窗户尺寸。应用bucket sorting(正如其他人建议的那样,因为您的价值范围非常有限)。不要像Crashworks建议的那样将评估中途削减一半。不要扔掉你的结果。这是第一步。
300- 300- 300- 300 300 300 300
0. 150. 300. 300 300 300 300
0. 0. 150. 300 300 300 300
0+ 0+ 0+ 0 300 300 300
0 0 0 0 150 300 300
0 0 0 0 0 150 300
0 0 0 0 0 0 300
移开你的窗口。而不是添加,减去您传递的最后一行/列中的存储桶并添加新存储桶。通过这种方式,您可以检查每个像素2(w + h)次,即当它穿过窗口边界时,而不是w * h次,即当该像素在窗口中时,在一个简单的实现中。
换句话说,你需要像这样移动你的窗口:
| ^->| ^
| | | |
| | | |
V->| V->|
我假设您正在尝试实现非线性卷积滤波器。
欢迎更正。
答案 5 :(得分:1)
看一下Paint.NET中的LocalHistogramEffect代码,特别是LocalHistorgramEffect.RenderRect。
我走过输入图像,为每个源像素保持一个强度直方图,其中包含目标像素的“r”像素。当遍历输出像素时,它将前沿添加到直方图并减去后沿。它可以很好地处理所有边缘情况,而且速度非常快。它是Median,Unfocus,Outline和Remove Noise效果的基础。
将其改为支持Hue而不是RGB强度将是相当微不足道的。
性能非常好,为了您的目的,它在O(r ^ 2 + w r + n w)中运行,其中r是半径,w是图像的宽度,和n是直方图中的级别数。
-tjackson
答案 6 :(得分:0)
int MaxValueIn2dArray(int[,] matrix)
{
var d = new int[360];
int MaxValue = 0;
for (int x = 0; x <= matrix.GetUpperBound(0); x++)
{
for (int y = 0; y <= matrix.GetUpperBound(1); y++)
{
d[matrix[x, y]]++;
}
}
foreach (int value in d)
{
if (value > MaxValue) MaxValue = value;
}
return MaxValue;
}
需要针对您的特定需求进行优化。
答案 7 :(得分:0)
所有我提供的是任何检查每个单元格的算法(这几乎是你期望做的)做两件额外的事情:
1。)确保例程在当前最常见值的计数时退出&gt; (M×N / 2)。如果您的网格上有大约50%的覆盖率,那么这是最常见的值,无需继续。如果您的例行程序只需要大部分时间,那么您可以降低百分比并将其视为启发式。您甚至可以运行一些分析,如果覆盖率> 37.6%,然后99.9%的时间它将是最常见的值,然后使用该百分比。
2。)如果有任何方法可以确定最常见的值可能在哪一侧,一角或一般位置(外边缘,中间等),则可以按顺序扫描上面的优化1可以减少你的大量扫描。例如,在您的示例中,右上角对公共值很重要。如果这可以通过某种启发式确定,则可以以某种方式从右上角扫描到左下角。如果所需的扫描模式很复杂,请预先生成它。