2012-04-15 182 views
7

所以我想通了,我可以将图像转换为灰度是这样的:如何高效地计算灰度图像中像素的平均“方向”?

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度),它们将以同样的方式结束(我不关心一个人是否颠倒过来)。


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

+0

我想如果你计算每个图像瓷砖与坐标(例如'meshgrid')的相关性,你应该得到你的“方向”。此外,我怀疑平铺方向的平均值会给出相同的答案,就好像您将整个图像关联为单个平铺一样。如果您还有MatLab,我会用它来测试您的算法,然后将最终版本移植到C#中。 – 2012-04-15 23:53:11

+0

@BenVoigt:什么是网格?如果我知道如何,我会高兴地计算整个图像的方向。我没有MatLab的副本了......我只用了几个学期的单元;我不确定我会记得如何使用它。需要不同的思考方式。 – mpen 2012-04-16 00:12:23

+0

所有你想要的声音都是渐变的。你试过了吗? – dranxo 2012-04-16 00:42:25

回答

9

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

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

一组角度的平均无明确的意义:例如,一个角均朝上,一个角度朝下是指向右方的角度。那是你要的吗?假设“向上”是+ PI,那么朝上的两个角度10几乎的平均值将是朝下的角度,如果一个角度是PI- [某个小值],则另一个-PI + [某个小值]。这可能不是你想要的。此外,您完全忽略了边缘的强度 - 实际图像中的大部分像素根本不是边缘,所以梯度方向主要是噪声。

如果你想计算类似“平均方向”的东西,你需要加上向量而不是角度,然后计算循环后的Atan2。问题是:这个向量和不会告诉你图像内部的物体,因为指向相反方向的梯度相互抵消。它只会告诉你关于图像的第一行/最后一行和第一/最后一列之间的亮度差异。这可能不是你想要的。

我认为定向图像最简单的方法是创建一个角度直方图:为360度梯度方向创建一个包含(例如)360个分档的数组。然后计算每个像素的梯度角度和幅度。将每个梯度大小添加到正确的角度箱。这不会给你一个单一的角度,而是一个角度直方图,然后可以使用角度直方图来使用简单的循环相关来将两个图像定向到彼此。

这里有一个验证的概念,数学实现我扔在一起,看看是否这会工作:

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. 拍摄的图像的直方图,并比较它们对您感兴趣的各旋转:

例如,在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个bin,这可能并不重要)

+0

优秀的答案;感谢您花时间编写和测试这个。我怎样才能将直方图作为一个整体来确定方向?你的最后一个例子看起来很漂亮。最大值*正好相差90度,尽管我可以看到如果最大值出现的方式稍有不同,它可能会选择不同的峰值。 – mpen 2012-04-17 01:46:13

+0

@Mark:最大值相差90度,因为它是完全相同的图像,旋转90度,所以梯度具有完全相同的大小,并精确旋转90度。如果您要调整图像大小或使用有损压缩进行压缩,则该模式可能会是另一个高峰。 – Niki 2012-04-17 06:13:51

+0

是的,那是我的观点 - 他们出来的*确切*正确,甚至没有一点关闭。我已经用旋转90度的图像进行了一些实验,答案总是正确的。我会尝试使用有损JPEG压缩,并希望今天晚些时候调整大小。 – mpen 2012-04-17 15:29:52

0

考虑使用图像的梯度来计算你想要的方向:en.wikipedia.org/wiki/Image_gradient

+2

这很棒,但你基本上只给了我一个我所描述的名字。仍然遇到实施麻烦。 – mpen 2012-04-16 06:40:15

+0

当你将所有角度平均在一起时,会出现一个实现难题。渐变需要一个图像,然后输出一个矢量场。在某些地方(例如,图像中的黑色区域),渐变将为0,并且平均会输出意想不到的东西。考虑使用模式或中位数。顺便说一句,这是为了什么?这听起来像你的最终目标是图像注册这是非常无辜cf http://l3.lcarrasco.com/2010/04/image-registration/ – dranxo 2012-04-16 07:15:29

+0

不,我不会那么疯狂。我只想在相同的90度方向上定向相同的图像。例如,用数码相机拍摄的照片通常会横向出现,有时也会以这种方式上传到网上。给定2个其他相同的图像,我想以相同的方式定位它们,以便我可以确定它们是否是相同的图像。我知道我可以旋转图像几次,并进行多次比较,但这太慢了。我想预先处理图像并将它们对齐到一致的方向,以加快未来的比较速度。 – mpen 2012-04-16 07:26:23

1

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

它可能你的计算是可以的。只是一次平均的梯度最终会以不同于你期望的平均值。所以我怀疑看着你觉得的图像必须有一个不同的平均角度。因此;

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

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

+0

我认为这将会产生与nikie相同的问题:如果没有主线,该怎么办? – mpen 2012-04-17 01:44:31

+0

然后将没有明确的方向。这就是为什么你需要一个参考图像来找到方向。 – 2012-04-17 01:48:50

+0

@Mark - 试试Gist吧。 – 2012-04-17 01:55:35

0

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

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

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

选择最佳平滑因子西格玛> = 1

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

祝你好运!

迈克尔