Wilson
Wilson

Reputation: 255

Slicing ndarray using floats in Python

I want to construct an algorithm in Python to slice an image into n-by-n equal blocks. I am aware of existing Python packages that can do this, but I am interested in performing mathematical computations using the dimensions of the resulting blocks, which need not be integer values.

I have successfully converted my image input into ndarray using numpy and cv2. I am curious if I can slice an ndarray object in a way so that when I split a "pixel", I simply take the weighted average of that "pixel's" contribution to their respective block.

For example, if the image input have dimensions 100px x 100px, and I want to split this image into 7x7 blocks, since 100/7 = ~14.2857, each block will have dimensions 14.2857 x 14.2857. But it doesn't make sense to have a fraction of a pixel. Instead, I wish to interpret this so that the first block contains all of the information from pixels (1,1),(1,2),...,(1,14),(2,1),...,(3,1),...,(14,1),... ,(14,14), 0.2857 of all pixels satisfying (15,k) and (k,15), and 0.2857*0.2857 for pixel (15,15). I wish to do this for all 49 blocks.

Any insight would be appreciated! Thanks.

Upvotes: 2

Views: 195

Answers (1)

MB-F
MB-F

Reputation: 23637

As you correctly observed, you cannot have 14.29 pixel in an image. However you can perform downsampling to 14 (or less) pixels or upsampling to 15 (or more) pixels. I think the former is what you intend, but the latter is a viable option too.

An easy approach is to resize the image to a new size that is easily split into 7x7. If you use the correct function to do this (e.g. scipy.ndimage.interpolation.zoom) it will automatically interpolate pixel values.

For simplicity let's assume you want to split 5x5 image into 2x2 blocks:

from scipy.ndimage.interpolation import zoom
import numpy as np

blocks = (2, 2)

image = np.arange(25).reshape(5, 5) * 10
print(image)
# [[  0  10  20  30  40]
#  [ 50  60  70  80  90]
#  [100 110 120 130 140]
#  [150 160 170 180 190]
#  [200 210 220 230 240]]

# use np.ceil instead of np.floor to do upsampling
zoom_factor = [np.floor(s / b) * b / s for s, b in zip(image.shape, blocks)]

zoomed = zoom(image, zoom_factor)
print(zoomed)
# [[  0  14  26  40]
#  [ 69  83  95 109]
#  [131 145 157 171]
#  [200 214 226 240]]

for b1 in range(0, zoomed.shape[0], blocks[0]):
    for b2 in range(0, zoomed.shape[1], blocks[1]):
        print('block:')
        print(zoomed[b1:b1+blocks[0], :][:, b2:b2+blocks[1]])
# block:
# [[ 0 14]
#  [69 83]]
# block:
# [[ 26  40]
#  [ 95 109]]
# block:
# [[131 145]
#  [200 214]]
# block:
# [[157 171]
#  [226 240]]

Note that in the zoomed image the second pixel in the first row has a value of 14, which contains contributions from both neigbors 10 and 20. (It probably contains contribution from other pixels too because zoom uses by default an order 3 spline for interpolation). Other pixels in the middle of the image contain contribution from the entire surrounding neigborhood.

Upvotes: 1

Related Questions