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.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.depth_total = float(self.args.stl_file[1])
self.depth_line = float(self.args.stl_file[2])
self.height_line = float(self.args.stl_file[1])
self.mode = "3d"
else:
print("No STL file given, saving image only")
exit(1)
self.input_file = self.args.input_file
self.output_file = self.args.output_file
@ -78,7 +88,6 @@ class app:
self.width = self.img.shape[1]
self.height = self.img.shape[0]
self.print_size(self.img.shape)
print(self.dpi)
fig = plt.figure(figsize=(self.width/self.dpi, self.height/self.dpi),
frameon=False, dpi=self.dpi)
@ -94,7 +103,7 @@ class app:
self.save_image(fig, ax)
plt.close()
if self.args.stl_file:
self.make_lithophane()
self.make_model()
def parse_params(self, params):
''' Parse parameters of filters.
@ -137,22 +146,20 @@ class app:
parser = ap.ArgumentParser(prog='main.py',
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 ...]) [-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
parser.add_argument("input_file", type=str,
help="location with input file")
parser.add_argument("output_file", type=str,
help="output file location")
parser.add_argument("input_file", type=str, help="location with input file")
parser.add_argument("output_file", type=str, help="output file location")
parser.add_argument("dpi", type=int, help="scanner dpi")
# boolean switch argument
parser.add_argument('-m', "--mirror", help="mirror input image",
type=bool, action=ap.BooleanOptionalAction)
parser.add_argument('-m', "--mirror", help="mirror input image", type=bool, action=ap.BooleanOptionalAction)
# another boolean switch argument, this time with value, name of the new file
parser.add_argument('-s', '--stl_file', type=str, nargs=3,
help="make stl model from processed image", required=False)
# another boolean switch argument, this time with value, name of the new file and dimensions
parser.add_argument('-s', '--stl_file', type=str, nargs='*',
help="make planar model from processed image", required=False)
# file with configuration containing presets, new preset name
# pair argument - give both or none
@ -195,10 +202,6 @@ class app:
filter = self.filter_factory(filter_name)
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):
''' Save processed image.
Colormap set to grayscale to avoid color mismatch.
@ -208,24 +211,34 @@ class app:
ax.imshow(self.img, cmap="gray")
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.
'''
print("Making meshgrid", file=sys.stderr)
self.make_meshgrid()
print("Making heighthmap", file=sys.stderr)
self.prepare_heightmap()
if self.mode == "2d":
print("Converting to stl format", file=sys.stderr)
self.make_stl_planar()
#print("Mapping to 3D finger", file=sys.stderr)
#self.map_image_to_3d()
plt.show()
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.
Modify image values to get more usable depth values.
Add zero padding to image to make sides of the plate.
Modify image values to get usable depth values.
'''
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.astype(np.uint8)
print("Total depth:", self.depth_total, "mm, line depth/height:", self.depth_line, "mm")
depth_base = self.depth_total - self.depth_line
if self.mode == "2d":
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
if self.depth_line < 0:
self.img = (depth_base + (1 - self.img/255) * self.depth_line)
# Transform image values to get a heightmap
if self.height_line < 0:
self.img = (self.height_base + (1 - self.img/255)
* self.height_line)
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
# This sets the scale 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)
if self.mode == "3d":
#TODO add some checks and print info
pass
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):
''' Create mesh from image.
''' Create mesh from meshgrid.
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):
ascii stl format consists of repeating struictures:
ascii stl format consists of repeating structures:
facet normal ni nj nk # normal vector
outer loop
@ -264,11 +291,16 @@ class app:
vertex v3x v3y v3z # vertex 3
endloop
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
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)
vertex_arr = np.concatenate((vertex_arr, z), axis=1)
@ -279,12 +311,6 @@ class app:
vertices = []
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
for i in range(self.height - 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+1]])
count = add_faces(count)
count = self.add_faces(faces, count)
# Add faces for the backside of the lithophane
null_arr = np.copy(vertex_arr)
@ -311,7 +337,7 @@ class app:
vertices.append([null_arr[i][j+1]])
vertices.append([null_arr[i+1][j+1]])
count = add_faces(count)
count = self.add_faces(faces, count)
# Horizontal side faces
for j in range(self.height - 1):
@ -320,7 +346,7 @@ class app:
vertices.append([null_arr[j][0]])
vertices.append([null_arr[j+1][0]])
count = add_faces(count)
count = self.add_faces(faces, count)
max = self.width - 1
@ -329,7 +355,7 @@ class app:
vertices.append([null_arr[j+1][max]])
vertices.append([null_arr[j][max]])
count = add_faces(count)
count = self.add_faces(faces, count)
# Vertical side faces
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]])
count = add_faces(count)
count = self.add_faces(faces, count)
max = self.height - 1
@ -347,38 +373,62 @@ class app:
vertices.append([null_arr[max][j]])
vertices.append([null_arr[max][j+1]])
count = add_faces(count)
count = self.add_faces(faces, count)
# Convert to numpy arrays
faces = np.array(faces)
vertices = np.array(vertices)
# 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 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.
'''
self.stl_mesh.save(self.stl_file)
self.stl_mesh_2d.save(self.stl_file)
def map_image_to_3d(self):
''' Map fingerprint to finger model.
'''
x = np.linspace(0, 25.4, self.dpi)
y = np.linspace(0, 25.4, self.dpi)
z1 = np.linspace(0, 10, self.dpi)
z2 = np.linspace(10, 0, self.dpi)
z = np.concatenate((z1, z2))
tmp = np.meshgrid(x, y)
self.mesh_finger = np.meshgrid(tmp,z)
print(len(self.mesh_finger))
#self.finger_base = mesh.Mesh(
#np.zeros(, dtype=mesh.Mesh.dtype))
x = np.linspace(0, self.width * 25.4 / self.dpi, self.width)
y = np.linspace(0, self.height * 25.4 / self.dpi, self.height)
z1 = np.logspace(0, 10, int(np.ceil(self.width / 2)), base=0.7)
z2 = np.logspace(10, 0, int(np.floor(self.width / 2)), base=0.7)
ztemp = 5*np.concatenate((z1, z2))
z = np.array([])
for i in range(self.height):
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
# extrude lines in 1 direction
@ -387,5 +437,20 @@ class app:
# normal projection
# 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()

Loading…
Cancel
Save