«нарисовать» случайный ромб (ромб) в массиве numpy (тестирование обнаружения угла Харриса)

Я пытаюсь создать случайный тест для реализации функции harris_corner_detector (ОЧЕНЬ ОБЩЕ И НЕМНОГО НЕПРАВИЛЬНО: функция, которая находит углы в изображении). В тесте я хочу создать случайные простые формы в двоичной матрице numpy (это легко знать координаты их углов) (например, прямоугольники, треугольники, ромб (ромб) и т. д.) и проверить, находит ли реализация Харриса правильные углы.

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

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

получение случайных координат:

    def _get_random_coords(self, start, end):
        x_start, y_start = np.random.randint(start, end, 2)
        x_end = np.random.randint(x_start + 7, end + 20)
        y_end = np.random.randint(y_start + 7, end + 20)
        return (x_start, x_end, y_start, y_end)

рисование случайного прямоугольника (значения 255 для фона и 0 для фигуры):

mat = np.ones((1024, 1024)) * 255
mat[x_start: x_end, y_start: y_end] = np.zeros((x_end - x_start, y_end - y_start))

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

    def _get_rhombus(self, size):
        rhombus = []
        for i in range(size):
            rhombus.append(np.zeros(i+1))
        for i in range(size - 1, 0, -1):
            rhombus.append(np.zeros(i))
        return np.array(rhombus)

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

Есть идеи получше? В качестве альтернативы - есть ли лучший способ проверить это?

Заранее спасибо.


person ChocolateBear    schedule 10.12.2020    source источник
comment
Вы можете создать случайный прямоугольник, а затем взять середины его краев за вершины ромба.   -  person Quang Hoang    schedule 10.12.2020
comment
Это хорошая идея! Но я все еще сталкиваюсь с главной проблемой - эффективно рисовать. (Если я не понял неправильно)   -  person ChocolateBear    schedule 11.12.2020


Ответы (2)


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

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

import numpy as np
import matplotlib.pyplot as plt

# given two (non-vertical) points, A and B, 
# fill above or below the line connecting them
def fill(A, B, fill_below=True, xs=10, ys=12):

    # the equation for a line is y = m*x + b, so calculate
    # m and b from the two points on the line
    m = (B[1]-A[1])/(B[0]-A[0]) # m = (y2 - y1)/(x2 - x1) = slope
    b = A[1] - m*A[0]           # b = y1 - m*x1 = y intercept

    # for each points of the grid, calculate whether it's above, below, or on
    # the line. Since y = m*x + b, calculating m*x + b - y will give
    # 0 when on the line, <0 when above, and >0 when below
    Y, X = np.mgrid[0:ys, 0:xs] 
    L = m*X + b - Y

    # select whether, >=0 is True, or, <=0 is True, to determine whether to
    # fill above or below the line
    op = np.greater_equal if fill_below else np.less_equal
    return op(L, 0.0)

Вот простой ромб с низким разрешением

r = fill((0, 3), (3, 8), True) & \
fill((3, 8), (7, 4), True) & \
fill((7,4), (5,0), False) & \
fill((5,0), (0,3), False)
plt.imshow(r, cmap='Greys',  interpolation='nearest', origin='lower')

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

То есть приведенная выше цифра является результатом объединения вместе следующих заливок:

fig, ax = plt.subplots(1, 4, figsize=(10, 3))
fill_params = [((0, 3), (3, 8), True), ((3, 8), (7, 4), True), ((7, 4), (5, 0), False), ((5, 0), (0, 3), False)]
for p, ax in zip(fill_params, ax):
    ax.imshow(fill(*p), cmap="Greys", interpolation='nearest', origin='lower')

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

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

r = fill((0, 300), (300, 800), True, 1000, 1200) & \
fill((300, 800), (600,700), True, 1000, 1200) & \
fill((600, 700), (700, 400), True, 1000, 1200) & \
fill((700,400), (500,0), False, 1000, 1200) & \
fill((500,0), (100,100), False, 1000, 1200) & \
fill((100, 100), (0,300), False, 1000, 1200)
plt.imshow(r, cmap='Greys',  interpolation='nearest', origin='lower')

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

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

person tom10    schedule 11.12.2020
comment
Это ИМЕННО то, что я искал. Действительно элегантно и хорошо сделано! Я еще не все понимаю, но это сработало так хорошо :) Теперь я медленно пройдусь по всей реализации, чтобы лучше понять ее и подогнать под свои нужды. Большое спасибо! - person ChocolateBear; 11.12.2020
comment
Отлично. Я добавил несколько комментариев к своему коду, которые, надеюсь, прояснят, что он делает. - person tom10; 11.12.2020
comment
Сам уже прошел через это, но уверен, что комментарии помогут тем, кто наткнется на мой вопрос. Большое спасибо! - person ChocolateBear; 12.12.2020

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

import numpy as np
import matplotlib.pyplot as plt

blank = np.zeros((10, 12))

anchorx, anchory = 2, 3
# better result for odd dimensions, because mid index exists
# can handle h != w but the rhombus would still fit to a square of dimension min(h, w) x min(h, w)
h, w = 7, 7

assert anchorx+h <= blank.shape[0], "Boundaries exceed, maintain 'anchorx+h <= blank.shape[0]' "
assert anchory+w <= blank.shape[1], "Boundaries exceed, maintain 'anchory+w <= blank.shape[1]' "

tri_rtc = np.fromfunction(lambda i, j: i >= j, (h // 2 + 1, w // 2 + 1), dtype=int)
tri_ltc = np.flip(tri_rtc, axis=1)

rhombus = np.vstack((np.hstack((tri_ltc, tri_rtc[:, 1:])), np.flip(np.hstack((tri_ltc, tri_rtc[:, 1:])), axis=0)[1:, :]))
blank[anchorx:anchorx+h, anchory:anchory+w] = rhombus

print(blank)
plt.imshow(blank)
plt.show()
[[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 1. 1. 1. 0. 0. 0. 0.]
 [0. 0. 0. 0. 1. 1. 1. 1. 1. 0. 0. 0.]
 [0. 0. 0. 1. 1. 1. 1. 1. 1. 1. 0. 0.]
 [0. 0. 0. 0. 1. 1. 1. 1. 1. 0. 0. 0.]
 [0. 0. 0. 0. 0. 1. 1. 1. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]]

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

person sai    schedule 11.12.2020
comment
Я тоже рассмотрю это! Спасибо, что нашли время опубликовать свое решение - person ChocolateBear; 11.12.2020