Search code examples
pythonopencvimage-processingcomputer-visionscikit-image

Generate image back after changing blocks with view_as_blocks in Python


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.


Solution

  • 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.