Added unfinished basic 3D finger generation.

master
Rostislav Lán 2 years ago
parent cc166cb5c0
commit 05c5a6c09f

@ -55,10 +55,20 @@ class app:
self.params[i][key] = value self.params[i][key] = value
self.parse_params(self.params[i]) self.parse_params(self.params[i])
if self.args.stl_file: if self.args.stl_file and len(self.args.stl_file) == 3:
self.stl_file = self.args.stl_file[0]
self.height_line = float(self.args.stl_file[1])
self.height_base = float(self.args.stl_file[2])
self.mode = "2d"
elif self.args.stl_file and len(self.args.stl_file) == 2:
self.stl_file = self.args.stl_file[0] self.stl_file = self.args.stl_file[0]
self.depth_total = float(self.args.stl_file[1]) self.height_line = float(self.args.stl_file[1])
self.depth_line = float(self.args.stl_file[2]) self.mode = "3d"
else:
print("No STL file given, saving image only")
exit(1)
self.input_file = self.args.input_file self.input_file = self.args.input_file
self.output_file = self.args.output_file self.output_file = self.args.output_file
@ -78,7 +88,6 @@ class app:
self.width = self.img.shape[1] self.width = self.img.shape[1]
self.height = self.img.shape[0] self.height = self.img.shape[0]
self.print_size(self.img.shape) self.print_size(self.img.shape)
print(self.dpi)
fig = plt.figure(figsize=(self.width/self.dpi, self.height/self.dpi), fig = plt.figure(figsize=(self.width/self.dpi, self.height/self.dpi),
frameon=False, dpi=self.dpi) frameon=False, dpi=self.dpi)
@ -94,7 +103,7 @@ class app:
self.save_image(fig, ax) self.save_image(fig, ax)
plt.close() plt.close()
if self.args.stl_file: if self.args.stl_file:
self.make_lithophane() self.make_model()
def parse_params(self, params): def parse_params(self, params):
''' Parse parameters of filters. ''' Parse parameters of filters.
@ -137,22 +146,20 @@ class app:
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 \ usage='%(prog)s [-h] [-m | --mirror | --no-mirror] input_file output_file dpi \
([-c config_file preset | --config config_file preset] | [filters ...]) [-s stl_file | --stl_file stl_file depth_total depth_line]') ([-c config_file preset | --config config_file preset] | [filters ...]) \
[-s stl_file | --stl_file stl_file depth_total depth_line]')
# positional arguments # positional arguments
parser.add_argument("input_file", type=str, parser.add_argument("input_file", type=str, help="location with input file")
help="location with input file") parser.add_argument("output_file", type=str, help="output file location")
parser.add_argument("output_file", type=str,
help="output file location")
parser.add_argument("dpi", type=int, help="scanner dpi") parser.add_argument("dpi", type=int, help="scanner dpi")
# boolean switch argument # 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, this time with value, name of the new file # another boolean switch argument, this time with value, name of the new file and dimensions
parser.add_argument('-s', '--stl_file', type=str, nargs=3, parser.add_argument('-s', '--stl_file', type=str, nargs='*',
help="make stl model from processed image", required=False) help="make planar model from processed image", required=False)
# file with configuration containing presets, new preset name # file with configuration containing presets, new preset name
# pair argument - give both or none # pair argument - give both or none
@ -195,10 +202,6 @@ class app:
filter = self.filter_factory(filter_name) filter = self.filter_factory(filter_name)
filter.apply(self, self.params[i+1]) filter.apply(self, self.params[i+1])
def print_size(self, size):
print("Height: " + str(size[0]), 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.
Colormap set to grayscale to avoid color mismatch. Colormap set to grayscale to avoid color mismatch.
@ -208,24 +211,34 @@ class app:
ax.imshow(self.img, cmap="gray") ax.imshow(self.img, cmap="gray")
fig.savefig(fname=self.output_file, dpi='figure') fig.savefig(fname=self.output_file, dpi='figure')
def make_lithophane(self): def print_size(self, size):
print("Image of height: " + str(size[0]) +
" px and width: " + str(size[1]) + " px", file=sys.stderr)
def make_model(self):
'''After processing image, make a lithophane from it. '''After processing image, make a lithophane from it.
''' '''
print("Making meshgrid", file=sys.stderr) print("Making heighthmap", file=sys.stderr)
self.make_meshgrid() self.prepare_heightmap()
if self.mode == "2d":
print("Converting to stl format", file=sys.stderr) print("Converting to stl format", file=sys.stderr)
self.make_stl_planar() self.make_stl_planar()
#print("Mapping to 3D finger", file=sys.stderr)
#self.map_image_to_3d()
plt.show() plt.show()
print(f"Saving lithophane to ", self.stl_file, file=sys.stderr) print(f"Saving lithophane to ", self.stl_file, file=sys.stderr)
self.save_stl() self.save_stl_2d()
elif self.mode == "3d":
self.map_image_to_3d()
plt.show()
self.save_stl_3d()
else:
print("Mode not supported", file=sys.stderr)
exit(1)
def make_meshgrid(self): def prepare_heightmap(self):
''' Create numpy meshgrid. ''' Create numpy meshgrid.
Modify image values to get more usable depth values. Modify image values to get usable depth values.
Add zero padding to image to make sides of the plate.
''' '''
if self.img.dtype == np.float32 or self.img.dtype == np.float64: if self.img.dtype == np.float32 or self.img.dtype == np.float64:
@ -233,29 +246,43 @@ class app:
self.img = self.img * 255 self.img = self.img * 255
self.img = self.img.astype(np.uint8) self.img = self.img.astype(np.uint8)
print("Total depth:", self.depth_total, "mm, line depth/height:", self.depth_line, "mm") if self.mode == "2d":
depth_base = self.depth_total - self.depth_line if self.height_base <= 0:
print("Depth of plate height must be positive", file=sys.stderr)
exit(1)
if self.height_line + self.height_base <= 0:
print("Line depth must be less than plate thickness", file=sys.stderr)
exit(1)
print("Base height:", self.height_base,
"mm, lines depth/height:", self.height_line, "mm")
# Make depth map from image # Transform image values to get a heightmap
if self.depth_line < 0: if self.height_line < 0:
self.img = (depth_base + (1 - self.img/255) * self.depth_line) self.img = (self.height_base + (1 - self.img/255)
* self.height_line)
else: else:
self.img = (depth_base + (1 - self.img/255) * self.depth_line) self.img = (self.height_base + (1 - self.img/255)
* self.height_line)
# Create meshgrid for 3D model if self.mode == "3d":
# This sets the scale of stl model and number of subdivisions / triangles #TODO add some checks and print info
x = np.linspace(0, self.width * 25.4 / self.dpi, self.width) pass
y = np.linspace(0, self.height * 25.4 / self.dpi, self.height)
self.meshgrid = np.meshgrid(x, y) def add_faces(self, faces, c):
# Function to add faces to the list
faces.append([c, c + 1, c + 2])
faces.append([c + 1, c + 3, c + 2])
return c + 4
def make_stl_planar(self): def make_stl_planar(self):
''' Create mesh from image. ''' Create mesh from meshgrid.
Create vertices from meshgrid, add depth values from image. Create vertices from meshgrid, add depth values from image.
Create faces from vertices. Add veectors to the model. Create faces from vertices. Add vectors and faces to the model.
From wikipedia.org/wiki/STL_(file_format): From wikipedia.org/wiki/STL_(file_format):
ascii stl format consists of repeating struictures: ascii stl format consists of repeating structures:
facet normal ni nj nk # normal vector facet normal ni nj nk # normal vector
outer loop outer loop
@ -264,11 +291,16 @@ class app:
vertex v3x v3y v3z # vertex 3 vertex v3x v3y v3z # vertex 3
endloop endloop
endfacet endfacet
''' '''
# This sets the size of stl model and number of subdivisions / triangles
x = np.linspace(0, self.width * 25.4 / self.dpi, self.width)
y = np.linspace(0, self.height * 25.4 / self.dpi, self.height)
self.meshgrid_2d = np.meshgrid(x, y)
# Add the image matrix to the 2D meshgrid and create 1D array of 3D points # Add the image matrix to the 2D meshgrid and create 1D array of 3D points
vertex_arr = np.vstack(list(map(np.ravel, self.meshgrid))).T vertex_arr = np.vstack(list(map(np.ravel, self.meshgrid_2d))).T
z = (self.img / 10).reshape(-1, 1) z = (self.img / 10).reshape(-1, 1)
vertex_arr = np.concatenate((vertex_arr, z), axis=1) vertex_arr = np.concatenate((vertex_arr, z), axis=1)
@ -279,12 +311,6 @@ class app:
vertices = [] vertices = []
faces = [] faces = []
# Function to add faces to the list
def add_faces(c):
faces.append([c, c + 1, c + 2])
faces.append([c + 1, c + 3, c + 2])
return c + 4
# 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):
@ -294,7 +320,7 @@ class app:
vertices.append([vertex_arr[i+1][j]]) vertices.append([vertex_arr[i+1][j]])
vertices.append([vertex_arr[i+1][j+1]]) vertices.append([vertex_arr[i+1][j+1]])
count = add_faces(count) count = self.add_faces(faces, count)
# Add faces for the backside of the lithophane # Add faces for the backside of the lithophane
null_arr = np.copy(vertex_arr) null_arr = np.copy(vertex_arr)
@ -311,7 +337,7 @@ class app:
vertices.append([null_arr[i][j+1]]) vertices.append([null_arr[i][j+1]])
vertices.append([null_arr[i+1][j+1]]) vertices.append([null_arr[i+1][j+1]])
count = add_faces(count) count = self.add_faces(faces, count)
# Horizontal side faces # Horizontal side faces
for j in range(self.height - 1): for j in range(self.height - 1):
@ -320,7 +346,7 @@ class app:
vertices.append([null_arr[j][0]]) vertices.append([null_arr[j][0]])
vertices.append([null_arr[j+1][0]]) vertices.append([null_arr[j+1][0]])
count = add_faces(count) count = self.add_faces(faces, count)
max = self.width - 1 max = self.width - 1
@ -329,7 +355,7 @@ class app:
vertices.append([null_arr[j+1][max]]) vertices.append([null_arr[j+1][max]])
vertices.append([null_arr[j][max]]) vertices.append([null_arr[j][max]])
count = add_faces(count) count = self.add_faces(faces, count)
# Vertical side faces # Vertical side faces
for j in range(self.width - 1): for j in range(self.width - 1):
@ -338,7 +364,7 @@ class app:
vertices.append([null_arr[0][j+1]]) vertices.append([null_arr[0][j+1]])
vertices.append([null_arr[0][j]]) vertices.append([null_arr[0][j]])
count = add_faces(count) count = self.add_faces(faces, count)
max = self.height - 1 max = self.height - 1
@ -347,38 +373,62 @@ class app:
vertices.append([null_arr[max][j]]) vertices.append([null_arr[max][j]])
vertices.append([null_arr[max][j+1]]) vertices.append([null_arr[max][j+1]])
count = add_faces(count) count = self.add_faces(faces, count)
# Convert to numpy arrays # Convert to numpy arrays
faces = np.array(faces) faces = np.array(faces)
vertices = np.array(vertices) vertices = np.array(vertices)
# Create the mesh - vertices.shape (no_faces, 3, 3) # Create the mesh - vertices.shape (no_faces, 3, 3)
self.stl_mesh = mesh.Mesh(np.zeros(faces.shape[0], dtype=mesh.Mesh.dtype)) self.stl_mesh_2d = mesh.Mesh(np.zeros(faces.shape[0], dtype=mesh.Mesh.dtype))
for i, face in enumerate(faces): for i, face in enumerate(faces):
for j in range(3): for j in range(3):
self.stl_mesh.vectors[i][j] = vertices[face[j], :] self.stl_mesh_2d.vectors[i][j] = vertices[face[j], :]
def save_stl(self): def save_stl_2d(self):
''' Save final mesh to stl file. ''' Save final mesh to stl file.
''' '''
self.stl_mesh.save(self.stl_file) self.stl_mesh_2d.save(self.stl_file)
def map_image_to_3d(self): def map_image_to_3d(self):
''' Map fingerprint to finger model. ''' Map fingerprint to finger model.
''' '''
x = np.linspace(0, 25.4, self.dpi) x = np.linspace(0, self.width * 25.4 / self.dpi, self.width)
y = np.linspace(0, 25.4, self.dpi) y = np.linspace(0, self.height * 25.4 / self.dpi, self.height)
z1 = np.linspace(0, 10, self.dpi)
z2 = np.linspace(10, 0, self.dpi) z1 = np.logspace(0, 10, int(np.ceil(self.width / 2)), base=0.7)
z = np.concatenate((z1, z2)) z2 = np.logspace(10, 0, int(np.floor(self.width / 2)), base=0.7)
tmp = np.meshgrid(x, y) ztemp = 5*np.concatenate((z1, z2))
self.mesh_finger = np.meshgrid(tmp,z)
print(len(self.mesh_finger)) z = np.array([])
#self.finger_base = mesh.Mesh( for i in range(self.height):
#np.zeros(, dtype=mesh.Mesh.dtype)) z = np.concatenate((z, ztemp * pow(np.log(i+2), -1)))
z = z.reshape(-1, 1)
self.meshgrid_3d = np.meshgrid(x, y)
vertex_arr = np.vstack(list(map(np.ravel, self.meshgrid_3d))).T
vertex_arr = np.concatenate((vertex_arr, z), axis=1)
vertex_arr = vertex_arr.reshape(self.height, self.width, 3)
count = 0
vertices = []
faces = []
# Iterate over all vertices, create faces
for i in range(self.height - 1):
for j in range(self.width - 1):
vertices.append([vertex_arr[i][j]])
vertices.append([vertex_arr[i][j+1]])
vertices.append([vertex_arr[i+1][j]])
vertices.append([vertex_arr[i+1][j+1]])
count = self.add_faces(faces, count)
#self.finger_base = mesh.Mesh(np.zeros(, dtype=mesh.Mesh.dtype))
# linear projection # linear projection
# extrude lines in 1 direction # extrude lines in 1 direction
@ -387,5 +437,20 @@ class app:
# normal projection # normal projection
# extrude lines in the direction of normals of given finger model # extrude lines in the direction of normals of given finger model
# Convert to numpy arrays
faces = np.array(faces)
vertices = np.array(vertices)
# Create the mesh - vertices.shape (no_faces, 3, 3)
self.mesh_finger = mesh.Mesh(np.zeros(faces.shape[0], dtype=mesh.Mesh.dtype))
for i, face in enumerate(faces):
for j in range(3):
self.mesh_finger.vectors[i][j] = vertices[face[j], :]
def save_stl_3d(self):
''' Save final mesh to stl file.
'''
self.mesh_finger.save(self.stl_file)
image = app() image = app()

Loading…
Cancel
Save