Как оптимально определить края изображения? - PullRequest
0 голосов
/ 24 мая 2010

Недавно я поставил перед проблемой обрезки и изменения размера изображений.Мне нужно было обрезать «основное содержимое» изображения, например, если у меня было изображение, похожее на это: альтернативный текст http://blstb.msn.com/i/8C/10F73EB7EE231B1EA8E65EFA7D69B.jpg

результат должен быть изображением с содержанием msn без белых полей (слева исправа).

Я ищу по оси X первое и последнее изменение цвета и по оси Y то же самое.Проблема в том, что построчное прохождение изображения занимает некоторое время ... для изображения размером 2000x1600 пикселей требуется до 2 секунд, чтобы вернуть данные CropRect => x1, y1, x2, y2.

Я пыталсясделать для каждой координаты обход и остановку на первом найденном значении, но это не сработало во всех тестовых случаях. Иногда возвращаемые данные не были ожидаемыми, а продолжительность операций была одинаковой.

Есть идеи, как сократить время обхода и обнаружения прямоугольника вокруг «основного содержимого»?

public static CropRect EdgeDetection(Bitmap Image, float Threshold)
        {
            CropRect cropRectangle = new CropRect();
            int lowestX = 0;
            int lowestY = 0;
            int largestX = 0;
            int largestY = 0;

            lowestX = Image.Width;
            lowestY = Image.Height;

            //find the lowest X bound;
            for (int y = 0; y < Image.Height - 1; ++y)
            {
                for (int x = 0; x < Image.Width - 1; ++x)
                {
                    Color currentColor = Image.GetPixel(x, y);
                    Color tempXcolor = Image.GetPixel(x + 1, y);
                    Color tempYColor = Image.GetPixel(x, y + 1);
                    if ((Math.Sqrt(((currentColor.R - tempXcolor.R) * (currentColor.R - tempXcolor.R)) +
                        ((currentColor.G - tempXcolor.G) * (currentColor.G - tempXcolor.G)) +
                        ((currentColor.B - tempXcolor.B) * (currentColor.B - tempXcolor.B))) > Threshold)) 
                    {
                        if (lowestX > x)
                            lowestX = x;

                        if (largestX < x)
                            largestX = x;
                    }

                    if ((Math.Sqrt(((currentColor.R - tempYColor.R) * (currentColor.R - tempYColor.R)) +
                        ((currentColor.G - tempYColor.G) * (currentColor.G - tempYColor.G)) +
                        ((currentColor.B - tempYColor.B) * (currentColor.B - tempYColor.B))) > Threshold))
                    {
                        if (lowestY > y)
                            lowestY = y;

                        if (largestY < y)
                            largestY = y;
                    }
                }                
            }

            if (lowestX < Image.Width / 4)
                cropRectangle.X = lowestX - 3 > 0 ? lowestX - 3 : 0;
            else
                cropRectangle.X = 0;

            if (lowestY < Image.Height / 4)
                cropRectangle.Y = lowestY - 3 > 0 ? lowestY - 3 : 0;
            else
                cropRectangle.Y = 0;

            cropRectangle.Width = largestX - lowestX + 8 > Image.Width ? Image.Width : largestX - lowestX + 8;
            cropRectangle.Height = largestY + 8 > Image.Height ? Image.Height - lowestY : largestY - lowestY + 8;
            return cropRectangle;
        }
    }

Ответы [ 3 ]

3 голосов
/ 24 мая 2010

Одной из возможных оптимизаций является использование Lockbits для прямого доступа к значениям цвета, а не через намного более медленный GetPixel.

Если вы ищете Lockbits, первое попадание будет http://www.bobpowell.net/lockingbits.htm. Это хорошая ссылка.

С другой стороны, мое тестирование показало, что издержки, связанные с Lockbits, замедляют этот подход, если вы попытаетесь написать GetPixelFast, эквивалентный GetPixel, и вставить его в качестве замены. Вместо этого вам нужно убедиться, что весь доступ к пикселям осуществляется одним ударом, а не несколькими попаданиями. Это должно хорошо сочетаться с вашим кодом, если вы не блокируете / разблокируете каждый пиксель.

Вот пример

BitmapData bmd = b.LockBits(new Rectangle(0, 0, b.Width, b.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, b.PixelFormat);

byte* row = (byte*)bmd.Scan0 + (y * bmd.Stride);

//                           Blue                    Green                   Red 
Color c = Color.FromArgb(row[x * pixelSize + 2], row[x * pixelSize + 1], row[x * pixelSize]);

b.UnlockBits(bmd);

Еще две вещи на заметку:

  1. Этот код небезопасен, поскольку использует указатели
  2. Этот подход зависит от размера пикселя в растровых данных, поэтому вам нужно будет извлечь pixelSize из bitmap.PixelFormat
2 голосов
/ 24 мая 2010

GetPixel, вероятно, является вашим основным виновником (я рекомендую запустить несколько тестов профилирования, чтобы отследить его), но вы можете перестроить алгоритм следующим образом:

  1. Сканирование первой строки (y = 0) слева-вправо и справа налево и записать первое и последнее местоположение края.Нет необходимости проверять все пиксели, поскольку вы хотите крайние края.
  2. Сканирование всех последующих строк, но теперь нам нужно только искать наружу (от центра к краям), начиная с нашего последнего известного минимального края.Мы хотим найти крайние границы, поэтому нам нужно искать только в области, где мы могли бы найти новые экстремумы.
  3. Повторить первые два шага для столбцов, установить начальные экстремумы и затем использовать эти экстремумы для итеративной привязки.поиск.

Это должно значительно сократить количество сравнений, если ваши изображения, как правило, в основном содержательные.В худшем случае это полностью пустое изображение, для которого это, вероятно, будет менее эффективно, чем исчерпывающий поиск.

В крайних случаях обработка изображений также может выиграть от параллелизма (разделить изображение и обработать его в несколько потоковна многоядерном процессоре), но это совсем немного дополнительной работы, и есть другие, более простые изменения, которые вы все еще делаете.Потоковые накладные расходы имеют тенденцию ограничивать применимость этого метода и, в основном, полезны, если вы планируете запускать эту вещь в режиме реального времени с выделенной повторной обработкой входящих данных (чтобы компенсировать первоначальные затраты на установку).

0 голосов
/ 24 мая 2010

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

Это должно дать значительное увеличение скорости.

...