Added functionality for finger mapping, adjusted mapping mode parameters.

master
Rostislav Lán 2 years ago
parent 2302428e05
commit 8af2e88b35

@ -7,7 +7,6 @@
import argparse as ap import argparse as ap
from os.path import exists from os.path import exists
import hashlib import hashlib
import math
# Libraries for image processing # Libraries for image processing
import numpy as np import numpy as np
@ -16,11 +15,14 @@ import cv2 as cv
from stl import mesh from stl import mesh
import trimesh import trimesh
import trimesh.transformations as tmtra import trimesh.transformations as tmtra
import trimesh.remesh as tmrem
# Import custom image filter library # Import custom image filter library
import filters as flt import filters as flt
import config_parser as cp import config_parser as cp
import log import log
import math
class fingerprint_app: class fingerprint_app:
'''Main class for the application. '''Main class for the application.
@ -37,7 +39,8 @@ class fingerprint_app:
# Parse configuration from json config file # Parse configuration from json config file
if self.args.config: if self.args.config:
self.config_file, self.preset_name = self.args.config self.config_file, self.preset_name = self.args.config
cp.parse_conf(self.preset_name, self.filters, self.params, self.config_file) cp.parse_conf(self.preset_name, self.filters,
self.params, self.config_file)
elif self.args.filters: elif self.args.filters:
filter_index = 0 filter_index = 0
@ -58,10 +61,11 @@ class fingerprint_app:
self.params[filter_index][key] = value self.params[filter_index][key] = value
cp.parse_params(self.params[filter_index]) cp.parse_params(self.params[filter_index])
# If database flag is set, save filters to database as a new preset # If database flag is set, save filters to database as a new preset
if self.args.database: if self.args.database:
cp.save_preset(self.filters, self.params, self.args.database[0]) cp.save_preset(self.filters, self.params,
self.args.database[0])
else: else:
log.print_message("No filters given, saving original image") log.print_message("No filters given, saving original image")
@ -76,18 +80,18 @@ class fingerprint_app:
self.run_filtering() self.run_filtering()
else: else:
log.error_exit("Input file " + self.input_file + log.error_exit("Input file " + self.input_file +
" does not exist") " does not exist")
if self.args.stl: if self.args.stl:
self.parse_stl() self.parse_stl()
def parse_arguments(self): def parse_arguments(self):
'''Parse arguments from command line using argparse library. '''Parse arguments from command line using argparse library.
''' '''
parser = ap.ArgumentParser(prog='main.py', parser = ap.ArgumentParser(prog='main.py',
description='Program for transforming a 2D image into 3D fingerprint.', description='Program for transforming a 2D image into 3D fingerprint.',
usage='%(prog)s [-h] [-m | --mirror | --no-mirror] [-p] input_file output_file dpi ([-c | --config config_file preset] | [filters ...]) [-s | --stl p height_line height_base | --stl c height_line curv_rate_x curv_rate_y | --stl m height_line]') usage='%(prog)s [-h] [-m | --mirror | --no-mirror] input_file output_file dpi ([-c | --config config_file preset] | [filters ...]) [-s | --stl p height_line height_base | --stl c height_line curv_rate_x curv_rate_y | --stl m height_line]')
# positional arguments # positional arguments
parser.add_argument("input_file", type=str, help="input file path") parser.add_argument("input_file", type=str, help="input file path")
@ -99,7 +103,7 @@ class fingerprint_app:
help="switch to mirror input image") help="switch to mirror input image")
# another boolean switch argument, this time with value, name of the new file and dimensions # another boolean switch argument, this time with value, name of the new file and dimensions
# TODO: behaves absolutely randomly for some reason # TODO: behaves absolutely randomly for some reason, fix it
parser.add_argument('-s', "--stl", type=str, nargs='*', parser.add_argument('-s', "--stl", type=str, nargs='*',
help="create stl model from processed image") help="create stl model from processed image")
@ -135,7 +139,7 @@ class fingerprint_app:
self.height_line = float(self.args.stl[1]) self.height_line = float(self.args.stl[1])
self.height_base = float(self.args.stl[2]) self.height_base = float(self.args.stl[2])
log.print_message("Base height:", self.height_base, log.print_message("Base height:", self.height_base,
"mm, lines depth/height:", self.height_line, "mm") "mm, lines depth/height:", self.height_line, "mm")
elif self.args.stl[0] == 'c': elif self.args.stl[0] == 'c':
self.mode = "curved" self.mode = "curved"
@ -146,24 +150,28 @@ class fingerprint_app:
self.height_base = 10 self.height_base = 10
self.curv_rate_x = 2 self.curv_rate_x = 2
self.curv_rate_y = 6 self.curv_rate_y = 6
log.print_message("Warning: Too few arguments, using default values (2mm lines, curvature 0.5 on x, 0.5 on y)") log.print_message(
"Warning: Too few arguments, using default values (2mm lines, curvature 0.5 on x, 0.5 on y)")
else: else:
self.height_line = float(self.args.stl[1]) self.height_line = float(self.args.stl[1])
self.height_base = float(self.args.stl[2]) self.height_base = float(self.args.stl[2])
self.curv_rate_x = float(self.args.stl[3]) self.curv_rate_x = float(self.args.stl[3])
self.curv_rate_y = float(self.args.stl[4]) self.curv_rate_y = float(self.args.stl[4])
log.print_message("Line height:", self.height_line, "mm, base height:", self.height_base, log.print_message("Line height:", self.height_line, "mm, base height:", self.height_base,
"mm, x axis curvature:", self.curv_rate_x, ", y axis curvature:", self.curv_rate_y) "mm, x axis curvature:", self.curv_rate_x, ", y axis curvature:", self.curv_rate_y)
elif self.args.stl[0] == 'm': elif self.args.stl[0] == 'm':
self.mode = "mapped" self.mode = "mapped"
# TODO: add default values for mapped mode, add finger model? # TODO: add default values for mapped mode, add finger model?
if len(self.args.stl) < 2: if len(self.args.stl) < 6:
log.print_message( log.print_message(
"Warning: Too few arguments, using default values") "Warning: Too few arguments, using default values")
else: else:
self.height_line = float(self.args.stl[1]) self.height_line = float(self.args.stl[1])
self.finger_model = self.args.stl[2] self.iter = int(self.args.stl[2])
self.finger_x = float(self.args.stl[3])
self.finger_y = float(self.args.stl[4])
self.finger_z = float(self.args.stl[5])
else: else:
log.error_exit("Unrecognized generation mode") log.error_exit("Unrecognized generation mode")
@ -219,13 +227,16 @@ class fingerprint_app:
if len(self.filters) != 0: if len(self.filters) != 0:
for i, filter_name in enumerate(self.filters): for i, filter_name in enumerate(self.filters):
# Get filter class from filter.py, use the apply method # Get filter class from filter.py, use the apply method
filter = getattr(flt, filter_name) try:
filter = getattr(flt, filter_name)
except AttributeError:
log.error_exit("Filter " + filter_name + " not found")
log.print_message("Applying filter:", filter_name) log.print_message("Applying filter:", filter_name)
for param in self.params[i+1]: for param in self.params[i+1]:
if self.params[i+1][param] is not None: if self.params[i+1][param] is not None:
log.print_message("\twith parameter", param, log.print_message("\twith parameter", param,
"=", str(self.params[i+1][param])) "=", str(self.params[i+1][param]))
filter.apply(self, self.params[i+1]) filter.apply(self, self.params[i+1])
else: else:
pass pass
@ -260,12 +271,8 @@ class fingerprint_app:
self.make_stl_curved() self.make_stl_curved()
elif self.mode == "mapped": elif self.mode == "mapped":
# TODO: find a more suitable finger model
self.make_stl_map() self.make_stl_map()
else:
log.error_exit("Incorrect stl generation mode")
plt.show() plt.show()
self.save_stl() self.save_stl()
log.print_message("Saving model to", self.stl_filename) log.print_message("Saving model to", self.stl_filename)
@ -297,8 +304,8 @@ class fingerprint_app:
if self.mode == "mapped": if self.mode == "mapped":
if self.height_line <= 0: if self.height_line <= 0:
log.error_exit("Line height must be positive") log.error_exit("Line height must be positive")
if not exists(self.finger_model): if self.iter < 0:
log.error_exit("Finger model file does not exist") log.error_exit("Number of iterations must be positive orr zero")
self.height_base = 0 self.height_base = 0
# TODO: curved height base could be done here? # TODO: curved height base could be done here?
@ -367,8 +374,11 @@ class fingerprint_app:
param_list.append(str(self.curv_rate_y)) param_list.append(str(self.curv_rate_y))
if self.mode == "mapped": if self.mode == "mapped":
#TODO param_list.append(str(self.height_line))
pass param_list.append(str(self.iter))
param_list.append(str(self.finger_x))
param_list.append(str(self.finger_y))
param_list.append(str(self.finger_z))
if self.mode == "planar": if self.mode == "planar":
param_list.append("P") param_list.append("P")
@ -384,7 +394,8 @@ class fingerprint_app:
# fill the rest with the ending char to rewrite any leftover header # fill the rest with the ending char to rewrite any leftover header
# this is done for easier parsing of the header # this is done for easier parsing of the header
self.param_string = "\\".join(param_list) self.param_string = "\\".join(param_list)
self.param_string = self.param_string + "\n" * (80 - len(self.param_string)) self.param_string = self.param_string + \
"\n" * (80 - len(self.param_string))
# hash the param string to get unique ID, this will be put in filename and on the back of the model # hash the param string to get unique ID, this will be put in filename and on the back of the model
# not using built-in hash function because it's seed cannot be set to constant number # not using built-in hash function because it's seed cannot be set to constant number
@ -435,7 +446,6 @@ class fingerprint_app:
plt.close() plt.close()
# TODO: maybe don't use nested for loops, use numpy?
# TODO: this is very badly written, fix it # TODO: this is very badly written, fix it
# TODO: this does not always work, fix it # TODO: this does not always work, fix it
# add the bottom array # add the bottom array
@ -498,7 +508,6 @@ class fingerprint_app:
vertices = [] vertices = []
faces = [] faces = []
# TODO: don't like this, could be done using numpy vectorisation?
# Iterate over all vertices, create faces # Iterate over all vertices, create faces
for i in range(self.height - 1): for i in range(self.height - 1):
for j in range(self.width - 1): for j in range(self.width - 1):
@ -564,7 +573,10 @@ class fingerprint_app:
self.create_stl_mesh(faces, vertices) self.create_stl_mesh(faces, vertices)
def make_stl_curved(self): def make_stl_curved(self):
'''Map fingerprint to finger model. '''Compute curved surface.
Create mesh from meshgrid.
Create vertices from meshgrid, add depth values from image.
Create faces from vertices. Add vectors and faces to the model.
''' '''
# TODO: this might be done in a better way, comment # TODO: this might be done in a better way, comment
@ -672,50 +684,82 @@ class fingerprint_app:
def make_stl_map(self): def make_stl_map(self):
'''Map fingerprint to a given finger model. '''Map fingerprint to a given finger model.
Experimental, does not work very well...
''' '''
# TODO: maybe use trimesh.update_vertices # Conversion constants for mm and pixels
mm2px = self.dpi/25.4
log.print_message("Mapping to finger") px2mm = 25.4/self.dpi
# TODO: try to merge meshes? or stl files?
# trimesh library? # Finds the image pixel closest to finger vertice in 2D plane
finger = trimesh.load(self.finger_model) def find_nearest(ver1, ver2, img):
# TODO: connect with curved generation searched_point = np.array([ver1, ver2])
# manually tried to allign two models and concatenated min1 = math.floor(ver1*mm2px)
fingerprint = trimesh.load('res/0-norm1_e5f52c0fe1.stl') max1 = math.ceil(ver1*mm2px)
min2 = math.floor(ver2*mm2px)
angle = math.pi max2 = math.ceil(ver2*mm2px)
dir = [0, 0, 1] min_dist_point = img[min2][min1]
center = [0, 0, 0]
mat = tmtra.rotation_matrix(angle, dir, center) for i in range(min2, max2 - 1):
finger.apply_transform(mat) for j in range(min1, max1 - 1):
if np.linalg.norm(img[i][j] - searched_point) < min_dist_point:
angle = -3 / 4 * math.pi min_dist_point = img[i][j]
dir = [1, 0, 0] return min_dist_point
center = [0, 0, 0]
mat = tmtra.rotation_matrix(angle, dir, center) # Load the finger model
finger.apply_transform(mat) finger = trimesh.load("res/finger-mod.stl")
# Implicitly translate it to match middle of the fingerprint
# TODO: random values that works for one finger model... # Later this can be modified
# TODO: this can later be modified to map finger to the core of the finger. x = (self.width * px2mm / 2) + self.finger_x
x = 2 + (self.width * 25.4 / self.dpi / 2) y = (self.height * px2mm / 2) + self.finger_y
y = 5 + (self.height * 25.4 / self.dpi / 2) z = self.finger_z
z = 20 matrix = tmtra.translation_matrix([x, y, z])
mat = tmtra.translation_matrix( finger.apply_transform(matrix)
[x, y, z])
finger.apply_transform(mat) # Subdivide the finger mesh to allow for more precision
vertices, faces = tmrem.subdivide_loop(
self.stl_model = trimesh.util.concatenate([finger, fingerprint]) finger.vertices, finger.faces, iterations=self.iter)
# For logging progress
c = 0
u = 0
for k, vertice in enumerate(vertices):
# Skip vertices under plane xy
# also skip vertices under the fingerprint image,
# they are all unused
if vertice[2] < 0 or vertice[1] > self.height * px2mm:
u += 1
continue
# This is the easiest way to avoid indexing errors
# Those errors are caused by vertices outside of the image
# When this occurs, input parameters need to be adjusted
try:
# Find the closest point in the image
# To the 2D image projection of vertice, add its value
point = find_nearest(vertice[0], vertice[1], self.img)
except IndexError:
log.error_exit("Fingerprint image is outside of the finger model")
vertices[k][2] += point
# Prints out generation progress
if k % 1000 == 0:
percentage = round(k/len(vertices) * 100, 2)
if percentage > c:
log.print_message("Carving finger: " + str(c) + "%")
c += 10
self.stl_model = trimesh.Trimesh(vertices, faces)
log.print_message("Carving finger finished")
print("Unused vertices: " + str(u))
def save_stl(self): def save_stl(self):
'''Save final mesh to stl file. '''Save final mesh to stl file.
''' '''
# create output file name, save it and write header with file info # Create output file name, save it and write header with file info
self.stl_filename = self.output_file.split( self.stl_filename = self.output_file.split(
".")[0] + "_" + self.id + ".stl" ".")[0] + "_" + self.id + ".stl"
if (self.mode == "mapped"): if (self.mode == "mapped"):

Loading…
Cancel
Save