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

mpe*_*pen 7 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;
}
Run Code Online (Sandbox Code Playgroud)

现在,我想计算像素的平均"方向".

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

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

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

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

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


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

Nik*_*iki 9

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

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

一组角度的平均值没有明确的含义:例如,一个角度指向上方的平均值和一个指向下方的角度的平均值是指向右侧的角度.那是你要的吗?假设"向上"是+ 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]"]
  )
Run Code Online (Sandbox Code Playgroud)

样本图像的结果:

在此输入图像描述

角度直方图还显示了为什么平均角度不起作用:直方图基本上是单个尖峰,其他角度大致均匀.该直方图的平均值将始终由均匀的"背景噪声"支配.这就是为什么你使用当前算法为每个"真实"图像获得几乎相同的角度(大约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;
   }
}
Run Code Online (Sandbox Code Playgroud)

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