Skip to content

Instantly share code, notes, and snippets.

@nepomnyi
Last active December 14, 2022 22:53
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save nepomnyi/b8546038a8e70f234f41969491d272c8 to your computer and use it in GitHub Desktop.
Save nepomnyi/b8546038a8e70f234f41969491d272c8 to your computer and use it in GitHub Desktop.
A python min-max filter implementation after Adrain & Westerweel, "Particle image velocimetry", 2011, p.250.
import cv2
import numpy as np
def minMaxFilter(img, filterSize, minContrast):
"""
After R.Adrian, J.Westerweel, "Particle image velocimetry", Cambridge
university press, 2011. See ch.6.1.2, p.248-250.
Parameters: img (ndarray) - image to be filtered
filterSize (ndarray) - a 1x2 numpy array of the filter height
and width correspondingly
minContrast (float) - minimum contrast value imposed on the
image (if the calculated contrast falls
bellow this level, this level is imposed
as the contrast: see the referenced book)
Returns: imgFiltered (ndarray) - filtered image
"""
# cv2 doesn't have min and max filters, erode and dilate play the role of
# min and max filters correspondingly. Note, that, in opencv, erode and
# dilate can be implemented to gray scale images (recall, that normally
# they can only be implemented to binary images.)
# Define the lower and upper envelopes (see the book) of the image intensity
low = cv2.erode(img,
cv2.getStructuringElement(cv2.MORPH_RECT, filterSize))
upp = cv2.dilate(img,
cv2.getStructuringElement(cv2.MORPH_RECT, filterSize))
# Smooth the lower and upper envelopes with a uniform filter of the same size
low = lowPassFilter(low, filterSize)
upp = lowPassFilter(upp, filterSize)
# Define contrast and put a lower limit on it
contrast = cv2.subtract(upp, low)
contrast[contrast < minContrast] = minContrast
# Normalize image intensity, multiplication by 255 is necessary because
# if the pixel value of the original image is smaller than the corresponding
# value of the contrast, you get a less than 1 value after the division,
# which at the show image operation gets converted to 0 and you get black
# image. Also, make sure the image is of np.uint8 type
imgFiltered = np.uint8(cv2.divide(cv2.subtract(img, low), contrast) * 255)
return imgFiltered
def lowPassFilter(img_src, kernelSize):
"""
This is a uniform low pass filter, which measn that all the values in the
filter kernel are equal to 1.
Parameters: img_src (ndarray) - image to be filtered
kernelSize (ndarray) - 1x2 numpy array where the first value is
the kernel's height, the second value is
the kernel's width
Returns: img_rst (ndarray) - filtered image
"""
kernel = np.ones(kernelSize)
kernel = kernel/(np.sum(kernel) if np.sum(kernel)!=0 else 1)
img_rst = cv2.filter2D(img_src, -1, kernel)
return img_rst
@nepomnyi
Copy link
Author

Note, that - according to Adrian&Westerweel - min-max is a normalization method. Another normalization method is histogram equalization. I tried both. To my experience it is easier to work with histogram equalization: it just works (see here for implementation: https://pyimagesearch.com/2021/02/01/opencv-histogram-equalization-and-adaptive-histogram-equalization-clahe/). On the other hand, min-max filter is really sensitive to its parameters: filter size and intensity threshold. But once you set them properly, you get approximately the same results as with histogram equalization. Don't be afraid to use big filter size. For instance, I used filters as big as 15x15 pixels.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment