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

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可能是我想要的;我能将其转换为灰度(如上所述),然后对灰度矩阵进行某些操作以计算这些方向吗?
如果我没记错的话,我想要的与edge detection非常相似。
在计算出这些方向向量之后,我将循环遍历它们并计算图像的平均方向。最终目标是旋转图像,使其平均方向始终朝上;这样如果我有两个完全相同的图像,只是一个被旋转了(90度,180度或270度),它们最终会被定向在同一方向上(我不关心人是否倒置)。
*snip* 删除一些垃圾邮件。如果您想阅读我的其他尝试,请查看修订版本。

我认为,如果你计算每个图像块与坐标(例如meshgrid)的相关性,你应该能够得到你的“方向”。此外,我怀疑平均瓦片方向将会给出与将整个图像作为单个瓦片进行相关的相同答案。如果你仍然有MatLab,我建议你使用它来测试你的算法,然后将最终版本移植到C#。 - Ben Voigt
@BenVoigt:什么是网格?如果我知道该怎么做,我很乐意在整个图像上计算方向。我不再拥有MatLab的副本了……我只在大学用了几个学期;我不确定我还记得如何使用它。这需要一种不同的思考方式。 - mpen
@rcompton:你具体尝试了什么? - mpen
@Mark:使用图像的梯度来计算所需方向。你在帖子中描述的是这个:http://en.wikipedia.org/wiki/Image_gradient,对吗? - dranxo
@Mark,你肯定需要一张“参考”图像来比较和判断图像是否旋转。 - user349026
显示剩余5条评论
4个回答

9

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

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

一组角度的平均值没有明确的含义:例如,一个向上的角度和一个向下的角度的平均值是指向右的角度。这就是你想要的吗?假设“向上”是+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. 获取图像的直方图,并针对您感兴趣的每个旋转进行比较:

例如,在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来说,可能并不重要)

非常好的答案;感谢您抽出时间编写和测试。我如何使用整个直方图来明确决定方向?您最后的示例看起来非常准确;最大值恰好相差90度,尽管我可以看出如果峰值稍微不同,它可能会选择不同的峰值。 - mpen
@Mark:最大值相隔正好90度,因为它是完全相同的图像,旋转90度,所以梯度具有完全相同的大小并且旋转正好90度。如果您调整图像大小或使用有损压缩进行压缩,则模式可能会成为不同的峰值。 - Niki
是的,这就是我的观点——它们完全正确,甚至没有一点偏差。我已经进行了一些实验,只是将图像旋转90度,答案总是正确的。我希望今天稍后尝试使用有损JPEG压缩和调整大小。 - mpen
我对我的图像运行了更多测试,运行各种Photoshop滤镜,以查看它们是否以相同的方式对齐。我尝试将图像缩小到10%,并以0/12 jpeg质量保存,自动平衡颜色,去饱和颜色,超级饱和颜色,旋转色调,高斯模糊图像并锐化它。唯一失败的情况是超级饱和度。通过超级饱和度,我的意思是边缘变得坚硬和明亮的蓝色或绿色。否则,在实践中似乎非常有效。 - mpen
虽然我成功地用一些调整大小和高斯模糊来修复了这些情况。 - mpen

1

我可以给你另一种方法来完成它。虽然不太美观,但希望对你有用。

很可能你的计算是正确的。只是平均梯度最终会得到一个与你期望不同的平均值。因此,我怀疑通过查看图像,你会感觉其中必须存在一个不同的平均角度。因此:

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

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


我认为这个问题可能会和nikie提到的一样:如果没有主导线怎么办? - mpen
那么就不会有明确的方向。这就是为什么你需要一个参考图像来找到方向。 - user349026

0

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

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

例如,可以参考此教程: http://bmia.bmt.tue.nl/people/bromeny/MICCAI2008/Materials/05%20Gaussian%20derivatives%20MMA6.pdf

选择最佳平滑因子sigma >= 1。

要计算高斯核,请将一维变量“(x-0) ^ 2”替换为(x ^ 2 + y ^ 2)后,对二维高斯函数(从正态分布中得知)进行一次微分。例如,在MS Excel中可以绘制它的二维图形。

祝好运!

迈克尔


0

2
太好了,但你基本上只是给了我一个名称来描述我所说的内容。在实现方面仍然有困难。 - mpen
平均所有角度时会遇到一个实现难度。梯度需要输入图像然后输出向量场。在某些地方(例如您的图像中的黑色区域),梯度将为零,平均将产生意外的结果。考虑改用众数或中位数。顺便问一下,这是做什么用的?听起来你的最终目标是图像配准,这非常不容易,参考 http://l3.lcarrasco.com/2010/04/image-registration/ - dranxo
不,我没有那么疯狂。我只是想将相同的图像定向为相同的90度方向。例如,用数字相机拍摄的照片经常会横着拍摄,有时它们也会以这种方式上传到网上。给定两个在其他方面相同的图像,我希望将它们定向为相同的方向,以便确定它们是否为相同的图像。我知道可以将图像旋转几次并进行多次比较,但那会太慢了。我想预处理这些图像并将它们对齐到一致的方向,以加快将来的比较速度。 - mpen
两张图像之间的角度。看起来这个人对此有一个很好的解决方案,非常接近你现在正在做的事情,但是有很多细节:http://www.mathworks.com/matlabcentral/newsreader/view_thread/248242 它是用matlab编写的,但这是图像处理中原型设计惯用的标准。 - dranxo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接