Опасное обнаружение краев все еще медленно

2

Я пытаюсь внедрить обнаружение изображения в программу WPF. У меня уже есть работа, но преобразование изображения происходит довольно медленно. Код не использует медленные функции GetPixel и SetPixel. Но вместо этого я просматриваю изображение в некотором небезопасном коде, чтобы я мог напрямую получить доступ к значению с помощью указателя. Перед запуском обнаружения края я также преобразовываю изображение в изображение с оттенками серого, чтобы улучшить скорость обнаружения края.

Но для преобразования изображения с разрешением 1920x1440 пикселей, которое, по моему мнению, может быть намного быстрее, требуется около 1600 мс.

Это исходное изображение: Изображение 174551

Что преобразуется в это (Снимок приложения): Изображение 174551

Вот как я конвертирую изображение, мне интересно, что я могу сделать, чтобы получить некоторые другие улучшения скорости?

Загрузите изображение и создайте Greyscale WriteableBitmap:

    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
  • 0
    Я бы использовал StopWatch, чтобы получить более точные измерения - но это не решает вашу проблему.
  • 3
    Вы пытались запустить этот код с простыми байтовыми массивами вместо WriteableBitmaps? Используйте BitmapSource.CopyPixels один раз, чтобы получить байтовый массив из BitmapSource, и BitmapSource.Create, чтобы преобразовать обратно в BitmapSource.
Показать ещё 2 комментария
Теги:
image-processing
wpf

1 ответ

4
Лучший ответ

Следующий код запускает ваш алгоритм в байтовом массиве вместо BackBuffer для WriteableBitmap. Он заканчивается менее чем за 300 мс с изображением 1900x1200 на моем ПК.

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);
}
  • 0
    Точно так же, как мой комментарий к вопросу, это пересчитывает значения в конечном условии, которые не меняются; в этом случае, однако, Math.Min (высота, y + 2); (и для ширины) составляют 20% от общей производительности.
  • 0
    Кроме того, нет необходимости объявлять байт цвета вне внутреннего цикла. Производительность точно такая же, как и перед использованием, как в исходном вопросе.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню