Copy the center pixel value to the majority value in the block

Viewed 678

I have a image pixel prediction array that is of size 9085x10852. I want to get a 10x10 block around each pixel. if the center pixel value is different from the majority pixel values in the block, then replace the center pixel value with the majority value. Can anyone help me please

3 Answers

I was digging around in scikit-image looking for something else today and if you dig down into scikit-image.filters and then even further into rank, you come across modal()! See documentation here.

I used the same random seed as @Tonechas to generate comparable results:

import numpy as np
from skimage.morphology import rectangle   # for Structuring Elements (e.g. disk, rectangle)
from skimage.filters.rank import modal     # the puppy we want

# Same seed for directly comparable results
np.random.seed(329)

# Sample array/image
arr = np.random.randint(low=0, high=10, size=(6, 8), dtype=np.uint8)

# Run the filter with a 5x5 rectangular Structuring Element
result = modal(arr,rectangle(5,5))

print(result)

array([[9, 2, 0, 0, 0, 2, 4, 5],
       [1, 1, 0, 0, 0, 2, 5, 2],
       [1, 1, 0, 5, 5, 5, 5, 5],
       [1, 1, 1, 5, 5, 5, 4, 5],
       [1, 1, 1, 1, 5, 5, 4, 5],
       [1, 1, 5, 5, 5, 5, 4, 4]], dtype=uint8)

Keywords: Python, numpy, scikit, skimage, image processing, process images, median, mode, modal, structuring element, morphology, filter, filters, rank.

As you seem a little unsure/inconsistent about the details of your question, I would like to suggest a very simple, alternative solution, not in Python but too big for a comment, that helps you and others explore whether you really want this and what you actually want - before anyone spends hours coding Python.

I suggest you use ImageMagick which is installed on most Linux distros and is available for macOS and Windows. So, just in Terminal, you can make a sample image which is black with a white square in the middle, like this:

convert -size 100x100 xc:black -fill white -draw "rectangle 10,10 90,90" test.png

enter image description here

Now try your filter and you can see the corners are rounded:

convert test.png -statistic mode 10x10 result.png

enter image description here

Now try again with a bigger "radius":

convert test.png -statistic mode 20x20 result.png

enter image description here

Maybe you can experiment with that and see if it does what you want before anyone wastes too much time coding anything.

One possible approach could be that of defining a function that replaces the central value by the mode...

import numpy as np
from scipy.ndimage import generic_filter

def most_frequent(x):
    central = x[x.size//2] 
    values, counts = np.unique(x, return_counts=True)
    max_freq = counts.max()
    modes = values[counts == max_freq]
    if central in modes:
        return central
    else:
        return modes[0]

... and passing such a function to scipy.ndimage.generic_filter.

Demo

In [143]: r = 2

In [144]: block_size = (2*r + 1, 2*r + 1)

In [145]: block_size
Out[145]: (5, 5)

In [146]: np.random.seed(329)

In [147]: arr = np.random.randint(low=0, high=10, size=(6, 8), dtype=np.uint8)

In [148]: arr
Out[148]: 
array([[9, 6, 2, 2, 0, 5, 6, 4],
       [9, 7, 0, 2, 0, 5, 4, 2],
       [1, 3, 8, 1, 4, 6, 5, 2],
       [5, 1, 7, 8, 5, 7, 0, 2],
       [8, 1, 0, 5, 4, 5, 4, 5],
       [4, 1, 5, 3, 6, 9, 4, 3]], dtype=uint8)

In [149]: generic_filter(arr, function=most_frequent, 
     ...:                size=block_size, mode='constant', cval=np.nan)
     ...: 
Out[149]: 
array([[9, 2, 2, 2, 0, 2, 4, 5],
       [9, 1, 0, 2, 0, 2, 5, 2],
       [1, 1, 0, 5, 5, 5, 5, 5],
       [1, 1, 1, 5, 5, 5, 4, 5],
       [1, 1, 1, 5, 5, 5, 4, 5],
       [1, 1, 5, 5, 5, 5, 4, 4]], dtype=uint8)

Notice that this code might take a while to run on a 9085×10852 array.

Related