Last active
December 14, 2022 22:53
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.