如何有效地计算灰度图像中像素的平均“方向”?

时间:2012-04-15 23:46:10

标签: c# image image-processing

所以我发现我可以像这样将图像转换为灰度:

public static Bitmap GrayScale(this Image img)
{
    var bmp = new Bitmap(img.Width, img.Height);
    using(var g = Graphics.FromImage(bmp))
    {
        var colorMatrix = new ColorMatrix(
            new[]
                {
                    new[] {.30f, .30f, .30f, 0, 0},
                    new[] {.59f, .59f, .59f, 0, 0},
                    new[] {.11f, .11f, .11f, 0, 0},
                    new[] {0, 0, 0, 1.0f, 0},
                    new[] {0, 0, 0, 0, 1.0f}
                });

        using(var attrs = new ImageAttributes())
        {
            attrs.SetColorMatrix(colorMatrix);
            g.DrawImage(img, new Rectangle(0, 0, img.Width, img.Height),
                0, 0, img.Width, img.Height, GraphicsUnit.Pixel, attrs);
        }
    }
    return bmp;
}

现在,我想计算像素的平均“方向”。

我的意思是我想要看一个3x3区域,然后如果左侧比右侧更暗,那么方向将是右侧,如果底部比顶部更暗然后方向会向上,如果左下方比右上方暗,那么方向将是右上方。 (想想每个3x3区域的小矢量箭头)。也许更好的例子是如果你在photoshop中绘制灰度渐变,并且你想要计算它们绘制的角度。

我做过像MatLab这样的东西,但那是几年前的事。我想我可以使用类似于ColorMatrix的矩阵来计算它,但我不太确定如何。看起来this function可能是我想要的;我可以将它转换为灰度(如上所述)然后用灰度矩阵做一些事情来计算这些方向吗?

IIRC,我想要的与edge detection非常相似。

在我计算出这些方向向量之后,我只是在它们上面循环并计算图像的平均方向。

最终目标是我想要旋转图像,使其平均方向始终向上;这样,如果我有两个相同的图像,除了一个旋转(90,180或270度),它们将以相同的方式结束(我不担心一个人是否倒置)。


* snip * 删除一些垃圾邮件。您可以查看您想要阅读其余尝试的修订版。

4 个答案:

答案 0 :(得分:9)

计算角度的平均值通常是一个坏主意:

...
        sum += Math.Atan2(yi, xi);
    }
}
double avg = sum / (img.Width * img.Height);

一组角度的平均值没有明确的含义:例如,一个角度指向上方的平均值和一个指向下方的角度的平均值是指向右侧的角度。那是你要的吗?假设“up”是+ PI,那么两个角度几乎指向上方的平均值将是指向下的角度,如果一个角度是PI- [某个小值],另一个-PI + [一些小]值]。这可能不是你想要的。此外,您完全忽略了边缘的强度 - 现实生活中的图像中的大多数像素根本不是边缘,因此渐变方向主要是噪声。

如果你想计算像“平均方向”这样的东西,你需要加上向量而不是角度,然后在循环后计算Atan2。问题是:该向量和不会告诉您图像内部的对象,因为指向相反方向的渐变会相互抵消。它只会告诉您有关图像的第一行/最后一行和第一列/最后一列之间亮度差异的信息。这可能不是你想要的。

我认为定向图像的最简单方法是创建角度直方图:创建一个带有(例如)360个区域的数组,用于360°的渐变方向。然后计算每个像素的梯度角和大小。将每个渐变幅度添加到右角度框中。这不会给你一个角度,而是一个角度直方图,然后可以使用简单的循环相关来将两个图像相互定向。

这是一个概念验证Mathematica实现,我把它们放在一起,看看这是否有效:

angleHistogram[src_] :=
 (
  Lx = GaussianFilter[ImageData[src], 2, {0, 1}];
  Ly = GaussianFilter[ImageData[src], 2, {1, 0}];
  angleAndOrientation = 
   MapThread[{Round[ArcTan[#1, #2]*180/\[Pi]], 
      Sqrt[#1^2 + #2^2]} &, {Lx, Ly}, 2];
  angleAndOrientationFlat = Flatten[angleAndOrientation, 1];
  bins = BinLists[angleAndOrientationFlat , 1, 5];
  histogram = 
   Total /@ Flatten[bins[[All, All, All, 2]], {{1}, {2, 3}}];
  maxIndex = Position[histogram, Max[histogram]][[1, 1]];
  Labeled[
   Show[
    ListLinePlot[histogram, PlotRange -> All],
    Graphics[{Red, Point[{maxIndex, histogram[[maxIndex]]}]}]
    ], "Maximum at " <> ToString[maxIndex] <> "\[Degree]"]
  )

样本图像的结果:

enter image description here

角度直方图还显示了平均角度无法工作的原因:直方图基本上是一个尖锐的峰值,其他角度大致是均匀的。该直方图的平均值将始终由均匀的“背景噪声”支配。这就是为什么你使用当前算法为每个“真实”图像获得几乎相同的角度(大约180°)。

树形图像具有单个支配角度(地平线),因此在这种情况下,您可以使用直方图的模式(最常见的角度)。但这对每张图片都不起作用:

enter image description here

这里有两个峰值。循环相关仍然应该将两个图像相互定向,但仅仅使用该模式可能是不够的。

另请注意,角度直方图中的峰值不是“向上”:在上面的树形图中,角度直方图中的峰值可能是地平线。所以它指向了。在Lena图像中,它是背景中的垂直白条 - 所以它指向右边。简单地使用最常见的角度定向图像将转动每个图像,右侧朝上。

enter image description here

此图像具有更多峰值:使用模式(或可能是任何单个角度)定位此图像将不可靠。但角度直方图作为一个整体应该仍然可以给你一个可靠的方向。

注意:我没有预先处理图像,我没有尝试不同尺度的渐变算子,我没有对得到的直方图进行后期处理。在实际应用程序中,您可以调整所有这些内容,以便为大量测试图像获得最佳算法。这只是一个快速测试,看看这个想法是否可行。

添加:要使用此直方图定位两个图像,您需要

  1. 对所有直方图进行归一化,因此直方图下的面积对于每个图像都是相同的(即使某些图像更亮,更暗或更模糊)
  2. 获取图像的直方图,并针对您感兴趣的每个轮换进行比较:
  3. 例如,在C#中:

    for (int rotationAngle = 0; rotationAngle < 360; rotationAngle++)
    {
       int difference = 0;
       for (int i = 0; i < 360; i++)
          difference += Math.Abs(histogram1[i] - histogram2[(i+rotationAngle) % 360]);
       if (difference < bestDifferenceSoFar)
       {
          bestDifferenceSoFar = difference;
          foundRotation = rotationAngle;
       }
    }
    

    (如果你的直方图长度是2的幂,你可以使用FFT来提高速度。但代码会复杂得多,对于256个分区,它可能并不重要)

答案 1 :(得分:1)

好吧,我可以给你另一种方式。虽然不会很漂亮,但希望它适合你。

可能你的计算没问题。只是平均值的平均值最终会以不同于您预期的平均值结束。所以我怀疑通过观察图像你觉得它必须有不同的平均角度。因此;

  • 将图像转换为二进制。
  • 使用hough变换查找行
  • 取最长的线并计算其角度。这应该给你最突出的角度。
  • 您可能需要进行一些前置/后置处理才能使线路正确。

还有一种方法。试试GIST这基本上是在场景识别中使用最广泛的实现。我发现你的图像是真实的scenes,因此我建议采用这种方法。此方法将为您提供一个矢量,您可以将该矢量与同一图像的不同方向矢量进行比较。这是一个非常了解的技术,绝对适用于您的情况。

答案 2 :(得分:0)

考虑使用图像的渐变来计算所需的方向:en.wikipedia.org/wiki/Image_gradient

答案 3 :(得分:0)

您需要使用两个高斯导数内核(一个在X中,一个在Y中)对图像进行卷积。 这实际上是上面答案中的Lx和Ly。

在计算滑动窗口(原始图像的子图像)和一阶高斯导数函数之间的总和乘积之前,先预测平均像素强度。

参见本教程的示例: http://bmia.bmt.tue.nl/people/bromeny/MICCAI2008/Materials/05%20Gaussian%20derivatives%20MMA6.pdf

选择最佳平滑因子sigma&gt; = 1。

为了计算高斯核,一次将2D高斯函数(从正态分布中得知)与用(x ^ 2 + y ^ 2)代替的1d变量'(x-0)^ 2'区分开。您可以在2D中绘制它,例如在MS Excel中绘制。

祝你好运!

迈克尔