Friday, 29 January 2021

Using medianBlur with a different quantile

I want to apply a function similar to medianBlur to an image, but instead of using the 0.5 quantile as medianBlur is doing, I want to apply another quantile (e.g. 0.15).

Outside of my toy example, i'll use this with 1000x1000px images with a large kernel size.

I wrote an implementation (apply_quantileblur) which is doing what I want, but non surprisingly it is quite slow.

import cv2
import numpy as np
from matplotlib import pyplot as plt


img = np.array([[  6, 249, 255, 255, 255, 249,   0,   6, 255, 255, 255, 255, 255,
        255, 255, 255, 255, 255, 255, 255],
       [252,   3,  34, 255, 252, 255, 170, 255, 252, 135, 255, 255, 255,
        255, 255, 255, 255, 255, 255, 255],
       [  0,   0, 255, 255, 255, 255,  83, 255,   0, 255,   0, 255, 255,
        255, 255, 255, 255, 255, 255, 255],
       [255,   0,   0, 207, 255,  48, 255,  48, 255,  48, 255, 207, 255,
        255, 207, 255, 255, 255, 255, 255],
       [245, 245,  38,   0,   0, 255,  86,   9, 245, 255, 245,   9, 101,
        255, 245, 255, 255, 255, 255, 255],
       [  0, 255,  32, 255, 255,   0,   0,   0,   0,   0, 255, 255,   0,
        255, 255, 255, 255, 255, 255, 255],
       [255, 255,  32, 255, 255,   0,  83,   0, 255,   0,   0,   0, 255,
        255, 255, 255, 255, 255, 255, 255],
       [239, 239, 225,   0, 239, 255, 239,   0,   0, 141,   0,   0, 255,
        255, 239,  15, 255, 255, 255, 255],
       [  0,   0, 201,  25, 255, 255, 229,   0,  25,   0, 229,  25,  25,
        229, 229,   0, 255, 255, 255, 255],
       [  0,   0,   0,   0,   0,   0, 172,   0, 255,   0,   0, 255, 255,
          0, 255, 255, 255, 255, 255, 255],
       [120, 255, 117,   0, 120, 255, 134,   0,   0,  64, 255,   0, 134,
        120, 120, 120, 255, 255, 255, 255],
       [ 76,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,  76,  28,
          0,   0,  76,  76,  76,  76, 255],
       [  0,   0, 223,   0,   0,   0,  83,   0,   0,   0,   0,   0,   0,
          0, 255, 255,   0, 255, 255, 255],
       [255, 210,  45,   0,  45,  45,  45,  45,  45,  23,   0,   0,   0,
         45,  45,  45, 210, 255, 255, 255],
       [ 89,   0, 255, 255,   0, 255, 141, 165, 165,  42, 165, 165,  89,
          0, 165,  89, 255, 255, 255, 255],
       [255, 255,  11,  12,  12, 255, 255, 255,   0, 255,  12,   0, 242,
        255, 255, 255, 255, 255, 255, 255],
       [255, 255, 246,   9, 245, 245, 255,   9, 255,   5, 245, 255, 255,
        255, 255, 255, 255, 255, 255, 255],
       [  0,   0,   0,   0,   0,   0,   0,   0,   0, 134, 255,   0,   0,
        255, 255, 255, 255, 255, 255, 255],
       [255, 217, 228, 255, 255,  38, 255, 255, 217, 121, 255, 255, 231,
        255, 255, 255, 255, 255, 255, 255],
       [255, 255, 230, 255,   0, 226, 255, 255, 255, 255, 255, 255, 255,
        255, 255, 255, 255, 255, 255, 255]]).astype(np.uint8)

    

plt.imshow(img)
plt.title('original image')
plt.show()

median_blurred_img = cv2.medianBlur(img, 5)
plt.imshow(median_blurred_img)
plt.title('median blurred image')
plt.show()


def apply_quantileblur(img, kernel_size, quantile):
    rows, cols = img.shape
    newmat = np.zeros((rows, cols))
    kerny = int((kernel_size - 1) / 2)
    for r in range(rows):
        rmin = max(r - kerny, 0)
        rmax = min(r + kerny + 1, rows)

        for c in range(cols):
            cmin = max(c - kerny, 0)
            cmax = min(c + kerny + 1, cols)

            miniimg  = img[rmin:rmax, cmin:cmax]
            newmat[r, c] = np.quantile(miniimg, quantile)

    return newmat

quantile_blurred_img = apply_quantileblur(img, 5, 0.15)
plt.imshow(quantile_blurred_img)
plt.title('0.15 quantile blurred image')
plt.show()

quantile_blurred_img = apply_quantileblur(img, 5, 0.85)
plt.imshow(quantile_blurred_img)
plt.title('0.85 quantile blurred image')
plt.show()

Which outputs:

these images

Since medianBlur is doing a very similar thing about 1000x quicker, I was wondering how to improve this process to actually be usable.

EDIT:

I've run a comparison between the 4 different methods there are so far:

from scipy.ndimage import generic_filter
import time
import diplib
# sudo apt install default-jre
# sudo apt-get install freeglut3
import cv2
import numpy as np
from matplotlib import pyplot as plt

def show_img(img, title):
    plt.imshow(img)
    plt.title(title)
    plt.show()


def apply_quantileblur_manual(img, kernel_size, quantile):
    rows, cols = img.shape
    newmat = np.zeros((rows, cols))
    kerny = int((kernel_size - 1) / 2)
    for r in range(rows):
        rmin = max(r - kerny, 0)
        rmax = min(r + kerny + 1, rows)

        for c in range(cols):
            cmin = max(c - kerny, 0)
            cmax = min(c + kerny + 1, cols)

            miniimg  = img[rmin:rmax, cmin:cmax]
            newmat[r, c] = np.quantile(miniimg, quantile)

    return newmat


def apply_quantileblur_generic(img, kernel_size, quantile):
    return generic_filter(img, lambda x: np.quantile(x, quantile), size=kernel_size)


def medianblur(img, kernel_size):
    return cv2.medianBlur(img, kernel_size)

def diplib_blur(img, kernel_size, quantile):
    k = diplib.Kernel([kernel_size,kernel_size])
    z = diplib.PercentileFilter(img, quantile, k)
    return z

img = np.random.randint(0,255,(500, 500), np.uint8)
cv2.imwrite('ugh.tiff', img)
dip_img = diplib.ImageReadTIFF('ugh.tiff')

kernel_size = 111
quantile = 50

start = time.time()
manual_img = apply_quantileblur_manual(img, kernel_size, quantile / 100)
print(time.time() - start)

start = time.time()
generic_img = apply_quantileblur_generic(img, kernel_size, quantile / 100)
print(time.time() - start)

start = time.time()
median_img = medianblur(img, kernel_size)
print(time.time() - start)

start = time.time()
diplib_img = diplib_blur(img, kernel_size, quantile)
print(time.time() - start)

which prints:

31.994836568832397
46.67619204521179
0.005266904830932617
1.999727725982666

So OpenCV is actually 10.000 faster, but without the option to specify the quantile.



from Using medianBlur with a different quantile

No comments:

Post a Comment