Added abd replaced some filters, added docstrings.

master
Rostislav Lán 2 years ago
parent cc5e39e53c
commit 3b5040d6ba

@ -1,17 +1,18 @@
"""! @file filters.py """! @file filters.py
@brief Filter library for the application @brief Filter library for the application
@author xlanro00 @author xlanro00
""" """
import numpy as np import numpy as np
#import matplotlib.pyplot as plt #import matplotlib.pyplot as plt
import cv2 as cv import cv2 as cv
from skimage import filters as skiflt
from skimage import restoration as skirest
# Parent class for all the filters # Parent class for all the filters
class filter: class filter:
''' ''' Parent class for all the filters.
Parent class for all the filters
''' '''
def __init__(self, img): def __init__(self, img):
@ -19,8 +20,9 @@ class filter:
class convolve(filter): class convolve(filter):
''' Convolve using custom kernel, ''' Convolve with custom kernel using opencv.
if no kernel is given, use default 3x3 kernel for averaging If no kernel is given, use default 3x3 kernel for averaging.
Possibly useful for custom filters.
''' '''
def __init__(self, img): def __init__(self, img):
@ -31,15 +33,20 @@ class convolve(filter):
kernel = np.array(params["kernel"]) if params["kernel"] else np.ones( kernel = np.array(params["kernel"]) if params["kernel"] else np.ones(
(ksize, ksize), np.float32) / np.sqrt(ksize) (ksize, ksize), np.float32) / np.sqrt(ksize)
#print("with params: " + " ksize: " + str(ksize) + " kernel: \n" + str(kernel)) print("with params: ksize: " +
str(ksize) + " kernel: \n" + str(kernel))
self.img = cv.filter2D(self.img, -1, kernel) self.img = cv.filter2D(self.img, -1, kernel)
class blur(filter): class blur(filter):
''' Blur filter from OpenCV.
Performs averaging of the image.
'''
def __init__(self, img): def __init__(self, img):
super().__init__(img) super().__init__(img)
def apply(self, params): def apply(self, params):
# TODO remove try-except
if(params["anchor"]): if(params["anchor"]):
try: try:
anchor = tuple(map(int, params["anchor"].split(','))) anchor = tuple(map(int, params["anchor"].split(',')))
@ -49,11 +56,14 @@ class blur(filter):
anchor = (-1, -1) anchor = (-1, -1)
ksize = int(params["ksize"]) if params["ksize"] else 3 ksize = int(params["ksize"]) if params["ksize"] else 3
#print("with params: " + " ksize: " + str(ksize) + " anchor: " + str(anchor)) print("with params: ksize: " +
str(ksize) + " anchor: " + str(anchor))
self.img = cv.blur(self.img, ksize=(ksize, ksize), anchor=anchor) self.img = cv.blur(self.img, ksize=(ksize, ksize), anchor=anchor)
class gaussian(filter): class gaussian(filter):
''' Gaussian blur filter from OpenCV.
'''
def __init__(self, img): def __init__(self, img):
super().__init__(img) super().__init__(img)
@ -62,22 +72,27 @@ class gaussian(filter):
sigmaX = float(params["sigmaX"]) if params["sigmaX"] else 0 sigmaX = float(params["sigmaX"]) if params["sigmaX"] else 0
sigmaY = float(params["sigmaY"]) if params["sigmaY"] else 0 sigmaY = float(params["sigmaY"]) if params["sigmaY"] else 0
#print("with params: " + " ksize: " + str(ksize) + " sigmaX: " + str(sigmaX) + " sigmaY: " + str(sigmaY)) print("with params: ksize: " + str(ksize) +
" sigmaX: " + str(sigmaX) + " sigmaY: " + str(sigmaY))
self.img = cv.GaussianBlur(self.img, (ksize, ksize), sigmaX, sigmaY) self.img = cv.GaussianBlur(self.img, (ksize, ksize), sigmaX, sigmaY)
class median(filter): class median(filter):
''' Median blur filter from OpenCV.
'''
def __init__(self, img): def __init__(self, img):
super().__init__(img) super().__init__(img)
def apply(self, params): def apply(self, params):
ksize = int(params["ksize"]) if params["ksize"] else 3 ksize = int(params["ksize"]) if params["ksize"] else 3
#print("with params: " + " ksize: " + str(ksize)) print("with params: ksize: " + str(ksize))
self.img = cv.medianBlur(np.uint8(self.img), ksize) self.img = cv.medianBlur(np.float32(self.img), ksize)
class bilateral(filter): class bilateral(filter):
''' Bilateral filter from OpenCV.
'''
def __init__(self, img): def __init__(self, img):
super().__init__(img) super().__init__(img)
@ -87,27 +102,87 @@ class bilateral(filter):
sigmaColor = int(params["sigmaColor"]) if params["sigmaColor"] else 75 sigmaColor = int(params["sigmaColor"]) if params["sigmaColor"] else 75
sigmaSpace = int(params["sigmaSpace"]) if params["sigmaSpace"] else 75 sigmaSpace = int(params["sigmaSpace"]) if params["sigmaSpace"] else 75
#print("with params: " + " d: " + str(d) + " sigmaColor: " + str(sigmaColor) + " sigmaSpace: " + str(sigmaSpace)) print("with params: d: " + str(d) + " sigmaColor: " +
str(sigmaColor) + " sigmaSpace: " + str(sigmaSpace))
self.img = cv.bilateralFilter(self.img, d, sigmaColor, sigmaSpace) self.img = cv.bilateralFilter(self.img, d, sigmaColor, sigmaSpace)
class denoise(filter): class denoise(filter):
# TODO possibly not necessary
def __init__(self, img): def __init__(self, img):
super().__init__(img) super().__init__(img)
def apply(self, params): def apply(self, params):
h = int(params["h"]) if params["h"] else 20 h = int(params["h"]) if params["h"] else 10
tWS = int(params["templateWindowSize"] tWS = int(params["templateWindowSize"]
) if params["templateWindowSize"] else 7 ) if params["templateWindowSize"] else 7
sWS = int(params["searchWindowSize"] sWS = int(params["searchWindowSize"]
) if params["searchWindowSize"] else 21 ) if params["searchWindowSize"] else 21
#print("with params: " + " h: " + str(h) + " tWS: " + str(tWS) + " sWS: " + str(sWS)) print("with params: h: " + str(h) +
" tWS: " + str(tWS) + " sWS: " + str(sWS))
self.img = np.uint8(self.img) self.img = np.uint8(self.img)
self.img = cv.fastNlMeansDenoising(self.img, h, tWS, sWS) self.img = cv.fastNlMeansDenoising(
self.img, h, tWS, sWS)
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.
'''
def __init__(self, img):
super().__init__(img)
def apply(self, params):
sigmaColor = float(params["sigmaColor"]
) if params["sigmaColor"] else 0.1
sigmaSpace = float(params["sigmaSpace"]
) if params["sigmaSpace"] else 15.0
channelAxis = int(params["channelAxis"]
) if params["channelAxis"] else None
iterations = int(params["iterations"]) if params["iterations"] else 1
print("with params: sigma_color: " + str(sigmaColor) +
" sigma_spatial: " + str(sigmaSpace) + " channel_axis: " +
str(channelAxis) + " iterations: " + str(iterations))
for i in range(iterations):
self.img = skirest.denoise_bilateral(
self.img, sigma_color=sigmaColor,
sigma_spatial=sigmaSpace, channel_axis=channelAxis)
class denoise_tv_chambolle(filter):
''' Scikit image denoise_tv_chambolle filter from scikit-image.
Performs total variation denoising technique on the image.
This filter removes fine detail, but preserves edges.
'''
def __init__(self, img):
super().__init__(img)
def apply(self, params):
weight = float(params["weight"]) if params["weight"] else 0.1
channelAxis = int(params["channelAxis"]
) if params["channelAxis"] else None
iterations = int(params["iterations"]) if params["iterations"] else 1
print("with params: weight: " + str(weight) +
" channel_axis: " + str(channelAxis) + " iterations: " + str(iterations))
for i in range(iterations):
self.img = skirest.denoise_tv_chambolle(
self.img, weight=weight, channel_axis=channelAxis)
class sharpen(filter): class sharpen(filter):
''' Convolution with a sharpening kernel using opencv.
'''
# TODO possibly unnecessary, because unsharp masking is working better
def __init__(self, img): def __init__(self, img):
super().__init__(img) super().__init__(img)
@ -115,12 +190,12 @@ class sharpen(filter):
kernel = np.matrix(params["kernel"]) if params["kernel"] else np.array( kernel = np.matrix(params["kernel"]) if params["kernel"] else np.array(
[[0, -1, 0], [-1, 5, -1], [0, -1, 0]]) [[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) self.img = cv.filter2D(self.img, ddepth=-1, kernel=kernel)
class unsharp_mask(filter): class unsharp_mask(filter):
''' Unsharp mask filter. ''' Unsharp mask filter from opencv.
First blur the image a little bit, then calculate Laplacian of the image to get the edges. 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. Scale the Laplacian and subtract it from the original image.
@ -132,13 +207,43 @@ class unsharp_mask(filter):
def apply(self, params): def apply(self, params):
strength = float(params["strength"]) if params["strength"] else 1.0 strength = float(params["strength"]) if params["strength"] else 1.0
ksize = int(params["ksize"]) if params["ksize"] else 3 ksize = int(params["ksize"]) if params["ksize"] else 3
blurred = cv.medianBlur(np.uint8(self.img), ksize)
blurred = cv.medianBlur(self.img, ksize)
lap = cv.Laplacian(blurred, cv.CV_32F) lap = cv.Laplacian(blurred, cv.CV_32F)
print("with params: strength: " +
str(strength) + " ksize: " + str(ksize))
self.img = blurred - strength*lap 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
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 morph(filter): class morph(filter):
''' General morphological operations. ''' General morphological operations from OpenCV.
Can be used with MORPH_OPEN, MORPH_CLOSE, MORPH_DILATE, MORPH_ERODE and more as 'op'. Can be used with MORPH_OPEN, MORPH_CLOSE, MORPH_DILATE, MORPH_ERODE and more as 'op'.
''' '''
@ -152,6 +257,7 @@ class morph(filter):
iterations = int(params["iterations"]) if params["iterations"] else 1 iterations = int(params["iterations"]) if params["iterations"] else 1
op = getattr(cv, params["op"]) if params["op"] else cv.MORPH_OPEN op = getattr(cv, params["op"]) if params["op"] else cv.MORPH_OPEN
if(params["anchor"]): if(params["anchor"]):
# TODO remove try-except
try: try:
anchor = tuple(map(int, params["anchor"].split(','))) anchor = tuple(map(int, params["anchor"].split(',')))
except AttributeError: except AttributeError:
@ -159,6 +265,8 @@ class morph(filter):
else: else:
anchor = (-1, -1) anchor = (-1, -1)
#print("with params: " + " kernel: \n" + str(kernel) + " anchor: " + str(anchor) + " iterations: " + str(iterations) + " op: " + str(op)) print("with params: kernel: \n" + str(kernel) + " anchor: " +
str(anchor) + " iterations: " + str(iterations) + " op: " + str(op))
self.img = cv.morphologyEx( self.img = cv.morphologyEx(
self.img, op=op, kernel=kernel, anchor=anchor, iterations=iterations) np.uint8(self.img), op=op, kernel=kernel,
anchor=anchor, iterations=iterations)

@ -1,13 +1,12 @@
"""! @file main.py """! @file main.py
@brief Main file for the application @brief Main file for the application
@author xlanro00 @author xlanro00
""" """
# Import basic libraries # Import basic libraries
import argparse as ap import argparse as ap
import sys import sys
import json import json
#from datetime import datetime
# Libraries for image processing # Libraries for image processing
import numpy as np import numpy as np
@ -33,7 +32,6 @@ class apply_filters:
self.config_file = self.args.config[0] self.config_file = self.args.config[0]
self.preset_name = self.args.config[1] self.preset_name = self.args.config[1]
self.config = json.load(open(self.config_file)) self.config = json.load(open(self.config_file))
print("Config loaded")
self.parse_conf() self.parse_conf()
# If no config file given, expect filters in command line # If no config file given, expect filters in command line
@ -63,11 +61,12 @@ class apply_filters:
def run(self): def run(self):
# read as numpy.array # read as numpy.array
self.img = cv.imread(self.input_file, cv.IMREAD_GRAYSCALE) self.img = cv.imread(
self.input_file, cv.IMREAD_GRAYSCALE).astype(np.uint8)
self.width = self.img.shape[1] self.width = self.img.shape[1]
self.height = self.img.shape[0] self.height = self.img.shape[0]
print(self.width, self.height) self.print_size(self.img.shape)
fig = plt.figure(figsize=(self.width, self.height), fig = plt.figure(figsize=(self.width, self.height),
frameon=False, dpi=self.dpi / 100) # dpi is in cm frameon=False, dpi=self.dpi / 100) # dpi is in cm
@ -86,21 +85,27 @@ class apply_filters:
if self.args.stl: if self.args.stl:
self.make_lithophane() self.make_lithophane()
def parse_params(self, params): def parse_params(self, params):
''' Parse parameters of filters.
Set to None if not given.
They are later set in the filter method.
'''
possible_params = {"h", "searchWindowSize", "templateWindowSize", possible_params = {"h", "searchWindowSize", "templateWindowSize",
"ksize", "kernel", "sigmaX", "sigmaY", "ksize", "kernel", "sigmaX", "sigmaY",
"sigmaColor", "sigmaSpace", "d", "anchor", "iterations", "sigmaColor", "sigmaSpace", "d", "anchor", "iterations",
"op", "strength"} "op", "strength", "amount", "radius", "weight", "channelAxis"}
for key in possible_params: for key in possible_params:
try: if params.get(key) is None:
params[key] = params[key]
except KeyError:
params[key] = None params[key] = None
else:
params[key] = params[key]
def parse_conf(self): def parse_conf(self):
# Parse configuration file if given. ''' Parse configuration file if one was given and store filters with their parameters
try: '''
if self.preset_name in self.config:
filter_array = self.config[self.preset_name] filter_array = self.config[self.preset_name]
for i, filter in enumerate(range(len(filter_array)), start=1): for i, filter in enumerate(range(len(filter_array)), start=1):
self.filters.append(filter_array[filter]["name"]) self.filters.append(filter_array[filter]["name"])
@ -109,13 +114,15 @@ class apply_filters:
if attribute != "name": if attribute != "name":
self.params[i][attribute] = value self.params[i][attribute] = value
self.parse_params(self.params[i]) self.parse_params(self.params[i])
print("Loaded preset: " + self.preset_name +
except(KeyError): " from file: " + self.config_file)
else:
print("Preset not found", file=sys.stderr) print("Preset not found", file=sys.stderr)
def parse_arguments(self): def parse_arguments(self):
''' Parse arguments from command line
'''
# Parse arguments
parser = ap.ArgumentParser(prog='main.py', parser = ap.ArgumentParser(prog='main.py',
description='Program for processing a 2D image into 3D fingerprint.', description='Program for processing a 2D image into 3D fingerprint.',
usage='%(prog)s [-h] [-m | --mirror | --no-mirror] input_file output_file dpi ([-c config_file preset | --config config_file preset] | [filters ...])') usage='%(prog)s [-h] [-m | --mirror | --no-mirror] input_file output_file dpi ([-c config_file preset | --config config_file preset] | [filters ...])')
@ -127,10 +134,11 @@ class apply_filters:
help="output file location") help="output file location")
parser.add_argument("dpi", type=int, help="scanner dpi") parser.add_argument("dpi", type=int, help="scanner dpi")
# boolean switch # boolean switch argument
parser.add_argument('-m', "--mirror", help="mirror input image", parser.add_argument('-m', "--mirror", help="mirror input image",
type=bool, action=ap.BooleanOptionalAction) type=bool, action=ap.BooleanOptionalAction)
# another boolean switch argument
parser.add_argument('-s', '--stl', help="make stl model from processed image", parser.add_argument('-s', '--stl', help="make stl model from processed image",
type=bool, action=ap.BooleanOptionalAction) type=bool, action=ap.BooleanOptionalAction)
@ -149,7 +157,7 @@ class apply_filters:
''' Selects filter method of filters library. ''' Selects filter method of filters library.
''' '''
print("Applying " + filter_name + " filter", file=sys.stderr) print("Applying " + filter_name + " filter ", end='')
return getattr(flt, filter_name) return getattr(flt, filter_name)
def resize_image(self): def resize_image(self):
@ -163,7 +171,7 @@ class apply_filters:
should be used only if we want a positive model should be used only if we want a positive model
''' '''
#TODO make this automatic for positive STL # TODO make this automatic for positive STL
print("Mirroring image", file=sys.stderr) print("Mirroring image", file=sys.stderr)
self.img = cv.flip(self.img, 1) # 1 for vertical mirror self.img = cv.flip(self.img, 1) # 1 for vertical mirror
@ -180,11 +188,13 @@ class apply_filters:
# Apply all filters # Apply all filters
for i, filter_name in enumerate(self.filters): for i, filter_name in enumerate(self.filters):
filter = self.filter_factory(filter_name) filter = self.filter_factory(filter_name)
# print(self.img.dtype)
filter.apply(self, self.params[i+1]) filter.apply(self, self.params[i+1])
# print(self.img.dtype)
def print_size(self, size): def print_size(self, size):
print("Width: " + str(size[0]), file=sys.stderr) print("Height: " + str(size[0]), file=sys.stderr)
print("Height: " + str(size[1]), file=sys.stderr) print("Width: " + str(size[1]), file=sys.stderr)
def save_image(self, fig, ax): def save_image(self, fig, ax):
''' Save processed image. ''' Save processed image.
@ -196,7 +206,6 @@ class apply_filters:
fig.savefig(fname=self.output_file) fig.savefig(fname=self.output_file)
def make_lithophane(self): def make_lithophane(self):
pass
'''After processing image, make a lithophane from it. '''After processing image, make a lithophane from it.
''' '''
@ -208,15 +217,21 @@ class apply_filters:
self.save_model() self.save_model()
def make_meshgrid(self): def make_meshgrid(self):
''' Create numpy meshgrid.
Modify image values to get more usable depth values.
Add zero padding to image to make sides of the plate.
'''
# Modify image to make it more suitable depth # Modify image to make it more suitable depth
# values1 = (1 + (1 - self.img/255)/6) * 255/10 # this works # values1 = (1 + (1 - self.img/255)/6) * 255/10 # this works
# values2 = (1 - (1 - self.img/255)/6) * 255/10 # TODO: i dont know how to make white surrounding be extruded # values2 = (1 - (1 - self.img/255)/6) * 255/10 #
# TODO: i dont know how to make white surrounding be extruded
values1better = 28.05 - 0.01*self.img values1better = 28.05 - 0.01*self.img
#values2better = 22.95 - 0.01*self.img #values2better = 22.95 - 0.01*self.img
# (np.around(values2[::300],3)) # (np.around(values2[::300],3))
# Add zero padding to image to make sides of the plate # Add zero padding to image
# TODO this better be done in the next function to keep dimensions intact
self.height = self.img.shape[0] + 2 self.height = self.img.shape[0] + 2
self.width = self.img.shape[1] + 2 self.width = self.img.shape[1] + 2
self.img = np.zeros([self.height, self.width]) self.img = np.zeros([self.height, self.width])
@ -228,6 +243,11 @@ class apply_filters:
self.meshgrid = np.meshgrid(verticesX, verticesY) self.meshgrid = np.meshgrid(verticesX, verticesY)
def make_mesh(self): def make_mesh(self):
''' Create mesh from image.
Create vertices from meshgrid, add depth values from image.
Create faces from vertices.
'''
# Convert meshgrid and image matrix to array of 3D points # Convert meshgrid and image matrix to array of 3D points
vertice_arr = np.vstack(list(map(np.ravel, self.meshgrid))).T vertice_arr = np.vstack(list(map(np.ravel, self.meshgrid))).T
z = (self.img / 10).reshape(-1, 1) z = (self.img / 10).reshape(-1, 1)
@ -278,7 +298,10 @@ class apply_filters:
self.model.vectors[i][j] = vertices[face[j], :] self.model.vectors[i][j] = vertices[face[j], :]
def save_model(self): def save_model(self):
print("Saving stl model", file=sys.stderr) ''' Save final model to stl file.
'''
print("Saving lithophane to stl file", file=sys.stderr)
self.model.save('res/test.stl') self.model.save('res/test.stl')

Loading…
Cancel
Save