user972014
user972014

Reputation: 3856

Efficient blurring of image using a mask in python

I need to blur specific areas of an image. I get the image and a mask depicting areas in the image that needs to be blurred. It works, but a bit slower than expected, given that I need to blur dozens of images.

Here is the code I use:

def soft_blur_with_mask(image: np.ndarray, mask: np.ndarray) -> np.ndarray:
    assert len(mask.shape) == 2, mask.shape
    # Create a blurred copy of the original image. This can take up to 1-2 seconds today, because the image is big (~5-10 Megapixels)
    blurred_image = cv2.GaussianBlur(image, (221, 221), sigmaX=20, sigmaY=20)
    image_height, image_width = image.shape[:2]
    mask = cv2.resize(mask.astype(np.uint8), (image_width, image_height), interpolation=cv2.INTER_NEAREST)
    # Blurring the mask itself to get a softer mask with no firm edges
    mask = cv2.GaussianBlur(mask.astype(np.float32), (11, 11), 10, 10)[:, :, None]
    mask = mask/255.0

    # Take the blurred image where the mask it positive, and the original image where the image is original
    return (mask * blurred_image + (1.0 - mask) * image).clip(0, 255.0).astype(np.uint8)

Upvotes: 5

Views: 5560

Answers (2)

nathancy
nathancy

Reputation: 46600

Here's a method using Numpy slicing

  • Convert mask to grayscale and find contours on the mask
  • Iterate through contours and extract ROI
  • Blur each ROI and replace in original image

Input and mask image

Grab ROI using Numpy slicing (left), blur ROI (right)

Replace blurred ROI back into original image

This method should be faster since you're taking advantage of Numpy slicing

import cv2
import numpy as np

def soft_blur_with_mask(image: np.ndarray, mask: np.ndarray) -> np.ndarray:
    mask = cv2.cvtColor(mask, cv2.COLOR_BGR2GRAY)
    cnts = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    cnts = cnts[0] if len(cnts) == 2 else cnts[1]
    for c in cnts:
        x,y,w,h = cv2.boundingRect(c)
        ROI = image[y:y+h, x:x+w]
        image[y:y+h, x:x+w] = cv2.GaussianBlur(ROI, (41,41), 0)
    return image

if __name__ == '__main__':
    image = cv2.imread('1.png')
    mask = cv2.imread('mask.png')

    result = soft_blur_with_mask(image, mask)

    cv2.imshow('result', result)
    cv2.waitKey()

Upvotes: 0

Piotr Siekański
Piotr Siekański

Reputation: 1715

You need to use different algorithm for blurring. Lets define two parameters: n - the number of pixels in the image and r the window size of the Gaussian blur filter.

You use a very big kernel - 221x221 pixels so r is equal to 221. That requires 221^2 = 48841 operations per pixel using standard convolution approach. That will result in computational complexity of O(r^2*n). However you can use Central Limit Theorem and approximate the kernel used in blurring with a series of box filters separately in two directions. You can achieve even faster processing time if you use the fact that neighborhood of two consecutive pixels differs in only one pixel. Finally you can get computational complexity that is independent of the window size: O(n). See this link that explain the whole process. It is written in Javascript but the math is simple and well explained so you can definitely implement it using Python.

Upvotes: 2

Related Questions