scipy.ndimage.interpolation.zoom utiliza el algoritmo similar a un vecino más cercano para reducir la escala

Al probar la función de zoom de Scipy, descubrí que los resultados de descifrar una matriz son similares al algoritmo del vecino más cercano, en lugar de promediar. Esto aumenta el ruido drásticamente, y es generalmente subóptimo para muchas aplicaciones.

¿Hay alguna alternativa que no use el algoritmo similar al vecino más cercano y promueva correctamente la matriz cuando reduzca el tamaño? Mientras que el coarsegraining funciona para los factores de escalado de enteros, yo también necesitaría factores de escala no enteros.

Caso de prueba: cree una matriz aleatoria de 100 * M x 100 * M, para M = 2..20 Reduzca la escala de la matriz por el factor de M de tres maneras:

1) tomando la media en los bloques MxM 2) usando el zoom de scipy con un factor de escala 1 / M 3) tomando un primer punto dentro de un

Las matrices resultantes tienen la misma media, la misma forma, pero la matriz de scipy tiene la varianza tan alta como el vecino más cercano. Tomar un orden diferente para scipy.zoom realmente no ayuda.

import scipy.ndimage.interpolation import numpy as np import matplotlib.pyplot as plt mean1, mean2, var1, var2, var3 = [],[],[],[],[] values = range(1,20) # down-scaling factors for M in values: N = 100 # size of an array a = np.random.random((N*M,N*M)) # large array b = np.reshape(a, (N, M, N, M)) b = np.mean(np.mean(b, axis=3), axis=1) assert b.shape == (N,N) #coarsegrained array c = scipy.ndimage.interpolation.zoom(a, 1./M, order=3, prefilter = True) assert c.shape == b.shape d = a[::M, ::M] # picking one random point within MxM block assert b.shape == d.shape mean1.append(b.mean()) mean2.append(c.mean()) var1.append(b.var()) var2.append(c.var()) var3.append(d.var()) plt.plot(values, mean1, label = "Mean coarsegraining") plt.plot(values, mean2, label = "mean scipy.zoom") plt.plot(values, var1, label = "Variance coarsegraining") plt.plot(values, var2, label = "Variance zoom") plt.plot(values, var3, label = "Variance Neareset neighbor") plt.xscale("log") plt.yscale("log") plt.legend(loc=0) plt.show() 

introduzca la descripción de la imagen aquí

EDITAR: el rendimiento de scipy.ndimage.zoom en una imagen realmente ruidosa también es muy pobre

introduzca la descripción de la imagen aquí

La imagen original está aquí http://wiz.mit.edu/lena_noisy.png

El código que lo produjo:

 from PIL import Image import numpy as np import matplotlib.pyplot as plt from scipy.ndimage.interpolation import zoom im = Image.open("/home/magus/Downloads/lena_noisy.png") im = np.array(im) plt.subplot(131) plt.title("Original") plt.imshow(im, cmap="Greys_r") plt.subplot(132) im2 = zoom(im, 1 / 8.) plt.title("Scipy zoom 8x") plt.imshow(im2, cmap="Greys_r", interpolation="none") im.shape = (64, 8, 64, 8) im3 = np.mean(im, axis=3) im3 = np.mean(im3, axis=1) plt.subplot(133) plt.imshow(im3, cmap="Greys_r", interpolation="none") plt.title("averaging over 8x8 blocks") plt.show() 

Nadie publicó una respuesta de trabajo, por lo que publicaré una solución que utilizo actualmente. No es el más elegante, sino que funciona.

 import numpy as np import scipy.ndimage def zoomArray(inArray, finalShape, sameSum=False, zoomFunction=scipy.ndimage.zoom, **zoomKwargs): """ Normally, one can use scipy.ndimage.zoom to do array/image rescaling. However, scipy.ndimage.zoom does not coarsegrain images well. It basically takes nearest neighbor, rather than averaging all the pixels, when coarsegraining arrays. This increases noise. Photoshop doesn't do that, and performs some smart interpolation-averaging instead. If you were to coarsegrain an array by an integer factor, eg 100x100 -> 25x25, you just need to do block-averaging, that's easy, and it reduces noise. But what if you want to coarsegrain 100x100 -> 30x30? Then my friend you are in trouble. But this function will help you. This function will blow up your 100x100 array to a 120x120 array using scipy.ndimage zoom Then it will coarsegrain a 120x120 array by block-averaging in 4x4 chunks. It will do it independently for each dimension, so if you want a 100x100 array to become a 60x120 array, it will blow up the first and the second dimension to 120, and then block-average only the first dimension. Parameters ---------- inArray: n-dimensional numpy array (1D also works) finalShape: resulting shape of an array sameSum: bool, preserve a sum of the array, rather than values. by default, values are preserved zoomFunction: by default, scipy.ndimage.zoom. You can plug your own. zoomKwargs: a dict of options to pass to zoomFunction. """ inArray = np.asarray(inArray, dtype=np.double) inShape = inArray.shape assert len(inShape) == len(finalShape) mults = [] # multipliers for the final coarsegraining for i in range(len(inShape)): if finalShape[i] < inShape[i]: mults.append(int(np.ceil(inShape[i] / finalShape[i]))) else: mults.append(1) # shape to which to blow up tempShape = tuple([i * j for i, j in zip(finalShape, mults)]) # stupid zoom doesn't accept the final shape. Carefully crafting the # multipliers to make sure that it will work. zoomMultipliers = np.array(tempShape) / np.array(inShape) + 0.0000001 assert zoomMultipliers.min() >= 1 # applying scipy.ndimage.zoom rescaled = zoomFunction(inArray, zoomMultipliers, **zoomKwargs) for ind, mult in enumerate(mults): if mult != 1: sh = list(rescaled.shape) assert sh[ind] % mult == 0 newshape = sh[:ind] + [sh[ind] // mult, mult] + sh[ind + 1:] rescaled.shape = newshape rescaled = np.mean(rescaled, axis=ind + 1) assert rescaled.shape == finalShape if sameSum: extraSize = np.prod(finalShape) / np.prod(inShape) rescaled /= extraSize return rescaled myar = np.arange(16).reshape((4,4)) rescaled = zoomArray(myar, finalShape=(3, 5)) print(myar) print(rescaled) 

FWIW encontré que orden = 1 al menos conserva la media mucho mejor que el valor predeterminado o orden = 3 (como se espera realmente)