2015-03-06 1 views
1

Ниже приведен код, который уменьшает разрешение 2D-массива numpy (изображение) путем разбиения маленьких пикселей на большие пиксели. Мне интересно, можно ли это сделать быстрее, или если есть альтернативы, которые будут быстрее. Также приветствуются любые предложения в целом. Например, если есть код, который аналогичен по скорости, но которая производит более гладкое уменьшенное изображение (например, с помощью сплайна)Код Python для быстрого уменьшения разрешения изображения с помощью numpy

import numpy as np 

def reduce_img (img, bin_fac=1): 
    assert(len(img.shape) == 2) 
    imgX0 = img.shape[0] # slow axis 
    imgX1 = img.shape[1] # fast axis 

    r0 = imgX0 % bin_fac 
    r1 = imgX1 % bin_fac 

    img = np.copy(img) [:imgX0 - r0, :imgX1-r1] 
    # bin the fast axis 
    img_C = img.ravel(order='C') 
    img = np.average([ img_C[i::bin_fac] for i in xrange(bin_fac) ],axis=0) 
    img = img.reshape((imgX0-r0, (imgX1-r1)/bin_fac) , order='C') 

    # bin the slow axis 
    img_F = img.ravel(order='F') 
    img = np.average([ img_F[i::bin_fac] for i in xrange(bin_fac) ],axis=0) 
    img = img.reshape(((imgX0-r0)/bin_fac, (imgX1-r1)/bin_fac), order='F') 

    return img 

Вот результат

>> from pylab import * 
>> imshow(img) 
>> show() 

test image

>> img_r = reduce_img(img, bin_fac = 7) 
>> imshow(img_r) 
>> show() 

test image reduced

>> %timeit(reduce_img(img, bin_fac=7) ) 
1000 loops, best of 3: 655 µs per loop 
+1

Возможно, вы ищете ['scipy.misc.imresize'] (http://docs.scipy.org/doc/scipy/reference/generated/scipy.misc.imresize.html)? – Carsten

+1

Возможный дубликат [Повторная выборка массива numpy, представляющего изображение] (http://stackoverflow.com/questions/13242382/resampling-a-numpy-array-representing-an-image) – Carsten

+1

Я не могу получить ваш func. даже работать. Общее определение reseape us 'numpy.reshape (a, newshape, order = 'C')', и вы называете это так, как если бы вы выполнили 'img.reshape'. Форма вашего примерного изображения - '(600, 400, 4)', когда, т. Е. Читается с помощью 'np.imread'. Вы пытаетесь изменить его, поскольку это всего лишь кортеж '(x, y)'. Ваша новая форма должна быть '(x, y, 4)'. Вы вызываете один 'reshape' с' np.', но не второй. Кроме того, я не думаю, что ваш генератор expr. делают то, что вы думаете, что они делают. Если вы думаете, что они берут «np.average» небольшого блока пикселей, это не так. (по крайней мере, не так). – ljetibo

ответ

1

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

Во-первых, вы могли бы улучшить свой код немного, избавившись от лишнего вызова np.copy, так как вы только подменойimg с видом передаваемая в img. Операция ravel вернет копию тогда, если форма изображения не была кратной коэффициенту биннинга bin_fac.

Теперь, когда понимание списка выполняется быстро, вы воссоздаете массив numpy из возможно несмежного списка, что означает, что вы снова копируете память из одного места в другое. Это все операции, которые съедают эффективность.

Что вам может быть интересно, это просто генерировать высокоэффективный вид на исходном изображении. Вот где as_strided приходит в:

from numpy.lib.stride_tricks import as_strided 

def strided_rescale(g, bin_fac): 
    strided = as_strided(g, 
     shape=(g.shape[0]//bin_fac, g.shape[1]//bin_fac, bin_fac, bin_fac), 
     strides=((g.strides[0]*bin_fac, g.strides[1]*bin_fac)+g.strides)) 
    return strided.mean(axis=-1).mean(axis=-1) # order is NOT important! See notes.. 

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

In [263]: stp = 'from __main__ import img, strided_rescale, reduce_img' 

In [264]: for n in range(1,21): 
    a = timeit.timeit(stmt='strided_rescale(img, {})'.format(n), setup=stp, number=100) 
    b = timeit.timeit(stmt='reduce_img(img, {})'.format(n), setup=stp, number=100) 
    c = b*1./a 
    d = np.ptp(strided_rescale(img, n) - reduce_img(img,n)) 
    print('{a:7f} | {b:7f} | {c:1.4f} | {d:1.4g}'.format(**locals())) 
    .....:  
0.124911 | 0.277254 | 2.2196 | 0 
0.303813 | 0.171833 | 0.5656 | 0 
0.217745 | 0.188637 | 0.8663 | 0 
0.162199 | 0.139770 | 0.8617 | 0 
0.132355 | 0.138402 | 1.0457 | 0 
0.121542 | 0.160275 | 1.3187 | 0 
0.102930 | 0.162041 | 1.5743 | 0 
0.090694 | 0.138881 | 1.5313 | 2.384e-07 
0.097320 | 0.174690 | 1.7950 | 1.788e-07 
0.082376 | 0.155261 | 1.8848 | 2.384e-07 
0.084228 | 0.178397 | 2.1180 | 2.98e-07 
0.070411 | 0.181175 | 2.5731 | 2.98e-07 
0.075443 | 0.175605 | 2.3277 | 5.96e-08 
0.068964 | 0.182602 | 2.6478 | 5.96e-08 
0.067155 | 0.168532 | 2.5096 | 1.192e-07 
0.056193 | 0.195684 | 3.4824 | 2.98e-07 
0.063575 | 0.206987 | 3.2558 | 2.98e-07 
0.078850 | 0.187697 | 2.3804 | 2.384e-07 
0.053072 | 0.168763 | 3.1799 | 2.384e-07 
0.047512 | 0.151598 | 3.1907 | 1.788e-07 
# time a | time b | b/a | peak-to-peak: check if both arrays are the same 

Я считаю, что наблюдаемые незначительные различия в равенстве массива происходят из-за операций копирования, в которых вы переходите из numpy-типа данных обратно в обычный поплавок Python и наоборот. На этом я не уверен на 100%.

Теперь, когда разговор об оптимизации завершен, давайте вернемся к вашему методу binning. С вашей текущей реализацией вы разделили изображение в квадратных, неперекрывающихся зонах. Эти подматрицы не обязательно должны быть квадратными для остальной части этой истории, они могут быть прямоугольными (если соотношение сторон изображения можно изменить), и выводы все равно будут действительны. Итак, в каждой подматрице вы принимаете среднее значение строки, после чего вы берете среднее значение результирующего вектора столбца. Математически легко показать, что это то же самое, что взять среднее значение по всей подматрице. Это хорошая новость, потому что это означает, что в функции strided_rescale, показанной выше, вы могли бы просто заменить return заявление:

return gstr.mean(axis=(-2,-1)) 

который даст вам еще один (маленький) увеличение скорости.

Я думал, что предложение использовать scipy.misc.imresize было очень хорошим, пока я не попробовал его на ndarrays с dtype! = Np.uint8. И даже тогда, параметр mode должен быть выбран правильно, и это, кажется, принимает только верхнюю левую углы подматрицы:

In [39]: a = np.arange(16, dtype=np.uint8).reshape(4,4) 

In [40]: a 
Out[40]: 
array([[ 0, 1, 2, 3], 
     [ 4, 5, 6, 7], 
     [ 8, 9, 10, 11], 
     [12, 13, 14, 15]], dtype=uint8) 

In [41]: imresize(a, (2,2), mode='P') 
Out[41]: 
array([[ 0, 2], 
     [ 8, 10]], dtype=uint8) 

In [42]: imresize(a, (2,2), mode='L') 
Out[42]: 
array([[0, 1], 
     [6, 7]], dtype=uint8) 

Что, вероятно, не то, что вы были после. Таким образом, stride_tricks отлично работает для реального биннинга. Если вы хотите плавный поведение изменения размера (например, с использованием сплайновой интерполяции), вы будете смотреть на библиотеку изображений Python и все функции, которые используют ее под капотом или, например, OpenCV, который также обеспечивает изменение размера как summarized in this post.

+0

Большое спасибо. Это те трюки, которые мне были интересны! – dermen

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