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来计算它,但我不太确定如何。看起来这个功能可能是我想要的;我可以将它转换为灰度(如上),然后用灰度矩阵做一些事情来计算这些方向吗?

IIRC,我想要的与边缘检测非常相似。

在计算完这些方向向量之后,我将遍历它们并计算图像的平均方向。

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


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

4

4 回答 4

9

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

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

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

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

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

这是我汇总的概念验证 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]"]
  )

带有示例图像的结果:

在此处输入图像描述

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

树图像有一个单一的主导角度(地平线),所以在这种情况下,您可以使用直方图的模式(最常见的角度)。但这不适用于每张图片:

在此处输入图像描述

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

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

在此处输入图像描述

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

注意:我没有对图像进行预处理,我没有尝试不同比例的梯度算子,我没有对生成的直方图进行后处理。在现实世界的应用程序中,您将调整所有这些东西以获得用于大量测试图像的最佳算法。这只是一个快速测试,看看这个想法是否可行。

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

  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,可能没那么重要)

于 2012-04-16T16:28:53.627 回答
1

好吧,我可以给你另一种方法。虽然不会很漂亮,但希望它对你有用。

您的计算可能没问题。只是梯度一旦平均值最终会出现与您期望的不同的平均值。因此,我怀疑通过查看图像,您会觉得其中一定有不同的平均角度。所以;

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

作为另一种方法。试试GIST这基本上是场景识别中使用最广泛的一种实现。我发现您的图像是真实的场景,因此我建议采用这种方法。此方法将为您提供一个向量,您可以将其与同一图像的不同方向向量进行比较。这是一种众所周知的技术,绝对应该适用于您的情况。

于 2012-04-16T07:31:55.110 回答
0

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

于 2012-04-16T06:24:59.870 回答
0

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

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

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

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

要计算高斯核,将 2D 高斯函数(从正态分布已知)与 1d 变量 '(x-0)^2' 用 (x^2 + y^2) 替换一次进行微分。您可以在 2D 中绘制它,例如在 MS Excel 中。

祝你好运!

迈克尔

于 2012-05-23T21:45:58.400 回答