c# - 如何有效地计算灰度图像中像素的平均 "direction"?

标签 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 度),它们最终会以相同的方式定向(我不担心一个人最终是否颠倒)。

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

最佳答案

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

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

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

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

我认为定位图像的最简单方法是创建一个角度直方图:创建一个具有(例如)360 个 bin 的数组,用于 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

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

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

地址:要使用此直方图定向两个图像,您需要
  • 标准化所有直方图,因此直方图下方的区域对于每个图像都是相同的(即使有些更亮、更暗或更模糊)
  • 获取图像的直方图,并针对您感兴趣的每个旋转比较它们:

  • 例如,在 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,它可能无关紧要)

    关于c# - 如何有效地计算灰度图像中像素的平均 "direction"?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10167183/

    相关文章:

    c# 并行运行 selenium 浏览器

    html - 文本鼠标悬停触发的基于 CSS 的横幅图像交换

    javascript - 如何使用javascript onload 下载多个图像?

    php - 如何检查上传的文件是否是没有 mime 类型的图像?

    c# - 如何使用 Entity Framework 更新自引用图?

    c# - WPF DataGrid 上的拖放行行为

    c++ - 使用 C++ 获取 vector<cv::Vec3b> 中出现次数最多的值

    python - 在 python 中处理 tiff 文件

    c# - 添加服务引用总是生成 xmlserializer 而不是 DataContractSerializer

    java - 如何使用 LWJGL 将 BufferedImage 绘制到屏幕上?