Home > OS >  How to improve a variable lens blur algorithm in Python OpenCV?
How to improve a variable lens blur algorithm in Python OpenCV?

Time:06-26

I want to emulate the blur of a cheap camera lens (like input image output image

I've chosen a maximum blur of 5 (this is the sigma, in pixels, of the Gaussian, not the footprint of the kernel), and blur_map here scales this sigma with a factor between 0 in the middle and 1 at the corners of the image.

Another interesting effect would be as follows, with increasing blur tangential to each circle centered in the middle of the image, with very little blur radially:

angle_map = dip.CreatePhiCoordinate(img.Sizes())
img_blur = dip.AdaptiveGauss(img, [angle_map, blur_map], sigmas=[8,1])

output image

CodePudding user response:

Here is one way to apply (uniform, non-varying) lens defocus blur in Python/OpenCV by transforming both the image and filter to the Fourier (frequency) domain.

  • Read the input
  • Take dft of input to transform to Fourier domain
  • Draw a white filled circle on a black background the size of the input as a mask (filter kernel). This is the defocus kernel in the spatial domain, i.e. a circular rect function.
  • Blur the circle slightly to anti-alias the edge
  • Roll the mask so that the center is at the origin (top left corner) and normalize so that the sum of values = 1
  • Take dft of mask to transform to Fourier domain where its amplitude profile is a jinx function.
  • Multiply the two dft images to apply the blur
  • Take the idft of the product to transform back to spatial domain
  • Get the magnitude of the real and imaginary components of the product, clip and convert to uint8 as the result
  • Save the result

Input:

enter image description here

import numpy as np
import cv2

# read input and convert to grayscale
img = cv2.imread('lena_512_gray.png', cv2.IMREAD_GRAYSCALE)

# do dft saving as complex output
dft_img = np.fft.fft2(img, axes=(0,1))

# create circle mask
radius = 32
mask = np.zeros_like(img)
cy = mask.shape[0] // 2
cx = mask.shape[1] // 2
cv2.circle(mask, (cx,cy), radius, 255, -1)[0]

# blur the mask slightly to antialias
mask = cv2.GaussianBlur(mask, (3,3), 0)

# roll the mask so that center is at origin and normalize to sum=1
mask_roll = np.roll(mask, (256,256), axis=(0,1))
mask_norm = mask_roll / mask_roll.sum() 

# take dft of mask
dft_mask_norm = np.fft.fft2(mask_norm, axes=(0,1))

# apply dft_mask to dft_img
dft_shift_product = np.multiply(dft_img, dft_mask_norm)

# do idft saving as complex output
img_filtered = np.fft.ifft2(dft_shift_product, axes=(0,1))

# combine complex real and imaginary components to form (the magnitude for) the original image again
img_filtered = np.abs(img_filtered).clip(0,255).astype(np.uint8)

cv2.imshow("ORIGINAL", img)
cv2.imshow("MASK", mask)
cv2.imshow("FILTERED DFT/IFT ROUND TRIP", img_filtered)
cv2.waitKey(0)
cv2.destroyAllWindows()

# write result to disk
cv2.imwrite("lena_512_gray_mask.png", mask)
cv2.imwrite("lena_dft_numpy_lowpass_filtered_rad32.jpg", img_filtered)

Mask - Filter Kernel In Spatial Domain:

enter image description here

Result for Circle Radius=4:

enter image description here

Result for Circle Radius=8:

enter image description here

Result for Circle Radius=16:

enter image description here

Result for Circle Radius=32

enter image description here:

ADDITION

Using OpenCV for the dft, etc rather than Numpy, the above becomes:

import numpy as np
import cv2

# read input and convert to grayscale
img = cv2.imread('lena_512_gray.png', cv2.IMREAD_GRAYSCALE)

# do dft saving as complex output
dft_img = cv2.dft(np.float32(img), flags = cv2.DFT_COMPLEX_OUTPUT)

# create circle mask
radius = 32
mask = np.zeros_like(img)
cy = mask.shape[0] // 2
cx = mask.shape[1] // 2
cv2.circle(mask, (cx,cy), radius, 255, -1)[0]

# blur the mask slightly to antialias
mask = cv2.GaussianBlur(mask, (3,3), 0)

# roll the mask so that center is at origin and normalize to sum=1
mask_roll = np.roll(mask, (256,256), axis=(0,1))
mask_norm = mask_roll / mask_roll.sum() 

# take dft of mask
dft_mask_norm = cv2.dft(np.float32(mask_norm), flags = cv2.DFT_COMPLEX_OUTPUT)

# apply dft_mask to dft_img
dft_product = cv2.mulSpectrums(dft_img, dft_mask_norm, 0)

# do idft saving as complex output, then clip and convert to uint8
img_filtered = cv2.idft(dft_product, flags=cv2.DFT_SCALE cv2.DFT_REAL_OUTPUT)
img_filtered = img_filtered.clip(0,255).astype(np.uint8)

cv2.imshow("ORIGINAL", img)
cv2.imshow("MASK", mask)
cv2.imshow("FILTERED DFT/IFT ROUND TRIP", img_filtered)
cv2.waitKey(0)
cv2.destroyAllWindows()

# write result to disk
cv2.imwrite("lena_512_gray_mask.png", mask)
cv2.imwrite("lena_dft_opencv_defocus_rad32.jpg", img_filtered)
  • Related