Подтвердить что ты не робот

Сканирование изображений для поиска прямоугольников

Я пытаюсь отсканировать изображение с постоянным размером и найти в нем рисованные прямоугольники. Прямоугольники могут иметь любой размер, но только красный цвет.

Это не, где проблема начинается.

Я буду использовать уже написанную функцию, и буду использовать ее в качестве псевдокодов позже в моей логике кода.

Rectangle Locate(Rectangle scanArea);//сканирует прямоугольник в заданной области сканирования.  если прямой прямоугольник не найден, возвращает null.

Моя логика была такой:

Найдите первый начальный красный прямоугольник, используя функцию Locate() с полным размером изображения в качестве аргумента. Теперь разделите остальные области и продолжайте сканирование рекурсивно. Главное в этой логике алгоритма состоит в том, что вы никогда не проверяете уже отмеченную область, и вам не нужно использовать какое-либо условие, потому что всегда параметр scanArea - это новая область, которую вы ранее не сканировали (и что благодаря техника разделения). Процесс деления выполняется следующим образом: правая область текущего найденного прямоугольника, нижняя область и левая область.

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

Итак, вот код для этого метода:

List<Rectangle> difList=new List<Rectangle>();

private void LocateDifferences(Rectangle scanArea)
{
    Rectangle foundRect = Locate(scanArea);
    if (foundRect == null)
        return; // stop the recursion.

    Rectangle rightArea = new Rectangle(foundRect.X + foundRect.Width, foundRect.Y, (scanArea.X + scanArea.Width) - (foundRect.X + foundRect.Width), (scanArea.Y + scanArea.Height) - (foundRect.Y + foundRect.Height)); // define right area.
    Rectangle bottomArea = new Rectangle(foundRect.X, foundRect.Y + foundRect.Height, foundRect.Width, (scanArea.Y + scanArea.Height) - (foundRect.Y + foundRect.Height)); // define bottom area.
    Rectangle leftArea = new Rectangle(scanArea.X, foundRect.Y, (foundRect.X - scanArea.X), (scanArea.Y + scanArea.Height) - (foundRect.Y + foundRect.Height)); // define left area.

    difList.Add(rectFound);

    LocateDifferences(rightArea);
    LocateDifferences(bottomArea);
    LocateDifferences(leftArea);
}

Пока все работает хорошо, он находит каждый красный прямоугольник. Но иногда прямоугольники сохраняются как несколько прямоугольников. По какой-то причине это очевидно для меня: с перекрывающимися прямоугольниками.

Проблемный случай, например: введите описание изображения здесь

Теперь в этом случае программа найдет первую красную область, как и планировалось, но тогда, поскольку правильная область начинается только в середине полной второй области, она не сканирует с начала второго красного прямоугольника!

Аналогичным образом я могу разделить области так, чтобы нижняя область простиралась от начала scanArea до конца, что было бы так: введите описание изображения здесь Но теперь у нас возникнет проблема при сканировании перекрывающихся прямоугольников справа и слева от прямоугольника foundRect, например, в этом случае:

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

Если что-то недостаточно ясно, просто скажите, и я объясню это как можно лучше! Спасибо!

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

4b9b3361

Ответ 1

Алгоритм, который может найти несколько прямоугольников путем сканирования изображения один раз, не должен быть сложным. Основное отличие от того, что вы делаете сейчас, это то, что, когда вы находите верхний угол прямоугольника, вы не должны сразу находить ширину и высоту и сохранять прямоугольник, но вместо этого хранить его в списке незавершенных прямоугольников временно, пока вы попадаете в его нижний угол. Затем этот список можно использовать для эффективной проверки того, является ли каждый красный пиксель частью нового прямоугольника или того, который вы уже нашли. Рассмотрим этот пример:

введите описание изображения здесь

Мы начинаем сканирование сверху вниз и слева направо. В строке 1 мы находим красный пиксель в позиции 10; мы продолжаем сканирование, пока не найдем следующий черный пиксель (или не дойдем до конца строки); теперь мы можем сохранить его в списке незавершенных прямоугольников как {left, right, top}:

unfinished: {10,13,1}  

При сканировании следующей строки мы перебираем список незавершенных прямоугольников, поэтому мы знаем, когда ожидать прямоугольник. Когда мы достигнем позиции 10, мы найдем красный пиксель, как и ожидалось, и мы можем перейти к позиции 14 и пройти мимо незавершенного прямоугольника. Когда мы достигнем позиции 16, мы обнаруживаем неожиданный красный пиксель и продолжаем первый черный пиксель в позиции 19, а затем добавим этот второй прямоугольник в незавершенный список:

unfinished: {10,13,1},{16,18,2}  

После сканирования строк с 3 по 5 мы получим этот список:

unfinished: {1,4,3},{6,7,3},{10,13,1},{16,18,2},{21,214}  

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

В строке 6, после прохождения первых двух незавершенных прямоугольников, мы обнаруживаем неожиданный черный пиксель в позиции 10; теперь мы можем удалить третий прямоугольник из незавершенного списка и добавить его в массив полных прямоугольников как {left, right, top, bottom}:

unfinished: {1,4,3},{6,7,3},{16,18,2},{21,21,4}  
finished: {10,13,1,5}  

Когда мы дойдем до конца строки 9, мы завершили все прямоугольники, которые были незавершенными после строки 5, но мы нашли новый прямоугольник в строке 7:

unfinished: {12,16,7}  
finished: {10,13,1,5},{16,18,2,5},{1,4,3,6},{6,7,3,8},{21,21,4,8}  

Если мы продолжим до конца, результат:

unfinished:  
finished: {10,13,1,5},{16,18,2,5},{1,4,3,6},{6,7,3,8},{21,21,4,8},  
          {12,16,7,10},{3,10,10,13},{13,17,13,14},{19,22,11,14}  

Если в этой точке есть какие-то незавершенные прямоугольники, они граничат нижний край изображения и могут быть заполнены нижним значением = высота-1.

Обратите внимание, что пропуски через незавершенные прямоугольники означают, что вам нужно только сканировать черные пиксели и верхний и левый края красных прямоугольников; в примере мы пропустили 78 из 384 пикселей.

Нажмите здесь, чтобы увидеть простую версию С++ в действии на rextester.com(извините, я не говорю С#).

Если вы обнаружите, что реализация связанного списка С# не достаточно быстро, вы можете использовать два массива длины ширину изображения, а когда вы найдете верхнюю часть прямоугольника между позициями x1 и x2 в строке y, сохраните неполный прямоугольник как ширину [x1] = x2-x1 и top [x1] = y, а reset до нуля, когда вы сохраните полный прямоугольник.

Этот метод найдет прямоугольники размером от 1 пикселя. Если минимальный размер, вы можете сканировать изображение с помощью более высоких шагов; с минимальным размером 10x10 вам нужно будет сканировать только около 1% пикселей.

Ответ 2

Следуя вашим критериям не изменять функцию Locate(), а просто расширяя существующую логику, нам необходимо присоединиться к любому сообщению posts. Попробуйте следующее:

Сначала немного измените функцию LocateDifferences(), чтобы отслеживать прямоугольники, которые, возможно, необходимо соединить.

private void LocateDifferences(Rectangle scanArea)
{
    Rectangle foundRect = Locate(scanArea);
    if (foundRect == null)
        return; // stop the recursion.

    Rectangle rightArea = new Rectangle(foundRect.X + foundRect.Width, foundRect.Y, (scanArea.X + scanArea.Width) - (foundRect.X + foundRect.Width), (scanArea.Y + scanArea.Height) - (foundRect.Y + foundRect.Height)); //define right area.
    Rectangle bottomArea = new Rectangle(foundRect.X, foundRect.Y + foundRect.Height, foundRect.Width, (scanArea.Y + scanArea.Height) - (foundRect.Y + foundRect.Height)); // define bottom area.
    Rectangle leftArea = new Rectangle(scanArea.X, foundRect.Y, (foundRect.X - scanArea.X), (scanArea.Y + scanArea.Height) - (foundRect.Y + foundRect.Height)); // define left area.

    if (foundRect.X == scanArea.X || foundRect.Y == scanArea.Y || (foundRect.X + foundRect.Width == scanArea.X + scanArea.Width) || (foundRect.Y + foundRect.Height == scanArea.Y + scanArea.Height)) 
    {
        // edge may extend scanArea
        difList.Add(Tuple.Create(foundRect, false));
    } else {
        difList.Add(Tuple.Create(foundRect, true));
    }

    LocateDifferences(rightArea);
    LocateDifferences(bottomArea);
    LocateDifferences(leftArea);
}

Я также добавил эти два метода для использования:

// JoinRects: will return a rectangle composed of r1 and r2.
private Rectangle JoinRects(Rectangle r1, Rectangle r2)
{
    return new Rectangle(Math.Min(r1.X, r2.X), 
                    Math.Min(r1.Y, r2.Y), 
                    Math.Max(r1.Y + r1.Width, r2.Y + r2.Width), 
                    Math.Max(r1.X + r1.Height, r2.X + r2.Height));
}

// ShouldJoinRects: determines if the rectangles are connected and the height or width matches.
private bool ShouldJoinRects(Rectangle r1, Rectangle r2)
{
    if ((r1.X + r1.Width + 1 == r2.X && r1.Y == r2.Y && r1.Height == r2.Height)
     || (r1.X - 1 == r2.x + r2.Width && r1.Y == r2.Y && r1.Height == r2.Height)
     || (r1.Y + r1.Height + 1 == r2.Y && r1.X == r2.X && r1.Width == r2.Width)
     || (r1.Y - 1 == r2.Y + r2.Height && r1.X == r2.X && r1.Width == r2.Width))
    {
        return true;
    } 
    else 
    {
        return false;
    }
}

Наконец, ваша основная функция, которая запускает сканирование

List<Tuple<Rectangle, Bool>> difList = new List<Tuple<Rectangle, Bool>();

// HERE: fill our list by calling LocateDifferences
LocateDifferences();

var allGood = difList.Where(t => t.Item2 == true).ToList();
var checkThese = difList.Where(t => t.Item2 == false).ToArray();

for (int i = 0; i < checkThese.Length - 1; i++)
{
    // check that its not an empty Rectangle
    if (checkThese[i].IsEmpty == false) 
    {
        for (int j = i; j < checkThese.Length; j++)
        {
            // check that its not an empty Rectangle
            if (checkThese[j].IsEmpty == false) 
            {
                if (ShouldJoinRects(checkThese[i], checkThese[j])
                {
                    checkThese[i] = JoinRects(checkThese[i], checkThese[j]);
                    checkThese[j] = new Rectangle(0,0,0,0);
                    j = i // restart the inner loop in case we are dealing with a rect that crosses 3 scan areas
                }
            }
        }
        allGood.Add(checkThese[i]);
    }
}

//Now 'allGood' contains all the rects joined where needed.

Ответ 3

Самый простой подход к использованию простого алгоритма:

function find(Image): Collection of Rects
   core_rect = FindRects(Image)
   split(core_rect) -> 4 rectangles (left-top, left-bottom, right-top, right-bottom)
   return Merge of (find(leftTop), find(leftBottom), ...)

function findAll(Image): Collection of Rects
   rects <- find(Image)
   sort rectangles by X, Y
   merge rectangles
   sort rectangles by Y, X
   merge rectangles
   return merged set

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

Ответ 4

Основываясь на ваших реквизитах:

  • Оставить функцию Locate(Rectangle scanArea) нетронутой.
  • Использование рекурсивного алгоритма сканирования Left/Bottom/Right (рис.).

scanAlg

Id вводит дополнительный аргумент типа Side в рекурсивную функцию.

internal enum Side : byte 
{
    Left,
    Bottom,
    Right
}

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

internal class RectangleInfo
{
    public RectangleInfo(Rectangle rect, bool leftOverlap, bool rightOverlap)
    {
        Rectangle = rect;
        LeftOverlap = leftOverlap;
        RightOverlap = rightOverlap;
    }
    public Rectangle Rectangle { get; set; }
    public bool LeftOverlap { get; set; }
    public bool RightOverlap { get; set; }
}

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

List<Rectangle> difList = new List<Rectangle>();

List<Rectangle> leftList = new List<Rectangle>();
List<RectangleInfo> bottomList = new List<RectangleInfo>();
List<Rectangle> rightList = new List<Rectangle>();

private void AccumulateDifferences(Rectangle scanArea, Side direction)
{
    Rectangle foundRect = Locate(scanArea);
    if (foundRect == null)
        return; // stop the recursion.

    switch (direction)
    {
        case Side.Left:
            if (foundRect.X + foundRect.Width == scanArea.X + scanArea.Width)
                leftList.Add(foundRect);
            else difList.Add(foundRect);
            break;

        case Side.Bottom:
            bottomList.Add(new RectangleInfo(foundRect, foundRect.X == scanArea.X, foundRect.X + foundRect.Width == scanArea.X + scanArea.Width));
            break;

        case Side.Right:
            if (foundRect.X == scanArea.X)
                rightList.Add(foundRect);
            else difList.Add(foundRect);
            break;
    }
    Rectangle leftArea = new Rectangle(scanArea.X, foundRect.Y, (foundRect.X - scanArea.X), (scanArea.Y + scanArea.Height) - (foundRect.Y + foundRect.Height)); // define left area.
    Rectangle bottomArea = new Rectangle(foundRect.X, foundRect.Y + foundRect.Height, foundRect.Width, (scanArea.Y + scanArea.Height) - (foundRect.Y + foundRect.Height)); // define bottom area.
    Rectangle rightArea = new Rectangle(foundRect.X + foundRect.Width, foundRect.Y, (scanArea.X + scanArea.Width) - (foundRect.X + foundRect.Width), (scanArea.Y + scanArea.Height) - (foundRect.Y + foundRect.Height)); //define right area.

    AccumulateDifferences(leftArea, Side.Left);
    AccumulateDifferences(bottomArea, Side.Bottom);
    AccumulateDifferences(rightArea, Side.Right);
}

private void ProcessDifferences()
{
    foreach (RectangleInfo rectInfo in bottomList)
    {
        if (rectInfo.LeftOverlap)
        {
            Rectangle leftPart =
                leftList.Find(r => r.X + r.Width == rectInfo.Rectangle.X
                                   && r.Y == rectInfo.Rectangle.Y
                                   && r.Height == rectInfo.Rectangle.Height
                             );
            if (leftPart != null)
            {
                rectInfo.Rectangle.X = leftPart.X;
                leftList.Remove(leftPart);
            }
        }

        if (rectInfo.RightOverlap)
        {
            Rectangle rightPart =
                rightList.Find(r => r.X == rectInfo.Rectangle.X + rectInfo.Rectangle.Width
                                    && r.Y == rectInfo.Rectangle.Y
                                    && r.Height == rectInfo.Rectangle.Height
                              );
            if (rightPart != null)
            {
                rectInfo.Rectangle.X += rightPart.Width;
                rightList.Remove(rightPart);
            }
        }

        difList.Add(rectInfo.Rectangle);
    }

    difList.AddRange(leftList);
    difList.AddRange(rightList);
}

private void LocateDifferences(Rectangle scanArea)
{
    AccumulateDifferences(scanArea, Side.Left);
    ProcessDifferences();

    leftList.Clear();
    bottomList.Clear();
    rightList.Clear();
}

Поиск соседних прямоугольников

Возможно, существует несколько прямоугольников с теми же значениями X в rightList (или X + Width значения в leftList), поэтому нам нужно проверить пересечение при обнаружении возможного совпадения.

В зависимости от количества элементов вы также можете использовать словари (для более быстрого поиска) в случае leftList и rightList. Используя верхнюю точку пересечения в качестве ключа, а затем проверяя Height перед слиянием.

Ответ 5

Я бы решил это следующим образом:

  • Я начну читать изображение с первого пикселя.
  • зарегистрируйте местоположение (x,y) каждого красного пикселя. [поместите 1 в (x,y) в матрицу результатов с одинаковым размером изображения]
  • cost O(nxm) где n - количество строк, а m - количество столбцов изображения.
  • прямоугольник представляет собой набор связанных 1s, где sum (y) одинакова для каждого x. Это необходимая проверка, чтобы обеспечить захват прямоугольников только в том случае, если были капли/круги (зеленый сегмент на изображении ниже).etc.

Ниже приведена фотография матрицы результатов: nxm result matrix

Ответ 7

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

Предполагая, что большая часть изображения не красная:

  • очистить вспомогательную растровую карту до 0
  • set x = y = 0, начальная позиция для сканирования
  • сканировать изображение с x, y, продолжить в порядке хранения для эффективности, найти первый красный пиксель на изображении, где вспомогательное растровое изображение 0
  • это угол прямоугольника, найдите его размеры с использованием существующего подхода (начните горизонтальное и вертикальное сканирование и т.д.).
  • запишите прямоугольник (x, y, w, h)
  • заполнить прямоугольник (x, y, w, h) в вспомогательной растровой картинке с помощью 1-s
  • x + = w + 1, продолжите с 2 (подразумевая, что он будет проверять, находится ли новая позиция x по-прежнему внутри размеров изображения и, если необходимо, попытаться использовать 0, y + 1, а также распознать, только что выполняется сканирование)

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

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

Ответ 8

Нет необходимости изобретать велосипед. Это проблема с маркировкой связанных компонентов.

https://en.wikipedia.org/wiki/Connected-component_labeling

Существуют различные способы решения проблемы. Один из них - это кодирование строк изображения по длине строки и поиск перекрытий от строки к строке.

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

Еще один из них - сканирование изображения и выполнение контура после того, как вы встретите красный пиксель (и вы помечаете каждый контурный пиксель так, чтобы blob больше обрабатывался).

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

Ответ 9

Извиняюсь, но я не читал ваше решение, потому что не уверен, хотите ли вы хорошего решения или решить проблему с этим решением.

Простое решение с использованием выходных строительных блоков (например, OpenCV, которое я не знаю, если есть порт для С#):

  • возьмите красный канал (потому что вы сказали, что хотите обнаружить только красные прямоугольники)
  • findContours
  • для каждого контура 3.1 взять свою ограничительную рамку 3.2 проверьте, является ли контур прямоугольником, сравнивая его общую область с общей площадью ограничивающей рамки.

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

Ответ 10

Вы выполняете сканирование строк на пиксель над изображением.

если пиксель вверх черный, а пиксель слева - черный, а сам пиксель красный то вы оставили верхний угол (x1, y1). повернуть направо, пока его черный снова (правый верхний y2 + 1) Нажмите на нижнюю часть, чтобы найти черный xs + 1, чтобы вы могли получить правое, нижнее (x2, y2)

Хранить x1, y1, x2, y2 в структуре списка или классе нарисуйте только что найденный прямоугольник черного цвета и сканирование контировки