2016-06-15 1 views
5

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

Эта функция принимает изображение как 1 одномерный массив, и перебирает отдельных элементов в массиве (пикселей в исходном изображении), который влияет на выходной массив, который является также изображение, представленное в виде 1 одномерного массива

пример: один пиксель на входном изображении (красный) влияет 8 окружающих пикселей в (оранжевый) example 1

базовая реализация в C является

/* C version 
* Given an input image create an 
* output image that is shaped by individual pixels 
* of the input image 
*/ 

int image[width * height]; //image retrieved elsewhere 
int output [width * height]; //output image 
int y = 0, x = 0; 
for(y = 1; y < height-1 ; ++ y) { 
    for(x = 1; x < width-1; ++ x) { 
     if (image[y * width + x] > condition) { 
      /* pixel affects the surrounding 8 pixels in the output image */ 

      output[(y-1) * width + x - 1]++; /* upper left */ 
      output[(y-1) * width + x ]++; /* above  */ 
      output[(y-1) * width + x + 1]++; /* upper right */ 
      output[y * width + x + 1 ]++; /* right  */ 
      output[y * width + x - 1 ]++; /* left  */ 
      output[(y+1) * width + x - 1]++; /* lower left */ 
      output[(y+1) * width + x ]++; /* below  */ 
      output[(y+1) * width + x + 1]++; /* lower right */ 


     } 
    } 
} 

наивный подход питона будет использовать точно такой же поэлементно доступ, как показано ниже

#Python version 
input = blah # formed elsewhere 
output = np.zeros(width * height) 
for y in xrange(1, height-1): 
    for x in xrange(1, width-1): 
     if input[y * width + x] > condition: 
      output[(y-1) * width + x - 1]+= 1; # upper left 
      output[(y-1) * width + x ]+= 1; # above  
      output[(y-1) * width + x + 1]+= 1; # upper right 
      output[y * width + x + 1 ]+= 1; # right  
      output[y * width + x - 1 ]+= 1; # left   
      output[(y+1) * width + x - 1]+= 1; # lower left 
      output[(y+1) * width + x ]+= 1; # below  
      output[(y+1) * width + x + 1]+= 1; # lower right 

есть лучший способ осуществить это? Можно ли векторизовать эту функцию?

ответ

0

Предположим, что arr представляет входной массив, а thresh - это пороговое значение для сравнения с каждым из входных элементов. Теперь мы можем порождать входной массив против данного порога, предоставляя нам маску/булевский массив. Затем мы можем выполнить 2D свертку и вычесть из нее 1s, где у нас было значение True для порожденного массива.

Таким образом, реализация будет выглядеть примерно так -

from scipy.signal import convolve2d 

# Get thresholded mask as int array & set first, last cols and rows as 0s 
mask = (arr > thresh).astype(int) 
mask[[0,-1]] = 0 
mask[:,[0,-1]] = 0 

# Perform 2D convolution and subtract 1s corresponding to True elems in mask 
out = convolve2d(mask,np.ones((3,3),dtype=int),'same') - mask 
5

Если я понял вопрос правильно, то этот подход может быть с ног на голову: если пиксель имеет в его окрестности точек, которые соответствуют условию, увеличивайте его на 1 для каждого совпадения. Сделайте это для всех пикселей. SciPy (среди прочих) предлагает инструменты для filtering images:

In [51]: import scipy.ndimage 

Создать изображение образца из 1-мерного массива. Reshape создает представление вместо копирования:

In [62]: I1d 
Out[62]: 
array([ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
     0, 0, 0, 0, 0, 129, 0, 129, 129, 0, 0, 0, 0, 
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 129, 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, 129]) 

In [63]: height 
Out[63]: 8 

In [64]: width 
Out[64]: 8 

In [65]: I = I1d.reshape((height, width)) 

In [66]: I 
Out[66]: 
array([[ 0, 0, 0, 0, 0, 0, 0, 0], 
     [ 0, 0, 0, 0, 0, 0, 0, 0], 
     [ 0, 0, 129, 0, 129, 129, 0, 0], 
     [ 0, 0, 0, 0, 0, 0, 0, 0], 
     [ 0, 0, 0, 0, 129, 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, 129]]) 

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

In [67]: scipy.ndimage.filters.convolve(
    (I > 128).astype(np.int), # conditioned binary image 
    weights=np.array([[1, 1, 1], # each match weighted as 1 
         [1, 0, 1], 
         [1, 1, 1]]), 
    mode='constant', cval=0) # Use zeros as constant fill values on edges 
Out[67]: 
array([[0, 0, 0, 0, 0, 0, 0, 0], 
     [0, 1, 1, 2, 2, 2, 1, 0], 
     [0, 1, 0, 2, 1, 1, 1, 0], 
     [0, 1, 1, 3, 3, 3, 1, 0], 
     [0, 0, 0, 1, 0, 1, 0, 0], 
     [0, 0, 0, 1, 1, 1, 0, 0], 
     [0, 0, 0, 0, 0, 0, 1, 1], 
     [0, 0, 0, 0, 0, 0, 1, 0]]) 

In [68]: conv = _ 

Если конечная цель состоит в том, чтобы добавить оригинал и приращения:

In [69]: I + conv 
Out[69]: 
array([[ 0, 0, 0, 0, 0, 0, 0, 0], 
     [ 0, 1, 1, 2, 2, 2, 1, 0], 
     [ 0, 1, 129, 2, 130, 130, 1, 0], 
     [ 0, 1, 1, 3, 3, 3, 1, 0], 
     [ 0, 0, 0, 1, 129, 1, 0, 0], 
     [ 0, 0, 0, 1, 1, 1, 0, 0], 
     [ 0, 0, 0, 0, 0, 0, 1, 1], 
     [ 0, 0, 0, 0, 0, 0, 1, 129]]) 

чтобы вывести 1-мерный массив, как ш ell, используйте либо ravel(), либо flatten(). Бывший создает 1-мерный вид оригинальный 2-мерный массив, последний создает уплощенную копию:

In [70]: conv.ravel() 
Out[70]: 
array([0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 2, 2, 1, 0, 0, 1, 0, 2, 1, 1, 1, 
     0, 0, 1, 1, 3, 3, 3, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 
     0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0]) 
3

Я думаю, что вы пытаетесь сделать, это проще всего с индексацией 2D массива. Вы можете легко преобразовывать массивы с помощью numpy. Данные не копируются. Новый 2D-массив просто обеспечивает удобный способ индексирования тех же значений, которые хранятся в исходном массиве. Вот пример кода.

#imports 
import numpy as np 


# Setup 
Nx = 5 
Ny = 7 
cutoff = 3.0 
arr_input = np.array([[0, 0, 0, 0, 0, 0, 9], 
         [0, 4, 0, 0, 0, 0, 0], 
         [0, 0, 0, 0, 0, 3, 0], 
         [0, 2, 0, 0, 0, 0, 1], 
         [0, 0, 0, 0, 5, 0, 0]]).flatten() 

# Make an array of center locations with input value bigger than the cutoff value 
centers_array_2d = np.where(arr_input>=cutoff, 1.0, 0.0) 

# Initialize the output array 
arr_output = np.zeros_like(centers_array_2d) 

# Reshape the arrays to use 2D indexing 
ai = centers_array_2d.reshape(Nx, Ny) 
ao = arr_output.reshape(Nx, Ny) 

# Do the neighbor calculation with numpy indexing rules 
ao[:-1, :-1] += ai[1:, 1:]    # lower left 
ao[:, :-1] += ai[:, 1:]     # lower center 
ao[1:, :-1] += ai[:-1, 1:]    # lower right 
ao[:-1, :] += ai[1:, :]     # middle left 
# ao[:, :] += ai[:, :]     # middle center 
ao[1:, :] += ai[:-1, :]     # middle right 
ao[:-1, 1:] += ai[1:, :-1]    # top left 
ao[:, 1:] += ai[:, :-1]     # top center 
ao[1:, 1:] += ai[:-1, :-1]    # top right 

# Reshape the output array to return a 1D array like the input 
arr_output = ao.flatten() 

# Print the results 
print('input1d: \n{}\n'.format(arr_input)) 
print("2D array 'ai':\n{}\n".format(ai)) 
print("2D array 'ao':\n{}\n".format(ao)) 
print('output1d: \n{}\n'.format(arr_output)) 

Массивы следующие.

input1d: 
[0 0 0 0 0 0 9 0 4 0 0 0 0 0 0 0 0 0 0 3 0 0 2 0 0 0 0 1 0 0 0 0 5 0 0] 

2D array 'ai': 
[[ 0. 0. 0. 0. 0. 0. 1.] 
[ 0. 1. 0. 0. 0. 0. 0.] 
[ 0. 0. 0. 0. 0. 1. 0.] 
[ 0. 0. 0. 0. 0. 0. 0.] 
[ 0. 0. 0. 0. 1. 0. 0.]] 

2D array 'ao': 
[[ 1. 1. 1. 0. 0. 1. 0.] 
[ 1. 0. 1. 0. 1. 2. 2.] 
[ 1. 1. 1. 0. 1. 0. 1.] 
[ 0. 0. 0. 1. 2. 2. 1.] 
[ 0. 0. 0. 1. 0. 1. 0.]] 

output1d: 
[ 1. 1. 1. 0. 0. 1. 0. 1. 0. 1. 0. 1. 2. 2. 1. 1. 1. 0. 1. 0. 1. 0. 0. 0. 1. 2. 2. 1. 0. 0. 0. 1. 0. 1. 0.] 

Это расчет, который вы ищите? Это то, что я бы назвал векторизацией кода, который вы дали. Кроме того, вы можете составить список индексов для 1D-массивов, соответствующих каждому соседу. Это, по сути, то, что происходит внутри, когда я использую индексы 2D-сегмента для доступа к элементам 2D-массивов.

Смежные вопросы