|
|
|
@ -8,13 +8,11 @@ import cv2 as cv
|
|
|
|
|
from skimage import filters as skiflt
|
|
|
|
|
from skimage import restoration as skirest
|
|
|
|
|
from skimage import morphology as skimorph
|
|
|
|
|
from scipy import ndimage
|
|
|
|
|
from PIL import Image, ImageFilter
|
|
|
|
|
import bm3d
|
|
|
|
|
import matplotlib.pyplot as plt
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class filter:
|
|
|
|
|
class img_filter:
|
|
|
|
|
''' Parent class for all the filters.
|
|
|
|
|
'''
|
|
|
|
|
|
|
|
|
@ -28,7 +26,7 @@ class filter:
|
|
|
|
|
# --------------------- DENOISING FILTERS ---------------------#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class gaussian(filter):
|
|
|
|
|
class gaussian(img_filter):
|
|
|
|
|
'''Gaussian blur filter from scikit-image.
|
|
|
|
|
Easier to use than opencv version.
|
|
|
|
|
'''
|
|
|
|
@ -45,7 +43,7 @@ class gaussian(filter):
|
|
|
|
|
self.img, sigma=sigma, preserve_range=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class median(filter):
|
|
|
|
|
class median(img_filter):
|
|
|
|
|
''' Median blur filter from scikit-image.
|
|
|
|
|
Using this over opencv version as that one is limited to 5x5 kernel.
|
|
|
|
|
'''
|
|
|
|
@ -61,7 +59,7 @@ class median(filter):
|
|
|
|
|
self.img = skiflt.median(self.img, footprint=skimorph.disk(ksize))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class bilateral(filter):
|
|
|
|
|
class bilateral(img_filter):
|
|
|
|
|
''' Bilateral filter from opencv.
|
|
|
|
|
'''
|
|
|
|
|
|
|
|
|
@ -87,7 +85,7 @@ class bilateral(filter):
|
|
|
|
|
self.img, diameter, sigmaColor, sigmaSpace)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class bilateral_scikit(filter):
|
|
|
|
|
class bilateral_scikit(img_filter):
|
|
|
|
|
''' Skimage denoise_bilateral filter.
|
|
|
|
|
Averages pixels based on their distance and color similarity.
|
|
|
|
|
Preserves edges while removing unwanted noise.
|
|
|
|
@ -116,7 +114,7 @@ class bilateral_scikit(filter):
|
|
|
|
|
self.img = np.uint8(self.img * 255.0) # converting back to uint8
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class nlmeans(filter):
|
|
|
|
|
class nlmeans(img_filter):
|
|
|
|
|
''' Non-local means filter from scikit-image.
|
|
|
|
|
'''
|
|
|
|
|
|
|
|
|
@ -143,7 +141,7 @@ class nlmeans(filter):
|
|
|
|
|
self.img = np.uint8(self.img * 255.0) # converting back to uint8
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class total_variation(filter):
|
|
|
|
|
class total_variation(img_filter):
|
|
|
|
|
''' Scikit image denoise_tv_chambolle filter from scikit-image.
|
|
|
|
|
|
|
|
|
|
Performs total variation denoising technique based on original Chambolle paper.
|
|
|
|
@ -163,7 +161,7 @@ class total_variation(filter):
|
|
|
|
|
self.img = np.uint8(self.img * 255.0) # converting back to uint8
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class block_match(filter):
|
|
|
|
|
class block_match(img_filter):
|
|
|
|
|
'''Block matching filter from bm3d.
|
|
|
|
|
|
|
|
|
|
This filter is very slow and should be used only on small images
|
|
|
|
@ -179,7 +177,7 @@ class block_match(filter):
|
|
|
|
|
stage_arg=bm3d.BM3DStages.ALL_STAGES)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class unsharp_mask_scikit(filter):
|
|
|
|
|
class unsharp_mask_scikit(img_filter):
|
|
|
|
|
''' Unsharp mask filter from scikit.
|
|
|
|
|
|
|
|
|
|
Apply blurring using gaussian filter, then subtract the blurred image from the original image.
|
|
|
|
@ -204,7 +202,7 @@ class unsharp_mask_scikit(filter):
|
|
|
|
|
# ------------------- EDGE DETECTION FILTERS -------------------#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class farid(filter):
|
|
|
|
|
class farid(img_filter):
|
|
|
|
|
''' Farid filter from filters.
|
|
|
|
|
Not sure what this might be used for yet.
|
|
|
|
|
'''
|
|
|
|
@ -212,27 +210,27 @@ class farid(filter):
|
|
|
|
|
def __init__(self, img):
|
|
|
|
|
super().__init__(img)
|
|
|
|
|
|
|
|
|
|
def apply(self, params):
|
|
|
|
|
def apply(self, _):
|
|
|
|
|
|
|
|
|
|
self.img = skiflt.farid(self.img)
|
|
|
|
|
|
|
|
|
|
# ------------------ RIDGE EXTRACTION FILTERS ------------------#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class meijering(filter):
|
|
|
|
|
class meijering(img_filter):
|
|
|
|
|
''' Meijering filter from scikit-image filters.
|
|
|
|
|
'''
|
|
|
|
|
|
|
|
|
|
def __init__(self, img):
|
|
|
|
|
super().__init__(img)
|
|
|
|
|
|
|
|
|
|
def apply(self, params):
|
|
|
|
|
def apply(self, _):
|
|
|
|
|
|
|
|
|
|
self.img = skiflt.meijering(self.img)
|
|
|
|
|
self.img = np.uint8(self.img * 255.0)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class sato(filter):
|
|
|
|
|
class sato(img_filter):
|
|
|
|
|
''' Meijering filter from scikit-image filters.
|
|
|
|
|
Exctracts black ridges.
|
|
|
|
|
'''
|
|
|
|
@ -240,13 +238,13 @@ class sato(filter):
|
|
|
|
|
def __init__(self, img):
|
|
|
|
|
super().__init__(img)
|
|
|
|
|
|
|
|
|
|
def apply(self, params):
|
|
|
|
|
def apply(self, _):
|
|
|
|
|
|
|
|
|
|
self.img = skiflt.sato(self.img)
|
|
|
|
|
self.img = np.uint8(self.img * 255.0)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class hessian(filter):
|
|
|
|
|
class hessian(img_filter):
|
|
|
|
|
''' Hessian filter from scikit-image filters.
|
|
|
|
|
'''
|
|
|
|
|
|
|
|
|
@ -263,18 +261,18 @@ class hessian(filter):
|
|
|
|
|
# ------------------- MISCELLANEOUS FILTERS -------------------#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class invert(filter):
|
|
|
|
|
class invert(img_filter):
|
|
|
|
|
''' Invert the image using bitwise_not from opencv.
|
|
|
|
|
'''
|
|
|
|
|
|
|
|
|
|
def __init__(self, img):
|
|
|
|
|
super().__init__(img)
|
|
|
|
|
|
|
|
|
|
def apply(self, params):
|
|
|
|
|
def apply(self, _):
|
|
|
|
|
self.img = cv.bitwise_not(self.img)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class scale_values(filter):
|
|
|
|
|
class scale_values(img_filter):
|
|
|
|
|
''' Scale values of the image to use the entire range of data type.
|
|
|
|
|
This should remove the line height issues.
|
|
|
|
|
'''
|
|
|
|
@ -294,19 +292,17 @@ class scale_values(filter):
|
|
|
|
|
tmp = tmp * coef
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class binarize(filter):
|
|
|
|
|
class binarize(img_filter):
|
|
|
|
|
def init(self, img):
|
|
|
|
|
super().__init__(img)
|
|
|
|
|
|
|
|
|
|
def apply(self, params):
|
|
|
|
|
threshold = int(params["threshold"]) if params["threshold"] else 128
|
|
|
|
|
maxval = int(params["maxval"]) if params["maxval"] else 255
|
|
|
|
|
type = int(params["type"]) if params["type"] else 0
|
|
|
|
|
|
|
|
|
|
self.img = cv.threshold(self.img, threshold, maxval, type)[1]
|
|
|
|
|
self.img = cv.threshold(self.img, threshold, 255, cv.THRESH_BINARY)[1]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class binarize_otsu(filter):
|
|
|
|
|
class binarize_otsu(img_filter):
|
|
|
|
|
''' Otsu binarization filter from opencv.
|
|
|
|
|
'''
|
|
|
|
|
def init(self, img):
|
|
|
|
@ -316,7 +312,7 @@ class binarize_otsu(filter):
|
|
|
|
|
self.img = cv.threshold(self.img, 0, 255, cv.THRESH_BINARY + cv.THRESH_OTSU)[1]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class add_margin(filter):
|
|
|
|
|
class add_margin(img_filter):
|
|
|
|
|
def init(self, img):
|
|
|
|
|
super().__init__(img)
|
|
|
|
|
|
|
|
|
@ -330,95 +326,52 @@ class add_margin(filter):
|
|
|
|
|
self.img, margin, margin, margin, margin, cv.BORDER_CONSTANT, value=color)
|
|
|
|
|
self.height, self.width = self.img.shape
|
|
|
|
|
|
|
|
|
|
# ---------------------- OLD --------------------------#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# TODO: REVISE, REMOVE unused filters
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
self.img = cv.filter2D(self.img, -1, kernel)
|
|
|
|
|
# ---------------------- MORPHOLOGICAL OPS --------------------------#
|
|
|
|
|
|
|
|
|
|
class erode(img_filter):
|
|
|
|
|
''' General morphological operations from OpenCV.
|
|
|
|
|
|
|
|
|
|
class blur(filter):
|
|
|
|
|
''' Blur filter from opencv.
|
|
|
|
|
Performs averaging of the image.
|
|
|
|
|
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):
|
|
|
|
|
ksize = int(params["ksize"]) if params["ksize"] else 3
|
|
|
|
|
|
|
|
|
|
self.img = cv.blur(self.img, ksize=(ksize, ksize))
|
|
|
|
|
|
|
|
|
|
# get an ellipse kernel
|
|
|
|
|
kernel = np.matrix(params["kernel"]) if params["kernel"] else cv.getStructuringElement(
|
|
|
|
|
cv.MORPH_ELLIPSE, (3, 3))
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
self.img = np.uint8(self.img)
|
|
|
|
|
self.img = cv.fastNlMeansDenoising(
|
|
|
|
|
self.img, h, tWS, sWS)
|
|
|
|
|
self.img = cv.morphologyEx(
|
|
|
|
|
np.uint8(self.img), op=cv.MORPH_ERODE, kernel=kernel)
|
|
|
|
|
|
|
|
|
|
class dilate(img_filter):
|
|
|
|
|
''' General morphological operations from OpenCV.
|
|
|
|
|
|
|
|
|
|
class sharpen(filter):
|
|
|
|
|
''' Convolution with a sharpening kernel using 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):
|
|
|
|
|
kernel = np.matrix(params["kernel"]) if params["kernel"] else np.array(
|
|
|
|
|
[[0, -1, 0], [-1, 5, -1], [0, -1, 0]])
|
|
|
|
|
|
|
|
|
|
self.img = cv.filter2D(self.img, ddepth=-1, kernel=kernel)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class unsharp_mask(filter):
|
|
|
|
|
''' Unsharp mask filter from opencv.
|
|
|
|
|
# get an ellipse kernel
|
|
|
|
|
kernel = np.matrix(params["kernel"]) if params["kernel"] else cv.getStructuringElement(
|
|
|
|
|
cv.MORPH_ELLIPSE, (3, 3))
|
|
|
|
|
|
|
|
|
|
First blur the image a little bit, then calculate Laplacian of the image to get the edges.
|
|
|
|
|
Scale the Laplacian and subtract it from the original image.
|
|
|
|
|
'''
|
|
|
|
|
self.img = cv.morphologyEx(
|
|
|
|
|
np.uint8(self.img), op=cv.MORPH_DILATE, kernel=kernel)
|
|
|
|
|
|
|
|
|
|
def __init__(self, img):
|
|
|
|
|
super().__init__(img)
|
|
|
|
|
|
|
|
|
|
def apply(self, params):
|
|
|
|
|
strength = float(params["strength"]) if params["strength"] else 1.0
|
|
|
|
|
ksize = int(params["ksize"]) if params["ksize"] else 3
|
|
|
|
|
|
|
|
|
|
blurred = cv.medianBlur(self.img, ksize)
|
|
|
|
|
lap = cv.Laplacian(blurred, cv.CV_32F)
|
|
|
|
|
# ---------------------- OLD --------------------------#
|
|
|
|
|
|
|
|
|
|
self.img = blurred - strength*lap
|
|
|
|
|
|
|
|
|
|
# TODO: REVISE, REMOVE unused filters
|
|
|
|
|
|
|
|
|
|
class unsharp_mask_pil(filter):
|
|
|
|
|
class unsharp_mask_pil(img_filter):
|
|
|
|
|
''' Unsharp mask filter from PIL.
|
|
|
|
|
|
|
|
|
|
'''
|
|
|
|
|
|
|
|
|
|
def __init__(self, img):
|
|
|
|
@ -439,73 +392,3 @@ class unsharp_mask_pil(filter):
|
|
|
|
|
tmp = Image.fromarray(self.img)
|
|
|
|
|
tmp = tmp.filter(ImageFilter.UnsharpMask(radius, percent, threshold))
|
|
|
|
|
self.img = np.asarray(tmp)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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))
|
|
|
|
|
|
|
|
|
|
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'.
|
|
|
|
|
'''
|
|
|
|
|
|
|
|
|
|
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))
|
|
|
|
|
|
|
|
|
|
self.img = cv.morphologyEx(
|
|
|
|
|
np.uint8(self.img), op=cv.MORPH_DILATE, kernel=kernel)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class gabor(filter):
|
|
|
|
|
''' Gabor filter from OpenCV.
|
|
|
|
|
|
|
|
|
|
Performs Gabor filtering on the image.
|
|
|
|
|
'''
|
|
|
|
|
|
|
|
|
|
def __init__(self, img):
|
|
|
|
|
super().__init__(img)
|
|
|
|
|
|
|
|
|
|
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]
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
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 = g_kernel / 1.5 * g_kernel.sum()
|
|
|
|
|
filters.append(g_kernel)
|
|
|
|
|
|
|
|
|
|
tmp = np.zeros_like(self.img)
|
|
|
|
|
for i in range(len(filters)):
|
|
|
|
|
tmp = cv.filter2D(self.img, -1, kernel=filters[i])
|
|
|
|
|
self.img += np.maximum(self.img, tmp)
|
|
|
|
|