Как я могу найти все прямоугольники, которые связывают области в растровом изображении?
У меня проблема: мне нужен алгоритм для моего движка плиток.
У меня есть 2d-массив, в котором хранятся мои неприступные плитки.
Сейчас я хочу реализовать легкий двигатель, но этот двигатель нуждается в теневых корпусах.
Поэтому мне нужен алгоритм, который будет создавать эти теневые оболочки.
Мне нужен набор прямоугольников, которые ограничивают неприступные части массива (ячейки, которые имеют 1
s)
Например:
Черная плитка 1
s; Мне нужно найти множество красных прямоугольников, которые полностью их заключают.
2 ответа
После дальнейших размышлений я нашел более быстрое решение:
Создать неизменный
Range
структурировать сStartX
,StartY
, а такжеEndY
свойства.Поддерживать разреженный массив тока
Range
s, с длиной, равной высоте изображения, и единственной переменной currentRange со значением NULL. Этот массив будет содержать диапазон, если он есть, который начинается с каждой координаты YДля каждого столбца
- Очистить
currentRange
Для каждой ячейки в столбце:
Если не существует currentRange, или если есть, но он закончился до этой ячейки (если
currentRange.EndY <= y
), установите currentRange наy
элемент в массиве диапазонов.
Таким образом,currentRange
всегда будет ссылаться на диапазон, который содержит текущую ячейку, илиnull
если текущая ячейка не находится в диапазоне.Если текущая ячейка
1
:Если мы находимся в диапазоне, ничего не делаем - диапазон продолжается в следующем столбце.
Если мы не находимся в пределах досягаемости, перебираем колонку, пока не достигнем
0
или конец столбца, а затем создать новый диапазон, растягиваясь от1
мы только что нашли до конца цикла.
Затем перейдите к следующему if (так как текущая ячейка сейчас0
или конец столбца, и мы не в диапазоне)
Если вы нажмете существующий диапазон, когда создаете новый диапазон, вы можете либо остановить новый диапазон, и оставить существующий диапазон ниже его (лучше всего для нечетких краев), либо закрыть существующий диапазон (см. Ниже) и позволить новый диапазон растягивается вниз, чтобы перенять существующий диапазон.
- Если текущая ячейка
0
:- Если мы находимся в диапазоне, закройте диапазон:
- Вернуть новый прямоугольник, растягивающийся от начала X и Y диапазона до текущего Y и конца X диапазона.
- Очистить диапазон из массива активных диапазонов.
- Если мы находимся в диапазоне, закройте диапазон:
- Очистить
Этот алгоритм O(x * y)
в вычислениях и O(y)
в космосе. Я считаю, что это самое быстрое решение проблемы.
Вы также можете вращать этот алгоритм и сканировать строки, а не столбцы (чтобы диапазоны были растянуты вниз, а не вправо), что будет O(x)
в хранилище.
Вот реализация C#:
class BoxFinder {
class Range {
public Range(int startX, int startY, int endY) {
StartX = startX;
StartY = startY;
EndY = endY;
}
public int StartX { get; private set; }
public int StartY { get; private set; }
public int EndY { get; private set; }
}
public BoxFinder(int[,] data) {
Data = data;
Width = data.GetLength(0);
Height = data.GetLength(1);
ranges = new Range[Height];
}
public int[,] Data { get; private set; }
public int Width { get; private set; }
public int Height { get; private set; }
private Range[] ranges;
public IEnumerable<Rectangle> GetBoxes() {
for (int x = 0; x < Width; x++) {
Range currentRange = null;
for (int y = 0; y < Height; y++) {
Func<Range, Rectangle> CloseRange = r => {
currentRange = null;
ranges[r.StartY] = null;
return new Rectangle(
r.StartY,
r.StartX,
x - r.StartX,
r.EndY - r.StartY
);
};
if (currentRange == null || currentRange.EndY <= y)
currentRange = ranges[y];
if (Data[x, y] == 1) {
if (currentRange == null) {
int startY = y;
for (; y < Height && Data[x, y] == 1; y++) {
if (ranges[y] != null)
yield return CloseRange(ranges[y]);
//If there are fuzzy edges, break; instead
}
ranges[startY] = new Range(x, startY, y);
if (y == Height)
continue;
//Otherwise, continue to the next if in case a previous range just ended
}
}
//No else; we can get here after creating a range
if(Data[x, y] == 0) {
if (currentRange != null)
yield return CloseRange(currentRange);
}
}
}
}
}
Попробуйте что-то вроде этого:
Создайте список, содержащий каждую желаемую точку. (В вашем случае координаты каждого
1
)Для каждой точки в этом списке:
- Зацикливайте ось Y от этой точки до тех пор, пока не достигнете нежелательной точки (
0
) - Зацикливайтесь прямо вдоль оси X, пока не достигнете координаты X, которая имеет
0
при любом значении Y между точкой и конечной координатой Y, полученной на шаге 1. - Добавьте только что найденный прямоугольник в список прямоугольников
- Удалите каждую точку в прямоугольнике из исходного списка.
- Зацикливайте ось Y от этой точки до тех пор, пока не достигнете нежелательной точки (
Это, вероятно, не самый быстрый способ сделать это, но он должен работать.