【问题标题】:Unsafe Edge Detection still slow不安全边缘检测仍然很慢
【发布时间】:2018-10-17 08:46:22
【问题描述】:

我正在尝试在 WPF 程序中实现图像边缘检测。 我已经让它工作了,但是图像的转换很慢。 该代码没有使用缓慢的 GetPixel 和 SetPixel 函数。但相反,我在一些不安全的代码中循环遍历图像,以便我可以使用指针直接访问该值。 在开始边缘检测之前,我还将图像转换为灰度图像以提高边缘检测速度。

但程序仍然需要大约 1600 毫秒来转换大小为 1920x1440 像素的图像,我认为这可能会快得多。

这是原图:

转换成这个(应用程序的快照):

这就是我转换图像的方式,我想知道我可以做些什么来获得其他一些速度改进?

加载图像并创建灰度可写位图:

    private void imageData_Loaded(object sender, RoutedEventArgs e)
    {
        if (imageData.Source != null)
        {
            BitmapSource BitmapSrc = new FormatConvertedBitmap(imageData.Source as BitmapSource, PixelFormats.Gray8 /* Convert to greyscale image */, null, 0);
            writeableOriginalBitmap = new WriteableBitmap(BitmapSrc);
            writeableBitmap = writeableOriginalBitmap.Clone();
            imageData.Source = writeableBitmap;
            EdgeDetection();
        }
    }

转换图像:

    private const int TOLERANCE = 20;

    private void EdgeDetection()
    {
        DateTime startTime = DateTime.Now;   //Save starting time
        writeableOriginalBitmap.Lock();
        writeableBitmap.Lock();
        unsafe
        {
            byte* pBuffer         = (byte*)writeableBitmap.BackBuffer.ToPointer();
            byte* pOriginalBuffer = (byte*)writeableOriginalBitmap.BackBuffer.ToPointer();

            for (int row = 0; row < writeableOriginalBitmap.PixelHeight; row++)
            {
                for (int column = 0; column < writeableOriginalBitmap.PixelWidth; column++)
                {
                    byte edgeColor = getEdgeColor(column, row, pOriginalBuffer); //Get pixel color based on edge value
                    pBuffer[column + (row * writeableBitmap.BackBufferStride)] = (byte)(255 - edgeColor);
                }
            }
        }

        //Refresh image
        writeableBitmap.AddDirtyRect(new Int32Rect(0, 0, writeableBitmap.PixelWidth, writeableBitmap.PixelHeight));
        writeableBitmap.Unlock();
        writeableOriginalBitmap.Unlock();

        //Calculate converting time
        TimeSpan diff = DateTime.Now - startTime;
        Debug.WriteLine("Loading Time: " + (int)diff.TotalMilliseconds);
    }

    private unsafe byte getEdgeColor(int xPos, int yPos, byte* pOriginalBuffer)
    {
        byte Color;
        byte maxColor = 0;
        byte minColor = 255;
        int difference;

        //Calculate max and min value of surrounding pixels
        for (int y = yPos - 1; y <= yPos + 1; y++)
        {
            for (int x = xPos - 1; x <= xPos + 1; x++)
            {
                if (x >= 0 && x < writeableOriginalBitmap.PixelWidth && y >= 0 && y < writeableOriginalBitmap.PixelHeight)
                {
                    Color = pOriginalBuffer[x + (y * writeableOriginalBitmap.BackBufferStride)];
                    if (Color > maxColor)            //If current pixel has higher value as previous max pixel
                        maxColor = Color;            //Save current pixel value as max
                    if (Color < minColor)            //If current pixel has lower value as previous min pixel
                        minColor = Color;            //Save current pixel value as min
                }
            }
        }

        //Difference of minimum and maximum pixel with tollerance
        difference = maxColor - minColor - TOLERANCE;
        if (difference < 0)
            difference = 0;

        return (byte)difference;
    }

控制台输出:

Loading Time: 1599

【问题讨论】:

  • 我会使用 StopWatch 来获得更准确的测量结果 - 但这并不能解决您的问题。
  • 您是否尝试过使用简单的字节数组而不是 WriteableBitmaps 来运行该代码?使用 BitmapSource.CopyPixels 一次从 BitmapSource 获取字节数组,然后使用 BitmapSource.Create 转换回 BitmapSource。
  • @Clemens 我看到你刚刚发布了一个答案,我没有尝试简单的咬合数组。于是开始实施。这只是给了我巨大的时间改进,并在 343 毫秒内完成了转换。感谢您提供有用的提示。现在让我看看你的回答,看看我是否可以改进我的代码。
  • 不那么重要,但根据 VS 分析器:writeableBitmap.BackBufferStride 和 writeableOriginalBitmap.PixelHeight,writeableOriginalBitmap.PixelWidth,一遍又一遍地访问未更改的变量是性能的 5%。

标签: c# wpf image-processing


【解决方案1】:

以下代码在字节数组而不是 WriteableBitmap 的 BackBuffer 上运行您的算法。它在我的 PC 上使用 1900x1200 的图像在 300 毫秒内完成。

private static BitmapSource EdgeDetection(BitmapSource source)
{
    var stopwatch = Stopwatch.StartNew();
    var bitmap = new FormatConvertedBitmap(source, PixelFormats.Gray8, null, 0);
    var width = bitmap.PixelWidth;
    var height = bitmap.PixelHeight;
    var originalBuffer = new byte[width * height];
    var buffer = new byte[width * height];

    bitmap.CopyPixels(originalBuffer, width, 0);

    for (var y = 0; y < height; y++)
    {
        for (var x = 0; x < width; x++)
        {
            byte edgeColor = GetEdgeColor(originalBuffer, width, height, x, y);
            buffer[width * y + x] = (byte)(255 - edgeColor);
        }
    }

    Debug.WriteLine(stopwatch.ElapsedMilliseconds);

    return BitmapSource.Create(
        width, height, 96, 96, PixelFormats.Gray8, null, buffer, width);
}

private static byte GetEdgeColor(byte[] buffer, int width, int height, int x, int y)
{
    const int tolerance = 20;
    byte minColor = 255;
    byte maxColor = 0;
    var xStart = Math.Max(0, x - 1);
    var xEnd = Math.Min(width - 1, x + 1);
    var yStart = Math.Max(0, y - 1);
    var yEnd = Math.Min(height - 1, y + 1);

    for (var j = yStart; j <= yEnd; j++)
    {
        for (var i = xStart; i <= xEnd; i++)
        {
            var color = buffer[width * j + i];
            minColor = Math.Min(minColor, color);
            maxColor = Math.Max(maxColor, color);
        }
    }

    return (byte)Math.Max(0, maxColor - minColor - tolerance);
}

【讨论】:

  • 就像我对这个问题的评论一样,这会重新计算不改变的结束条件中的值;然而,在这种情况下,Math.Min(height, y + 2); (以及宽度)占总性能的 20%。
  • 另外,不需要在内循环之外声明颜色字节。性能与在使用前定义的性能完全相同,如原始问题。
猜你喜欢
  • 2016-09-12
  • 2016-08-18
  • 1970-01-01
  • 2014-03-30
  • 1970-01-01
  • 2021-03-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多