Как сделать среднее из всех окон, содержащих рамку? - PullRequest
1 голос
/ 14 апреля 2019

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

Мое существующее решение

Мое первое решение этой проблемы - разделить мой массив на массив размера (3, 128, 128).Затем у меня есть список квадратов изображений, и я могу применить свою модель к каждому из этих квадратов, а затем объединить все результаты, чтобы получить новое (3, 128, n) изображение.Проблема этого метода заключается в том, что модель не так хорошо работает на краях изображения.

Мое желаемое решение

Чтобы обойти эту проблему, я подумал об альтернативном решении.Вместо того чтобы рассматривать непересекающиеся квадратные изображения, я могу рассмотреть все квадратные изображения, которые можно извлечь из моего исходного изображения.Я могу передать все эти изображения моей модели.Затем, чтобы восстановить точку координат (a, b, c), я рассмотрю все восстановленные квадратные изображения, которые содержат c, и возьму их среднее значение.Я хочу, чтобы это среднее значение придавало больший вес квадрату, где c находится недалеко от центра.Чтобы быть более конкретным:

  • Я начинаю с массива 3 * 128 * n (назовем его A).Я накладываю слева и справа, что дает мне новый массив (назовем его A_pad) размером 3*128*(n+2*127)
  • Для i в диапазоне (0, n + 127), пусть A_i = A_pad[:, :, i:i+128], A_i имеет размер (3 * 128 * 128) и может быть передан моей модели, которая создает новый массив B_i того же размера.
  • Теперь я хочу новый массив B того жеразмер, чем A, который определяется следующим образом: для каждого (x, y, z) B[x, y, z] является средним значением 128 B_i[x, y, z+127-i] таким, что z <= i <z + 128 с весом <code>1 + min(z + 127 -i, i-z).Это соответствует взятию среднего значения всех окон, содержащих z, с весом, пропорциональным расстоянию до ближайшего края.

Мой вопрос основан на вычислении B. Учитывая то, что я описалЯ мог бы написать несколько циклов for, которые бы дали правильные результаты, но я боюсь, что это будет медленно.Я ищу решение, использующее numpy, которое работает максимально быстро.

1 Ответ

2 голосов
/ 14 апреля 2019

Это пример реализации, который следует шагам, изложенным в разделе «Мое желаемое решение».Он широко использует np.lib.stride_tricks.as_strided, что на первый взгляд может показаться неочевидным;Я добавил подробные комментарии к каждому использованию для уточнения.Также обратите внимание, что в вашем описании вы используете z для обозначения позиции столбца в изображениях, в то время как в комментариях я использую термин n-position, чтобы соответствовать спецификации формы через n.

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

Строки, содержащие комментарий с префиксом # [TEST], были добавлены для тестирования.Конкретно это означает, что мы перезаписываем веса для окончательной суммы кадров с 1 / 128, чтобы в конечном итоге восстановить исходное изображение (поскольку преобразование модели ML также не применяется).

import numpy as np

n = 640  # For example.
image = np.random.randint(0, 256, size=(3, 128, n))
print('image.shape: ', image.shape)  # (3, 128, 640)

padded = np.pad(image, ((0, 0), (0, 0), (127, 127)), mode='edge')
print('padded.shape: ', padded.shape)  # (3, 128, 894)

sliding = np.lib.stride_tricks.as_strided(
    padded,
    # Frames stored along first dimension; sliding across last dimension of `padded`.
    shape=(padded.shape[-1]-128+1, 3, 128, 128),
    # First dimension: Moving one frame ahead -> move across last dimension of `padded`.
    # Remaining three dimensions: Move as within `padded`.
    strides=(padded.strides[-1:] + padded.strides)
)
print('sliding.shape: ', sliding.shape)  # (767, 3, 128, 128)

# Now at this part we would feed the frames `sliding` to the ML model,
# where the first dimension is the batch size.
# Assume the output is assigned to `sliding` again.
# Since we're not using an ML model here, we create a copy instead
# in order to update the strides of `sliding` with it's actual shape (as defined above).
sliding = sliding.copy()

sliding_128 = np.lib.stride_tricks.as_strided(
    # Reverse last dimension since we want the last column from the first frame.
    # Need to copy again because `[::-1]` creates a view with negative stride,
    # but we want actual reversal to work with the strides below.
    # (There's perhaps a smart way of adjusting the strides below in order to not make a copy here.)
    sliding[:, :, :, ::-1].copy(),
    # Second dimension corresponds to the 128 consecutive frames.
    # Previous last dimension is dropped since we're selecting the
    # column that corresponds to the current n-position.
    shape=(128, n, 3, 128),
    # First dimension (frame position): Move one frame and one column ahead
    #     (actually want to move one column less in `sliding` but since we reverted order of columns
    #      we need to move one ahead now) -> move across first dimension of `sliding` + last dimension of `sliding`.
    # Second dimension (n-position): Moving one frame ahead -> move across first dimension of `sliding`.
    # Remaining two dimensions: Move within frames (channel and row dimensions).
    strides=((sliding.strides[0] + sliding.strides[-1],) + sliding.strides[:1] + sliding.strides[1:3])
)
print('sliding_128.shape: ', sliding_128.shape)  # (128, 640, 3, 128)

# Weights are independent of the n-position -> we can precompute.
weights = 1 + np.concatenate([np.arange(64), np.arange(64)[::-1]])
weights = np.ones(shape=128)  # [TEST] Assign weights for testing -> want to obtain the original image back.
weights = weights.astype(float) / weights.sum()  # Normalize?
weights = weights[:, None, None, None]  # Prepare for broadcasting.

weighted_image = np.moveaxis(np.sum(sliding_128 * weights, axis=0), 0, 2)
print('weighted_image.shape: ', weighted_image.shape)  # (3, 128, 640)

assert np.array_equal(image, weighted_image.astype(int))  # [TEST]
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...