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

Я пытаюсь написать функцию для загрузки и обработки данных для NN.В качестве входных данных у меня есть набор картинок разных размеров.Изображения должны быть представлены в виде трехмерного массива с каналами RGB.Мне нужно, чтобы они были одинакового размера (размер самой большой картинки).

Я пробовал np.pad, но, похоже, я не понял, как это должно работать.И на самом деле, даже если бы я получил отступ, я не знаю, как его изменить в зависимости от размера картинки.Вот код:

from PIL import Image
import numpy as np
import cv2
import os


def load_data(path):
    aminoacids = ['Ala','Arg','Asn','Asp','Cys','Gln','Glu','Gly','His','Ile', 'Ini', 'Leu','Lys','Met','Phe','Pro','Pyr', 'Sec','Ser','Thr','Trp','Tyr','Val']
    matrix = []
    answer_labeled = []
    names = os.listdir(path)

    for i in names:
      matrix = cv2.imread(path + i, 1)
      matrix = np.pad(matrix, (0, 1), 'constant', constant_values=[255,255,255])
      for y in aminoacids:
        if y + "-" in i:
          a = [matrix, y]
          answer_labeled.append(a)
    return answer_labeled
data_processed = load_data("/content/drive/My Drive/Thesis/dl/img/ans/")

Я получаю эту ошибку:

ValueErrorTraceback (most recent call last)
<ipython-input-50-e021738e59ea> in <module>()
     20     return answer_labeled
     21 
---> 22 data_processed = load_data("/content/drive/My Drive/Thesis/dl/img/ans/")
     23 
     24 # print(len(os.listdir("/content/drive/My Drive/Thesis/dl/img/ans/")))

<ipython-input-50-e021738e59ea> in load_data(path)
     13     for i in names:
     14       matrix = cv2.imread(path + i, 1)
---> 15       matrix = np.pad(matrix, (0, 1), 'constant', constant_values=[255,255,255])
     16       for y in aminoacids:
     17         if y + "-" in i:

/usr/local/lib/python2.7/dist-packages/numpy/lib/arraypad.pyc in pad(array, pad_width, mode, **kwargs)
   1208                 kwargs[i] = _as_pairs(kwargs[i], narray.ndim, as_index=True)
   1209             if i in ['end_values', 'constant_values']:
-> 1210                 kwargs[i] = _as_pairs(kwargs[i], narray.ndim)
   1211     else:
   1212         # Drop back to old, slower np.apply_along_axis mode for user-supplied

/usr/local/lib/python2.7/dist-packages/numpy/lib/arraypad.pyc in _as_pairs(x, ndim, as_index)
    951     # Converting the array with `tolist` seems to improve performance
    952     # when iterating and indexing the result (see usage in `pad`)
--> 953     return np.broadcast_to(x, (ndim, 2)).tolist()
    954 
    955 

/usr/local/lib/python2.7/dist-packages/numpy/lib/stride_tricks.pyc in broadcast_to(array, shape, subok)
    180            [1, 2, 3]])
    181     """
--> 182     return _broadcast_to(array, shape, subok=subok, readonly=True)
    183 
    184 

/usr/local/lib/python2.7/dist-packages/numpy/lib/stride_tricks.pyc in _broadcast_to(array, shape, subok, readonly)
    127     it = np.nditer(
    128         (array,), flags=['multi_index', 'refs_ok', 'zerosize_ok'] + extras,
--> 129         op_flags=[op_flag], itershape=shape, order='C')
    130     with it:
    131         # never really has writebackifcopy semantics

ValueError: operands could not be broadcast together with remapped shapes [original->remapped]: (3,) and requested shape (3,2)

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

Ответы [ 2 ]

1 голос
/ 26 сентября 2019

Мне когда-то приходилось решать подобную задачу, поэтому я создал для нее следующую функцию.Позволяет указать долю разницы в размерах для каждого измерения, которая должна быть дополнена до и после (аналогично np.pad).Например, если у вас есть два массива формы (3,) и (5,), то before=1 будет заполнять всю разницу (в данном случае 2) слева, тогда как before=0.5 дополняет один элемент слева и один направо.Подобно np.pad эти факторы могут быть указаны и для каждого измерения.Вот реализация:

import numpy as np


def pad_max_shape(arrays, before=None, after=1, value=0, tie_break=np.floor):
    """Pad the given arrays with a constant values such that their new shapes fit the biggest array.

    Parameters
    ----------
    arrays : sequence of arrays of the same rank
    before, after : {float, sequence, array_like}
        Similar to `np.pad -> pad_width` but specifies the fraction of values to be padded before
        and after respectively for each of the arrays.  Must be between 0 and 1.
        If `before` is given then `after` is ignored.
    value : scalar
        The pad value.
    tie_break : ufunc
        The actual number of items to be padded _before_ is computed as the total number of elements
        to be padded times the `before` fraction and the actual number of items to be padded _after_
        is the remainder. This function determines how the fractional part of the `before` pad width
        is treated. The actual `before` pad with is computed as ``tie_break(N * before).astype(int)``
        where ``N`` is the total pad width. By default `tie_break` just takes the `np.floor` (i.e.
        attributing the fraction part to the `after` pad width). The after pad width is computed as
        ``total_pad_width - before_pad_width``.

    Returns
    -------
    padded_arrays : list of arrays

    Notes
    -----
    By default the `before` pad width is computed as the floor of the `before` fraction times the number
    of missing items for each axis. This is done regardless of whether `before` or `after` is provided
    as a function input. For that reason the fractional part of the `before` pad width is attributed
    to the `after` pad width (e.g. if the total pad width is 3 and the left fraction is 0.5 then the
    `before` pad width is 1 and the `after` pad width is 2; in order to f). This behavior can be controlled
    with the `tie_break` parameter.
    """
    shapes = np.array([x.shape for x in arrays])
    if before is not None:
        before = np.zeros_like(shapes) + before
    else:
        before = np.ones_like(shapes) - after
    max_size = shapes.max(axis=0, keepdims=True)
    margin = (max_size - shapes)
    pad_before = tie_break(margin * before.astype(float)).astype(int)
    pad_after = margin - pad_before
    pad = np.stack([pad_before, pad_after], axis=2)
    return [np.pad(x, w, mode='constant', constant_values=value) for x, w in zip(arrays, pad)]

Для вашего примера вы можете использовать его следующим образом:

test = [np.ones(shape=(i, i, 3)) for i in range(5, 10)]
result = pad_max_shape(test, before=0.5, value=255)

print([x.shape for x in result])
print(result[0][:, :, 0])

Это приводит к следующему выводу:

[(9, 9, 3), (9, 9, 3), (9, 9, 3), (9, 9, 3), (9, 9, 3)]
[[255. 255. 255. 255. 255. 255. 255. 255. 255.]
 [255. 255. 255. 255. 255. 255. 255. 255. 255.]
 [255. 255.   1.   1.   1.   1.   1. 255. 255.]
 [255. 255.   1.   1.   1.   1.   1. 255. 255.]
 [255. 255.   1.   1.   1.   1.   1. 255. 255.]
 [255. 255.   1.   1.   1.   1.   1. 255. 255.]
 [255. 255.   1.   1.   1.   1.   1. 255. 255.]
 [255. 255. 255. 255. 255. 255. 255. 255. 255.]
 [255. 255. 255. 255. 255. 255. 255. 255. 255.]]

Итак, мыможно увидеть, что каждый массив был дополнен симметрично по форме самого большого массива (9, 9, 3).

0 голосов
/ 26 сентября 2019

Использование np.pad() на самом деле довольно хорошо задокументировано .

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

import numpy as np


arr = np.random.randint(0, 255, (72, 72, 3))

new_arr = np.pad(
    arr, ((0, 92 - 72), (0, 92 - 72), (0, 0)),
    'constant', constant_values=255)

print(new_arr.shape)
# (92, 92, 3)

РЕДАКТИРОВАТЬ

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


Если вы можете уместить все своиизображения в памяти, самый простой подход будет просто использовать fc.extra.multi_reframe(), то есть:

import flyingcircus as fc


new_arrs = fc.extra.multi_reframe(arrs, background=255)

Если вы не можете поместить все данные в память, вы должны сделать этов два прохода, один для вычисления минимальной формы, подходящей для всех ваших входов, а затем выполнить фактическое заполнение с помощью fc.extra.reframe():

# assume your data is loaded with `load(filepath)`
# ... and saved with `save(array, filepath)`

# : first pass
shapes = [load(filepath).shape for filepath in filepaths]
target_shape = tuple(np.max(np.array(shapes), axis=0))

# : second pass
for filepath in filepaths:
    arr = load(filepath)
    new_arr = fc.extra.reframe(arr, target_shape, 255)
    save(new_arr, filepath)

Внутренне, fc.extra.reframe() - этоиспользуя np.pad() (или что-то похожее на это, но быстрее), и это примерно эквивалентно:

def reframe(arr, target_shape, position=0.5, *args, **kws):
    source_shape = arr.shape
    padding = tuple(
        (int(position * (dim_target - dim_source)),
         (dim_target - dim_source) - int(position * (dim_target - dim_source)))
        for dim_target, dim_source in zip(target_shape, source_shape))
    return np.pad(arr, padding, *args, **kws)


reframe(arr, target_shape, 0.5, 'constant', constant_values=255)

обратите внимание, что параметры position определяют, где находится массив относительно новой фигуры.Значение по умолчанию 0.5 будет размещать все изображения в центре, тогда как 0.0 или 1.0 будет сдвигать его в одну или другую сторону новой формы по всем осям.Его версия FlyingCircus немного более гибкая, так как вы можете указать значение position для всех осей отдельно.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...