0

I would like to change a given block of the following image, and then rebuilding image back.

enter image description here

I would like to divide and change pixels from 224x224x3 blocks from such an image. As the image is 1024x1024x3, there will not be an integer number of 224x224x3 blocks, numpyso, I have to select a sub-area of the image to fit the block shape, like as follows:

>>> block_shape = np.array((224, 224, 3))
>>> nblocks = np.array(img.shape) // block_shape  # integer division
>>> crop_r, crop_c, crop_ch = nblocks * block_shape
>>> cropped_img = img[:crop_r, :crop_c, :crop_ch]

The cropped image is, therefore, a sub-area of the image that will select non-overlapping blocks of 224x224x3 pixels

>>> cropped_img.shape
(896, 896, 3)
>>> Blocks = view_as_blocks(cropped_img, block_shape=(224, 224, 3))
>>> Blocks.shape
(4, 4, 1, 224, 224, 3)

Now, just suppose I want to change the pixels of a given block, like zeroing all pixels of the first block:

Blocks[0,0,:,:,:,:]=Blocks[0,0,:,:,:,:]*0

Now I have the blocks with one block changed. Then, I need to recover cropped_img with that block changed (by converting variable Blocks back to be only one image/NumPy array), and finally, save the image. How to do that in Python?

P.s= I checked a similar thread (Reverse skimage view_as_blocks() with numpy.reshape()) but the image considered has a different shape, channels and also the order of channels is different.

mad
  • 2,677
  • 8
  • 35
  • 78

2 Answers2

1

The nice thing here is that view_as_blocks returns a view of the array. So if you modify the blocks in-place, you will have modified the original array and you won't need to "unblock":

from skimage import data, util
import matplotlib.pyplot as plt

image = data.camera()
blocked = util.view_as_blocks(image, (256, 256))
blocked[0, 0] *= 0

fig, ax = plt.subplots()
ax.imshow(image)
plt.show()

gives:

cameraman-blocked

If you do need to unblock, you want to think of your blocks as nested lists, and use calls to np.concatenate to concatenate the blocks on the leading axis along the remaining axes.

In this case, the leading axis is the rows of the blocks, and we want to concatenate along the rows of the remaining axes, which is axis 1:

intermediate = np.concatenate(blocked, axis=1)
print(intermediate.shape)

prints:

(2, 512, 256)

So we now have two "tall" blocks, and we want to concatenate them along the columns, which again will be axis 1 after ignoring the leading axis.

unblocked = np.concatenate(intermediate, axis=1)

fig, ax = plt.subplots()
ax.imshow(unblocked)
plt.show()

This shows the same image as above.

For your color image, the principle is the same, you just need to switch axis=1 for axis=2.

Juan
  • 5,433
  • 21
  • 23
  • Just one question: it seems that once called view_as_blocks, everything done in the blocked variable will be copied to the input image variable that contains the whole image (I can see that from your first example). Is that correct? – mad Feb 23 '22 at 07:42
  • 1
    Yes, but note that "will be copied" is confusing terminology. The issue is that the blocked image and the original image are both *views* into the same array memory. So modifying one modifies both. What you want to search for is "NumPy array views vs copies", which will take you to e.g. https://www.jessicayung.com/numpy-views-vs-copies-avoiding-costly-mistakes/ – Juan Feb 23 '22 at 23:29
1

If you are willing to use Imagemagick directly (or call if using Python subprocess or do the same using Python Wand, which is based upon Imagemagick), it is very simple. Even non-full-sized tiles are kept appropriately.

Input:

enter image description here

convert man.jpg -crop 50x50% man_tile_%02d.png

This produces a 2x2 array of tiles labeled man_tile_00.png man_tile_01.png man_tile_02.png and man_tile_03.png

enter image description here

enter image description here

enter image description here

enter image description here

Then to build it back:

convert man_tile_*.png -background none -flatten man_tile_reconstitute.jpg

enter image description here

fmw42
  • 46,825
  • 10
  • 62
  • 80