Reworked core filters, simplified naming, parameters, removed unneccesary ones.

master
Rostislav Lán 2 years ago
parent c484011200
commit a95e53a69f

@ -7,121 +7,296 @@ import numpy as np
import cv2 as cv
from skimage import filters as skiflt
from skimage import restoration as skirest
#from scipy import signal as sig
from skimage import morphology as skimorph
# from scipy import signal as sig
from PIL import Image, ImageFilter
import bm3d
# Parent class for all the filters
class filter:
''' Parent class for all the filters.
'''
def __init__(self, img):
'''
:param img: Image to be filtered
'''
self.img = img
class convolve(filter):
''' Convolve with custom kernel using opencv.
If no kernel is given, use default 3x3 kernel for averaging.
Possibly useful for custom filters.
# --------------------- DENOISING FILTERS ---------------------#
class gaussian(filter):
'''Gaussian blur filter from scikit-image.
Easier to use than opencv version.
'''
def __init__(self, img):
super().__init__(img)
def apply(self, params):
ksize = int(params["ksize"]) if params["ksize"] else 3
kernel = np.array(params["kernel"]) if params["kernel"] else np.ones(
(ksize, ksize), np.float32) / np.sqrt(ksize)
# Standard deviation for Gaussian kernel
sigma = float(params["sigma"]) if params["sigma"] else 1
#print("with params: ksize: " +
# str(ksize) + " kernel: \n" + str(kernel))
self.img = cv.filter2D(self.img, -1, kernel)
print("with params: sigma: " + str(sigma))
self.img = skiflt.gaussian(
self.img, sigma=sigma, preserve_range=True)
class blur(filter):
''' Blur filter from OpenCV.
Performs averaging of the image.
class median(filter):
''' Median blur filter from scikit-image.
Using this over opencv version as that one is limited to 5x5 kernel.
'''
def __init__(self, img):
super().__init__(img)
def apply(self, params):
# TODO remove try-except
if(params["anchor"]):
try:
anchor = tuple(map(int, params["anchor"].split(',')))
except AttributeError:
anchor = tuple(params["anchor"])
else:
anchor = (-1, -1)
# Size of the median filter kernel
# Used kernel is disk of size ksize
ksize = int(params["ksize"]) if params["ksize"] else 3
#print("with params: ksize: " +
# str(ksize) + " anchor: " + str(anchor))
self.img = cv.blur(self.img, ksize=(ksize, ksize), anchor=anchor)
print("with params: ksize: " + str(ksize))
self.img = skiflt.median(self.img, footprint=skimorph.disk(ksize))
class gaussian(filter):
''' Gaussian blur filter from OpenCV.
class bilateral(filter):
''' Bilateral filter from opencv.
'''
def __init__(self, img):
super().__init__(img)
def apply(self, params):
ksize = int(params["ksize"]) if params["ksize"] else 3
sigmaX = float(params["sigmaX"]) if params["sigmaX"] else 0
sigmaY = float(params["sigmaY"]) if params["sigmaY"] else 0
#print("with params: ksize: " + str(ksize) +
# " sigmaX: " + str(sigmaX) + " sigmaY: " + str(sigmaY))
self.img = cv.GaussianBlur(self.img, (ksize, ksize), sigmaX, sigmaY)
# Diameter of pixel neighborhood used for filtering
# This determines how fast the filtering is going to be
diameter = int(params["diameter"]) if params["diameter"] else 3
# Standard deviation for grayvalue/color distance
# A larger value results in averaging of pixels with larger radiometric differences
sigmaColor = int(params["sigmaColor"]) if params["sigmaColor"] else 75
class median(filter):
''' Median blur filter from scikit-image.
Using this over opencv version as that one is limited to 5x5 kernel.
# Standard deviation for range distance in pixels
# A larger value results in averaging of pixels with larger spatial differences
sigmaSpace = int(params["sigmaSpace"]) if params["sigmaSpace"] else 75
print("with params: diameter: " + str(diameter) + " sigmaColor: " +
str(sigmaColor) + " sigmaSpace: " + str(sigmaSpace))
self.img = np.uint8(self.img)
self.img = cv.bilateralFilter(
self.img, diameter, sigmaColor, sigmaSpace)
class bilateral_scikit(filter):
''' Skimage denoise_bilateral filter.
Averages pixels based on their distance and color similarity.
Preserves edges while removing unwanted noise.
Much slower than opencv implementation.
'''
def __init__(self, img):
super().__init__(img)
def apply(self, params):
ksize = int(params["ksize"]) if params["ksize"] else 3
#print("with params: ksize: " + str(ksize))
self.img = skiflt.median(self.img, footprint=np.ones((ksize, ksize)))
# Standard deviation for grayvalue/color distance.
# A larger value results in averaging of pixels with larger radiometric differences.
# Range of values: 0 to 1.
sigmaColor = float(params["sigmaColor"]
) if params["sigmaColor"] else 0.1
# Standard deviation for range distance in pixels.
# A larger value results in averaging of pixels with larger spatial differences.
# Range of values: 0 to image size, recommend not more than 20 pixels.
sigmaSpace = float(params["sigmaSpace"]
) if params["sigmaSpace"] else 9.0
print("with params: sigma_color: " + str(sigmaColor) +
" sigma_spatial: " + str(sigmaSpace))
self.img = skirest.denoise_bilateral(
self.img, sigma_color=sigmaColor, sigma_spatial=sigmaSpace)
self.img = np.uint8(self.img * 255.0) # converting back to uint8
class bilateral(filter):
''' Bilateral filter from OpenCV.
class nlmeans(filter):
''' Non-local means filter from scikit-image.
'''
def __init__(self, img):
super().__init__(img)
def apply(self, params):
# Set default values
d = int(params["d"]) if params["d"] else 1
sigmaColor = int(params["sigmaColor"]) if params["sigmaColor"] else 75
sigmaSpace = int(params["sigmaSpace"]) if params["sigmaSpace"] else 75
#print("with params: d: " + str(d) + " sigmaColor: " +
# str(sigmaColor) + " sigmaSpace: " + str(sigmaSpace))
self.img = np.uint8(self.img)
self.img = cv.bilateralFilter(self.img, d, sigmaColor, sigmaSpace)
# Size of patches used for denoising
patch_size = int(params["patch_size"]) if params["patch_size"] else 5
# Distance in pixels where to search patches
patch_distance = int(params["patch_distance"]
) if params["patch_distance"] else 3
# Estimated standard deviation of the noise
sigma = np.mean(skirest.estimate_sigma(self.img))
# Cut-off distance, higher means more smoothed image
h = float(params["h"])*sigma if params["h"] else 0.1*sigma
print("with params: patch_size: " + str(patch_size) + " patch_distance: " +
str(patch_distance) + " h: " + str(round(h, 4)))
self.img = skirest.denoise_nl_means(
self.img, patch_size=patch_size, fast_mode=True, patch_distance=patch_distance, h=h)
self.img = np.uint8(self.img * 255.0) # converting back to uint8
class total_variation(filter):
''' Scikit image denoise_tv_chambolle filter from scikit-image.
Performs total variation denoising technique based on original Chambolle paper.
This filter removes fine detail, but preserves details such as edges.
'''
def __init__(self, img):
super().__init__(img)
def apply(self, params):
# Denoising weight. Larger values result in more denoising.
weight = float(params["weight"]) if params["weight"] else 0.1
print("with params: weight: " + str(weight))
self.img = skirest.denoise_tv_chambolle(
self.img, weight=weight)
self.img = np.uint8(self.img * 255.0) # converting back to uint8
class block_match(filter):
'''Block matching filter from bm3d.
This filter is very slow and should be used only on small images
'''
def __init__(self, img):
super().__init__(img)
def apply(self, params):
sigma = float(params["sigma"]) if params["sigma"] else 20
print("with params: sigma: " + str(sigma))
self.img = bm3d.bm3d(self.img, sigma_psd=sigma,
stage_arg=bm3d.BM3DStages.ALL_STAGES)
class unsharp_mask_scikit(filter):
''' Unsharp mask filter from scikit.
Apply blurring using gaussian filter, then subtract the blurred image from the original image.
Radius parameter is the sigma parameter of the gaussian filter.
Amount parameter regulates the strength of the unsharp mask.
Better results than using this from opencv.
'''
def __init__(self, img):
super().__init__(img)
def apply(self, params):
# radius of the gaussian filter
radius = int(params["radius"]) if params["radius"] else 3
# strength of the unsharp mask
amount = float(params["amount"]) if params["amount"] else 1.0
print("with params: radius: " +
str(radius) + " amount: " + str(amount))
self.img = skiflt.unsharp_mask(self.img, radius=radius,
amount=amount, channel_axis=None)
self.img = np.uint8(self.img * 255.0) # converting back to uintknapsack
# ------------------- EDGE DETECTION FILTERS -------------------#
class farid(filter):
''' Farid filter from filters.
Not sure what this might be used for yet.
'''
def __init__(self, img):
super().__init__(img)
def apply(self, params):
self.img = skiflt.farid(self.img)
# ------------------ RIDGE EXTRACTION FILTERS ------------------#
class meijering(filter):
''' Meijering filter from scikit-image filters.
'''
def __init__(self, img):
super().__init__(img)
def apply(self, params):
self.img = skiflt.meijering(self.img)
self.img = np.uint8(self.img * 255.0)
class sato(filter):
''' Meijering filter from scikit-image filters.
Exctracts black ridges.
'''
def __init__(self, img):
super().__init__(img)
def apply(self, params):
self.img = skiflt.sato(self.img)
self.img = np.uint8(self.img * 255.0)
class hessian(filter):
''' Hessian filter from scikit-image filters.
'''
def __init__(self, img):
super().__init__(img)
def apply(self, params):
sigmas = float(params["sigma"]) if params["sigma"] else 1.2
self.img = skiflt.hessian(self.img, sigmas=sigmas)
self.img = np.uint8(self.img * 255.0)
# ------------------- MISCELLANEOUS FILTERS -------------------#
class invert(filter):
''' Invert the image using bitwise_not from opencv.
'''
def __init__(self, img):
super().__init__(img)
def apply(self, params):
self.img = cv.bitwise_not(self.img)
class scale_values(filter):
''' Scale values of the image to use the entire range of data type.
This should remove the line height issues.
'''
def __init__(self, img):
super().__init__(img)
def apply(self, params):
# do this once for inverted image and once for original
# this is done to get whiter whites and blacker blacks
# which helps to get exact line height on stl model
# scale once for inverted image and once for original
# this is done to get the full value range of the data type
# which might help getting exact line height on stl model
tmp = cv.bitwise_not(self.img.astype(np.uint8))
coef = 255 / np.max(tmp)
tmp = tmp * coef
@ -139,8 +314,8 @@ class binarize(filter):
maxval = int(params["maxval"]) if params["maxval"] else 255
type = int(params["type"]) if params["type"] else 0
#print("with params: threshold: " + str(threshold) +
# " maxval: " + str(maxval) + " type: " + str(type))
print("with params: threshold: " + str(threshold) +
" maxval: " + str(maxval) + " type: " + str(type))
self.img = cv.threshold(self.img, threshold, maxval, type)[1]
@ -151,96 +326,75 @@ class add_margin(filter):
def apply(self, params):
margin = int(params["margin"]) if params["margin"] else 10
color = int(params["color"]) if params["color"] else 255
print("with params: margin: " + str(margin) + " color: " + str(color))
self.fig.set_size_inches(
((self.width + 2 * margin) / self.dpi, (self.height + 2 * margin) / self.dpi))
self.img = cv.copyMakeBorder(
self.img, margin, margin, margin, margin, cv.BORDER_CONSTANT, value=color)
self.height, self.width = self.img.shape
class denoise(filter):
# TODO possibly not necessary
def __init__(self, img):
super().__init__(img)
def apply(self, params):
h = int(params["h"]) if params["h"] else 10
tWS = int(params["templateWindowSize"]
) if params["templateWindowSize"] else 7
sWS = int(params["searchWindowSize"]
) if params["searchWindowSize"] else 21
# ---------------------- OLD --------------------------#
#print("with params: h: " + str(h) +
# " tWS: " + str(tWS) + " sWS: " + str(sWS))
self.img = np.uint8(self.img)
self.img = cv.fastNlMeansDenoising(
self.img, h, tWS, sWS)
# TODO: REVISE, REMOVE unused filters
class denoise_bilateral(filter):
''' Scikit image denoise_bilateral filter.
Performs bilateral denoising technique on the image.
Averages pixels based on their distance and color similarity.
Preserves edges while removing unwanted noise.
class convolve(filter):
''' Convolve with custom kernel using opencv.
If no kernel is given, use default 3x3 kernel for averaging.
Possibly useful for custom filters.
'''
def __init__(self, img):
super().__init__(img)
def apply(self, params):
kernel = np.array(params["kernel"]) if params["kernel"] else np.ones(
(3, 3), np.float32) / 9
# Standard deviation for grayvalue/color distance.
# A larger value results in averaging of pixels with larger radiometric differences.
sigmaColor = float(params["sigmaColor"]
) if params["sigmaColor"] else 0.1
print("with params: kernel: \n" + str(kernel))
self.img = cv.filter2D(self.img, -1, kernel)
# Standard deviation for range distance.
# A larger value results in averaging of pixels with larger spatial differences.
sigmaSpace = float(params["sigmaSpace"]
) if params["sigmaSpace"] else 15.0
# Repetition of filter application.
iterations = int(params["iterations"]) if params["iterations"] else 1
#print("with params: sigma_color: " + str(sigmaColor) +
# " sigma_spatial: " + str(sigmaSpace) + " iterations: " + str(iterations))
class blur(filter):
''' Blur filter from opencv.
Performs averaging of the image.
'''
for i in range(iterations):
self.img = skirest.denoise_bilateral(
self.img, sigma_color=sigmaColor,
sigma_spatial=sigmaSpace, channel_axis=None)
def __init__(self, img):
super().__init__(img)
def apply(self, params):
ksize = int(params["ksize"]) if params["ksize"] else 3
class denoise_tv_chambolle(filter):
''' Scikit image denoise_tv_chambolle filter from scikit-image.
print("with params: ksize: " + str(ksize))
self.img = cv.blur(self.img, ksize=(ksize, ksize))
Performs total variation denoising technique on the image.
This filter removes fine detail, but preserves edges.
'''
class denoise(filter):
def __init__(self, img):
super().__init__(img)
def apply(self, params):
h = int(params["h"]) if params["h"] else 10
tWS = int(params["templateWindowSize"]
) if params["templateWindowSize"] else 7
sWS = int(params["searchWindowSize"]
) if params["searchWindowSize"] else 21
# Denoising weight. The greater weight, the more denoising.
weight = float(params["weight"]) if params["weight"] else 0.1
# Maximal number of iterations used for the optimization.
iterations = int(params["iterations"]) if params["iterations"] else 1
#print("with params: weight: " + str(weight) +
# " iterations: " + str(iterations))
for i in range(iterations):
self.img = skirest.denoise_tv_chambolle(
self.img, weight=weight, channel_axis=None)
# print("with params: h: " + str(h) +
# " tWS: " + str(tWS) + " sWS: " + str(sWS))
self.img = np.uint8(self.img)
self.img = cv.fastNlMeansDenoising(
self.img, h, tWS, sWS)
class sharpen(filter):
''' Convolution with a sharpening kernel using opencv.
'''
# TODO possibly unnecessary, because unsharp masking is working better
def __init__(self, img):
super().__init__(img)
@ -248,7 +402,7 @@ class sharpen(filter):
kernel = np.matrix(params["kernel"]) if params["kernel"] else np.array(
[[0, -1, 0], [-1, 5, -1], [0, -1, 0]])
#print("with params: kernel: \n" + str(kernel))
# print("with params: kernel: \n" + str(kernel))
self.img = cv.filter2D(self.img, ddepth=-1, kernel=kernel)
@ -269,43 +423,16 @@ class unsharp_mask(filter):
blurred = cv.medianBlur(self.img, ksize)
lap = cv.Laplacian(blurred, cv.CV_32F)
#print("with params: strength: " +
# print("with params: strength: " +
# str(strength) + " ksize: " + str(ksize))
self.img = blurred - strength*lap
class unsharp_mask_scikit(filter):
''' Unsharp mask filter from scikit.
Apply blurring using gaussian filter, then subtract the blurred image from the original image.
Radius parameter is the sigma parameter of the gaussian filter.
Amount parameter regulates the strength of the unsharp mask.
Better results than using opencv module.
'''
def __init__(self, img):
super().__init__(img)
def apply(self, params):
radius = int(params["radius"]) if params["radius"] else 3
amount = float(params["amount"]) if params["amount"] else 1
# TODO: i have no idea what this is or how to use it
channelAxis = int(params["channelAxis"]
) if params["channelAxis"] else None
#self.img = cv.cvtColor(self.img, cv.COLOR_GRAY2RGB)
#print("with params: radius: " +
# str(radius) + " amount: " + str(amount))
self.img = skiflt.unsharp_mask(
self.img, radius=radius, amount=amount, channel_axis=channelAxis)
#self.img = cv.cvtColor(self.img, cv.COLOR_RGB2GRAY)
class unsharp_mask_pil(filter):
''' Unsharp mask filter from PIL.
'''
# TODO: does not work
def __init__(self, img):
super().__init__(img)
@ -320,7 +447,7 @@ class unsharp_mask_pil(filter):
# Threshold controls the minimum brightness change that will be sharpened
threshold = int(params["threshold"]) if params["threshold"] else 3
#print("with params: radius: " +
# print("with params: radius: " +
# str(radius) + " percent: " + str(percent) + " threshold: " + str(threshold))
self.img = np.uint8(self.img)
tmp = Image.fromarray(self.img)
@ -328,7 +455,27 @@ class unsharp_mask_pil(filter):
self.img = np.asarray(tmp)
class morph(filter):
class erode(filter):
''' General morphological operations from OpenCV.
Can be used with MORPH_OPEN, MORPH_CLOSE, MORPH_DILATE, MORPH_ERODE and more as 'op'.
'''
def __init__(self, img):
super().__init__(img)
def apply(self, params):
# get an ellipse kernel
kernel = np.matrix(params["kernel"]) if params["kernel"] else cv.getStructuringElement(
cv.MORPH_ELLIPSE, (3, 3))
print("with params: kernel: \n" + str(kernel))
self.img = cv.morphologyEx(
np.uint8(self.img), op=cv.MORPH_ERODE, kernel=kernel)
class dilate(filter):
''' General morphological operations from OpenCV.
Can be used with MORPH_OPEN, MORPH_CLOSE, MORPH_DILATE, MORPH_ERODE and more as 'op'.
@ -338,25 +485,14 @@ class morph(filter):
super().__init__(img)
def apply(self, params):
# TODO: this is probably better with binarized image
kernel = np.matrix(params["kernel"]) if params["kernel"] else np.ones(
(3, 3), np.uint8)
iterations = int(params["iterations"]) if params["iterations"] else 1
op = getattr(cv, params["op"]) if params["op"] else cv.MORPH_OPEN
if(params["anchor"]):
# TODO remove try-except
try:
anchor = tuple(map(int, params["anchor"].split(',')))
except AttributeError:
anchor = tuple(params["anchor"])
else:
anchor = (-1, -1)
#print("with params: kernel: \n" + str(kernel) + " anchor: " +
# str(anchor) + " iterations: " + str(iterations) + " op: " + str(op))
# get an ellipse kernel
kernel = np.matrix(params["kernel"]) if params["kernel"] else cv.getStructuringElement(
cv.MORPH_ELLIPSE, (3, 3))
print("with params: kernel: \n" + str(kernel))
self.img = cv.morphologyEx(
np.uint8(self.img), op=op, kernel=kernel,
anchor=anchor, iterations=iterations)
np.uint8(self.img), op=cv.MORPH_DILATE, kernel=kernel)
class gabor(filter):
@ -368,11 +504,11 @@ class gabor(filter):
def __init__(self, img):
super().__init__(img)
# TODO: not working properly
def apply(self, params):
ksize = int(params["ksize"]) if params["ksize"] else 31
sigma = float(params["sigma"]) if params["sigma"] else 10.0
theta = params["theta"] if params["theta"] else [0,np.pi/16,np.pi-np.pi/16]
theta = params["theta"] if params["theta"] else [
0, np.pi/16, np.pi-np.pi/16]
lambd = float(params["lambd"]) if params["lambd"] else 10.0
gamma = float(params["gamma"]) if params["gamma"] else 0.02
psi = float(params["psi"]) if params["psi"] else 0.0
@ -380,7 +516,8 @@ class gabor(filter):
filters = []
for i in range(len(theta)):
g_kernel = cv.getGaborKernel(ksize=(ksize, ksize), sigma=sigma, theta=theta[i], lambd=lambd, gamma=gamma, psi=psi)
g_kernel = cv.getGaborKernel(ksize=(
ksize, ksize), sigma=sigma, theta=theta[i], lambd=lambd, gamma=gamma, psi=psi)
g_kernel = g_kernel / 1.5 * g_kernel.sum()
filters.append(g_kernel)

Loading…
Cancel
Save