2

I have a binary image that I want to divide into 4 x 4 pixels of blocks and counts the number the number of black colour pixel in a block. If the sum of black colour pixel in a block is even, the corresponding block is assigned a value of 0. Otherwise, the value is 1. After that, save/write it into txt file so I can see the result.

I have tried with the code but got stuck

import matplotlib.pyplot as plt
import numpy as np
image = plt.imread('myplot1.png')
image = np.array(image)
image = image[:,:,1] #if RGB

print(image.shape)
for x in np.arange(0,image.shape[0]):
    for y in np.arange(image.shape[1]):
        if x+4 < image.shape[0] and y+4 < image.shape[1]:
             sum = np.sum(image[x:x+4,y:y+4])
             if sum > 4:
                image[x:x + 4, y:y + 4] = 1
             elif sum < 4:
                image[x:x + 4, y:y + 4] = 0
Dian Arief R
  • 69
  • 1
  • 9

2 Answers2

7

With help from the solution provided to this question about splitting up a 2D array into smaller blocks:

def block_view(A, block):
    # Reshape the array into a 2D array of 2D blocks, with the resulting axes in the
    # order of:
    #    block row number, pixel row number, block column number, pixel column number
    # And then rearrange the axes so that they are in the order:
    #    block row number, block column number, pixel row number, pixel column number
    return A.reshape(A.shape[0]//block[0], block[0], A.shape[1]//block[1], block[1])\
            .transpose(0, 2, 1, 3)

# Initial grayscale image
image = np.random.rand(16, 16)

# Boolean array where value is True if corresponding pixel in `image` is
# "black" (intensity less than 0.5)
image_bin = image < 0.5

# Create a 2D array view of 4x4 blocks
a = block_view(image_bin, (4, 4))

# XOR reduce each 4x4 block (i.e. reduce over last two axis), so even number
# of blacks is 0, else 1
a = np.bitwise_xor.reduce(a, axis=(-2, -1))

print(a.astype(np.uint8))

Example output from a 16x16 image:

[[0 1 1 0]
 [0 0 1 0]
 [1 1 1 1]
 [0 0 0 1]]

Edit:

The block_view() function was originally implemented following this answer (which uses as_strided()), however after more searching around, I decided to use a variation of this answer instead (which makes use of reshaping). Timing both methods, the latter was about 8 times faster (at least through my testing).

eugenhu
  • 1,168
  • 13
  • 22
  • Thanks, it helps me a lot! :) – Dian Arief R May 21 '18 at 08:46
  • 1
    @DianAriefRisdianto Oh no, I've made a huge error, `np.reshape(image_bin, (-1, 4*4))` is **not** the correct way to reshape the array into indexable 4x4 blocks, an easy way to see this to try `print(np.reshape(range(32), (-1, 4*4)))`. All it would do is collect up 16 consecutive elements instead of those in a 4x4 block. I have updated the question to fix this with many thanks from [an answer to another question](https://stackoverflow.com/a/5078155/8944057). Sorry I should have been more scrupulous when testing the solution before submitting it as an answer. – eugenhu May 21 '18 at 12:34
  • @eugenhu this mistake a nice example why einops is so useful – Alleo Jul 22 '20 at 07:12
2

Einops allows verbose reductions. In your case

import numpy as np
from einops import reduce

# Black / white image
image = np.random.rand(16, 16) < 0.5

# compute number of bright pixels in each block, then compute residual modulo 2
reduce(image, '(h h2) (w w2) -> h w', 'sum', h2=4, w2=4) % 2

Example output:

array([[0, 0, 1, 1],
       [1, 1, 0, 1],
       [1, 0, 1, 1],
       [0, 0, 1, 1]])
Alleo
  • 7,891
  • 2
  • 40
  • 30