如何在2d数组中找到最常见的int?

时间:2009-01-29 18:54:33

标签: c# paint.net

好的,所以我刚开始考虑如何为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。对于大图像中的每个像素,结果将计算一次......因此更快更好。 ;)

8 个答案:

答案 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可以减少你的大量扫描。例如,在您的示例中,右上角对公共值很重要。如果这可以通过某种启发式确定,则可以以某种方式从右上角扫描到左下角。如果所需的扫描模式很复杂,请预先生成它。