diff --git a/.github/img1.png b/.github/img1.png new file mode 100644 index 0000000..2b73fe3 Binary files /dev/null and b/.github/img1.png differ diff --git a/.github/img2.png b/.github/img2.png new file mode 100644 index 0000000..ba8665e Binary files /dev/null and b/.github/img2.png differ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ceaaa64 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +test.py +#enviroment +env +__pycache__ +# images +images/* +enhanced/* +# building +guifinger.spec +build +dist \ No newline at end of file diff --git a/GUI_main.py b/GUI_main.py new file mode 100755 index 0000000..6041d28 --- /dev/null +++ b/GUI_main.py @@ -0,0 +1,193 @@ +#! ./env/bin/python +import os +import subprocess +from os.path import join + +import cv2 +import tkinter as tk +from tkinter import * +from tkinter import ttk, filedialog + +from main_enhancement import ROOT, IMG_FORMATS, SUFFIX, image_enhance_from + +COLORS = { + 'primary': '#1976D2', + 'secondary': '#424242', + 'accent': '#82B1FF', + 'error': '#FF5252', + 'info': '#2196F3', + 'success': '#4CAF50', + 'warning': '#FFC107' +} + + +class FingerprintImgEnhancer: + def __init__(self, master: Tk, title: str) -> None: + self.master = master + self.master.title(title) + # properties + self.inp_folder = 'images' + self.tar_folder = 'enhanced' + # labels + Label( + master=self.master, + text='Esta es una aplicacion para mejorar imagenes de huellas digitales').pack(pady=5) + Label(self.master, text='Imagenes seleccionadas').pack(pady=2) + # list box + self.dir_list_box = Listbox(self.master) + self.dir_list_box.pack(fill='x', padx=100) + # butons + self.select_dir_b = Button( + master=self.master, + text='Selecciona un directorio', + command=self.select_directory) + self.select_dir_b.pack(pady=5) + Button( + master=self.master, + text='Abrir carpeta destino', + command=self.open_enhance_dir + ).pack(pady=5) + self.enhace_b = Button( + master=self.master, + text='Mejorar!', + command=self.start_enhance) + self.enhace_b.pack(fill=None, anchor="s", + side="left", padx=30, pady=30) + self.enhace_b['state'] = 'normal' if self.update_dir_list( + self.inp_folder) else 'disable' + self.exit_b = Button( + master=self.master, + bg=COLORS['error'], + text='Salir', + command=self.master.quit) + self.exit_b.pack(fill=None, anchor="s", + side="right", padx=30, pady=30) + + def select_directory(self): + inp_folder = filedialog.askdirectory() + self.inp_folder = inp_folder if inp_folder else self.inp_folder + self.enhace_b['state'] = 'normal' if self.update_dir_list( + self.inp_folder) else 'disable' + + def update_dir_list(self, inp_folder) -> bool: + self.files = [ + filename for filename in os.listdir(inp_folder) if filename.split('.')[-1] in IMG_FORMATS] + self.dir_list_box.delete(0, 'end') + for i, filename in enumerate(self.files): + self.dir_list_box.insert(i+1, filename) + self.dir_list_box.update() + return bool(self.files) + + def start_enhance(self): + dialog = EnhancerDialog( + self, self.master, 'Progress', + message='¿Desea mejorar estas imagenes?') + dialog.minsize(width=340, height=80) + + def open_enhance_dir(self): + try: + subprocess.run(['open', 'enhanced'], check=True) + except Exception as e: + print(e) + try: + subprocess.run(['open', 'enhanced'], check=True) + except Exception as e: + print(e) + + +class EnhancerDialog(Toplevel): + def __init__(self, app: FingerprintImgEnhancer, master: Tk, title: str, message): + super().__init__(master) + # properties + self.app = app + self.title(title) + self.wm_attributes("-type", "dialog") + self.text = tk.StringVar() + self.text.set(message) + self.label = Label(self, textvariable=self.text) + self.label.pack(padx=10, pady=10) + # progress bar + self.progbar = ttk.Progressbar( + self, orient=HORIZONTAL, length=300, mode='determinate') + self.progbar.pack(pady=2) + # buttoms + self.frame = Frame(self) + self.frame.pack(side='bottom') + self.start_b = Button( + master=self.frame, + text='Iniciar', + bg=COLORS['primary'], + command=self.start) + self.start_b.pack(fill='y', anchor="s", side='left') + self.cancel_b = Button( + master=self.frame, + text='Cancel', + bg=COLORS['error'], + command=self.__cancel) + self.cancel_b.pack(fill='y', anchor="s", side='left') + + def start(self): + self.__disable_buttoms() + self.master.update + self.update + step_size = 100 / len(self.app.files) + for img_name in self.app.files: + self.__update_dialog(img_name) + enhanced_img = image_enhance_from( + join(self.app.inp_folder, img_name)) + enhanced_img_name = img_name.split('.') + enhanced_img_name = enhanced_img_name[0] + \ + SUFFIX + enhanced_img_name[-1] + cv2.imwrite(join(self.app.tar_folder, + enhanced_img_name), enhanced_img) + self.__update_dialog(img_name) + self.progbar['value'] += step_size + self.__normal_buttoms() + self.text.set('Imagenes transformadas exitosamente.') + self.start_b.destroy() + self.cancel_b.destroy() + Button( + master=self.frame, text='Ok!', + command=self.destroy, padx=20 + ).pack(fill='y', anchor="s", side='left') + self.update() + + def __disable_buttoms(self): + self.app.enhace_b['state'] = 'disable' + self.app.exit_b['state'] = 'disable' + self.app.select_dir_b['state'] = 'disable' + self.start_b['state'] = 'disable' + #self.cancel_b['state'] = 'disable' + + def __normal_buttoms(self): + self.app.enhace_b['state'] = 'normal' + self.app.exit_b['state'] = 'normal' + self.app.select_dir_b['state'] = 'normal' + self.start_b['state'] = 'normal' + #self.cancel_b['state'] = 'normal' + + def __update_dialog(self, img_name): + text_filename = f'{img_name[:10]}...' if len( + img_name) > 10 else img_name + self.text.set(f'Imagen: {text_filename:15} en progreso') + self.master.update() + self.update() + + def __cancel(self): + self.__normal_buttoms() + self.update + self.master.update + self.destroy() + + +if __name__ == '__main__': + if not os.path.exists('images'): + os.mkdir('images') + if not os.path.exists('enhanced'): + os.mkdir('enhanced') + root = Tk() + root.minsize(width=600, height=440) + root.maxsize(width=600, height=440) + root.geometry('540x360') + feapp = FingerprintImgEnhancer(root, 'Fingerprint enhancer') + root.mainloop() diff --git a/README.md b/README.md index e5f2248..dbbd0df 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,73 @@ # Fingerprint-Enhancement-Python -Using oriented gabor filters to enhance fingerprint images based on https://github.com/Utkarsh-Deshmukh/Fingerprint-Enhancement-Python +Using oriented gabor filters to enhance fingerprint images based on https://github.com/ylevalle/Fingerprint-Enhancement-Python + +this version is written in python3 # Usage: python main_enhancement.py -The fingerprint image to enhance must be in the images directory, the default name is 1.jpg. The enhanced image will be located on the directory enhanced and the default name is enhanced.jpg +The images of the fingerprint for the enhancement must be in the "images" directory, the program will enhance all the images in that directory and they will be located in the "enhanced" directory with the same names and the suffix "_enhanced" + +# Requirements +* [Python v3.9](https://www.python.org/downloads/release/python-398/) + +## Installation of requirements + +Execute installation.sh +```bash +./installation.sh +``` + +## Usage: +### Terminal +1. Copy your photos in png or jpg format to the images folder. +2. execute `execution` file: +```bash +./execution +``` +Output: +``` +Found 1 images in "images" folder +Processing images ... +Processing 1.jpg: (1/1) + All enhanced images saved in the "enhanced" folder. +done. +``` +Results: +``` +enhanced +└── 1_enhanced.jpg + +0 directories, 1 file +``` +### GUI +1. Execute `finger-print-enhancer-gui` file: +```bash +./finger-print-enhancer-gui +``` +or +**Double click on this file**. Example: +![img1](.github/img1.png) +this window will appear +![img2](.github/img2.png) + -This version doesn't work on Python 3 yet # Requirements: -numpy -cv2 -scipy -matplotlib -imageio +install [python](https://www.python.org/) + +```r +numpy==1.21.2 +opencv-python==4.5.3.56 +scipy==1.7.1 +``` +Install it: +```sh +pip install -r requirements.txt +``` +# Colaborators +* **Willy Samuel Paz Colque** +# Donations +If you want to help me be a better developer and continue collaborating in these projects, you can invite me a caffe, I would appreciate it. [Donate](https://paypal.me/WillyPaz95?country.x=UY&locale.x=es_XC) + diff --git a/README.pdf b/README.pdf new file mode 100644 index 0000000..6195294 Binary files /dev/null and b/README.pdf differ diff --git a/enhanced/enhanced.jpg b/enhanced/enhanced.jpg deleted file mode 100644 index c2ddabe..0000000 Binary files a/enhanced/enhanced.jpg and /dev/null differ diff --git a/execution b/execution new file mode 100755 index 0000000..0e7e94b --- /dev/null +++ b/execution @@ -0,0 +1,2 @@ +#! /bin/bash +$(pwd)/main_enhancement.py \ No newline at end of file diff --git a/finger-print-enhancer-gui b/finger-print-enhancer-gui new file mode 100755 index 0000000..99b90eb --- /dev/null +++ b/finger-print-enhancer-gui @@ -0,0 +1,2 @@ +#! /bin/bash +$(pwd)/GUI_main.py diff --git a/fpe-icon.jpg b/fpe-icon.jpg new file mode 100644 index 0000000..48d5804 Binary files /dev/null and b/fpe-icon.jpg differ diff --git a/images/1.jpg b/images/1.jpg deleted file mode 100644 index 69201ff..0000000 Binary files a/images/1.jpg and /dev/null differ diff --git a/installation.sh b/installation.sh new file mode 100755 index 0000000..0be3bfb --- /dev/null +++ b/installation.sh @@ -0,0 +1,23 @@ +#! /bin/bash +python3 -m venv env +source ./env/bin/activate +pip install -r requirements.txt +chmod +x ./main_enhancement.py + +# creating desktop icon + +rm fp-enhancer.desktop +touch fp-enhancer.desktop + +chmod +x fp-enhancer.desktop + +echo fp-enhancer.desktop fue creado, puede acceder a la aplicacion haciendo doble click en fp-enhancer.desktop +echo [Desktop Entry] >> fp-enhancer.desktop +echo Version=0.0.1 >> fp-enhancer.desktop +echo Name=FingerPrintEnhancer >> fp-enhancer.desktop +echo Comment=Interfaz grafica para el mejorador de huellas digitales >> fp-enhancer.desktop +echo Exec=$(pwd)/finger-print-enhancer-gui >> fp-enhancer.desktop +echo Icon=$(pwd)/fpe-icon.jpg >> fp-enhancer.desktop +echo Terminal=false >> fp-enhancer.desktop +echo Type=Application >> fp-enhancer.desktop +echo "Categories=Utility;Application;" >> fp-enhancer.desktop diff --git a/main_enhancement.py b/main_enhancement.py new file mode 100755 index 0000000..3e22b26 --- /dev/null +++ b/main_enhancement.py @@ -0,0 +1,59 @@ +#! ./env/bin/python3 +import os +from os.path import abspath, dirname, join, exists + +import cv2 +import numpy as np +from numpy import ndarray as NumpyArray + +from src import image_enhance + +ROOT = abspath(dirname(__file__)) +IMG_FORMATS = ['jpg', 'png', 'jpeg'] +IMGS_PATH = join(ROOT, 'images') +ENHANCED_PATH = join(ROOT, 'enhanced') +SUFFIX = '_enhanced.' + +def image_enhance_from(image_file:str): + img: NumpyArray = cv2.imread(image_file) + if(len(img.shape)>2): + img = np.dot(img[...,:3], [0.299, 0.587, 0.114]) + rows, cols = img.shape + aspect_ratio = rows / cols + new_rows: int = 350 + new_cols: int = int(new_rows / aspect_ratio) + img: NumpyArray = cv2.resize(img, (new_cols, new_rows)) + enhanced_img: NumpyArray = image_enhance(img) + enhanced_img = ((1 - enhanced_img) * 255).astype(np.uint8) + return enhanced_img + +def main(): + image_names = os.listdir(IMGS_PATH) + image_names = list(filter(lambda fname: fname.split('.') + [-1] in IMG_FORMATS, image_names)) + + print(f'Found {len(image_names)} images in "images" folder') + + print('Processing images ...') + images_processed = False + for i, img_name in enumerate(image_names): + enhanced_img_name = img_name.split('.') + enhanced_img_name = enhanced_img_name[0] + SUFFIX + enhanced_img_name[-1] + if exists(join(ENHANCED_PATH, enhanced_img_name)): + print(f'{img_name} is already processed: ({i+1}/{len(image_names)})') + continue # if an image exists its enhanced, nothing is done + images_processed = True + print(f'Processing {img_name}: ({i+1}/{len(image_names)})') + enhanced_img = image_enhance_from(join(IMGS_PATH, img_name)) + cv2.imwrite(join(ENHANCED_PATH, enhanced_img_name), enhanced_img) + + if images_processed: + print('\tAll enhanced images saved in the "enhanced" folder.') + else: + print('\tNo image was processed, it probably already exists') + print('\tits enhanced versions in the "enhanced" folder.') + print('done.') + + +if __name__ == '__main__': + main() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..1ce3090 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +numpy==1.21.2 +opencv-python==4.5.3.56 +scipy==1.7.1 diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000..4eaea47 --- /dev/null +++ b/src/__init__.py @@ -0,0 +1 @@ +from .image_enhance import image_enhance \ No newline at end of file diff --git a/src/image_enhance.py b/src/image_enhance.py index eeb2497..a4d1785 100644 --- a/src/image_enhance.py +++ b/src/image_enhance.py @@ -1,28 +1,31 @@ -# -*- coding: utf-8 -*- +from .utils import ridge_segment, ridge_orient, ridge_freq, ridge_filter -from ridge_segment import ridge_segment -from ridge_orient import ridge_orient -from ridge_freq import ridge_freq -from ridge_filter import ridge_filter def image_enhance(img): - blksze = 16; - thresh = 0.1; - normim,mask = ridge_segment(img,blksze,thresh); # normalise the image and find a ROI + blksze = 16 + thresh = 0.1 + # normalise the image and find a ROI + normim, mask = ridge_segment(img, blksze, thresh) - gradientsigma = 1; - blocksigma = 7; - orientsmoothsigma = 7; - orientim = ridge_orient(normim, gradientsigma, blocksigma, orientsmoothsigma); # find orientation of every pixel + gradientsigma = 1 + blocksigma = 7 + orientsmoothsigma = 7 + # find orientation of every pixel + orientim = ridge_orient(normim, gradientsigma, + blocksigma, orientsmoothsigma) + + blksze = 38 + windsze = 5 + minWaveLength = 5 + maxWaveLength = 15 + # find the overall frequency of ridges + freq, medfreq = ridge_freq( + normim, mask, orientim, blksze, windsze, minWaveLength, maxWaveLength) + + freq = medfreq * mask + kx = 0.65 + ky = 0.65 + # create gabor filter and do the actual filtering + newim = ridge_filter(normim, orientim, freq, kx, ky) - blksze = 38; - windsze = 5; - minWaveLength = 5; - maxWaveLength = 15; - freq,medfreq = ridge_freq(normim, mask, orientim, blksze, windsze, minWaveLength,maxWaveLength); # find the overall frequency of ridges - - freq = medfreq*mask; - kx = 0.65;ky = 0.65; - newim = ridge_filter(normim, orientim, freq, kx, ky); # create gabor filter and do the actual filtering - return(newim < -3) diff --git a/src/main_enhancement.py b/src/main_enhancement.py deleted file mode 100644 index 090872e..0000000 --- a/src/main_enhancement.py +++ /dev/null @@ -1,37 +0,0 @@ -# -*- coding: utf-8 -*- - -import numpy as np -import matplotlib.pylab as plt; -import scipy.ndimage -import sys -import imageio -import matplotlib - -from image_enhance import image_enhance - -if(len(sys.argv)<2): - print('loading sample image'); - img_name = '1.jpg' - img = scipy.ndimage.imread('../images/' + img_name); -elif(len(sys.argv) >= 2): - img_name = sys.argv[1]; - img = scipy.ndimage.imread(sys.argv[1]); - -if(len(img.shape)>2): - img = np.dot(img[...,:3], [0.299, 0.587, 0.114]); - -rows,cols = np.shape(img); -aspect_ratio = np.double(rows)/np.double(cols); - -new_rows = 350; #randomly selected number -new_cols = new_rows/aspect_ratio; - -img = scipy.misc.imresize(img,(np.int(new_rows),np.int(new_cols))); - -enhanced_img = image_enhance(img); - -if(1): - print('saving the image') - matplotlib.image.imsave('../enhanced/' + 'enhanced.jpg', enhanced_img) -else: - plt.imshow(enhanced_img,cmap = 'Greys_r'); diff --git a/src/ridge_freq_2.py b/src/ridge_freq_2.py deleted file mode 100644 index bbbebd5..0000000 --- a/src/ridge_freq_2.py +++ /dev/null @@ -1,29 +0,0 @@ -# -*- coding: utf-8 -*- - -import numpy as np -from frequest import frequest - -def rifdge_freq(im, mask, orient, blksze, windsze,minWaveLength, maxWaveLength): - rows,cols = im.shape; - freq = np.zeros((rows,cols)); - - for r in range(0,rows-blksze,blksze): - for c in range(0,cols-blksze,blksze): - blkim = im[r:r+blksze][:,c:c+blksze]; - blkor = orient[r:r+blksze][:,c:c+blksze]; - - - freq[r:r+blksze][:,c:c+blksze] = frequest(blkim,blkor,windsze,minWaveLength,maxWaveLength); - - freq = freq*mask; - freq_1d = np.reshape(freq,(1,rows*cols)); - ind = np.where(freq_1d>0); - - ind = np.array(ind); - ind = ind[1,:]; - - non_zero_elems_in_freq = freq_1d[0][ind]; - - medianfreq = np.median(non_zero_elems_in_freq); - - return(medianfreq) diff --git a/src/utils/__init__.py b/src/utils/__init__.py new file mode 100644 index 0000000..db492b5 --- /dev/null +++ b/src/utils/__init__.py @@ -0,0 +1,4 @@ +from .ridge_segment import ridge_segment +from .ridge_orient import ridge_orient +from .ridge_freq import ridge_freq +from .ridge_filter import ridge_filter \ No newline at end of file diff --git a/src/frequest.py b/src/utils/frequest.py similarity index 100% rename from src/frequest.py rename to src/utils/frequest.py diff --git a/src/ridge_filter.py b/src/utils/ridge_filter.py similarity index 50% rename from src/ridge_filter.py rename to src/utils/ridge_filter.py index 27cc017..9749294 100644 --- a/src/ridge_filter.py +++ b/src/utils/ridge_filter.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - # RIDGEFILTER - enhances fingerprint image via oriented filters # # Function to enhance fingerprint image via oriented filters @@ -22,114 +20,127 @@ # 0.5 for both kx and ky is a good starting point. # showfilter - An optional flag 0/1. When set an image of the # largest scale filter is displayed for inspection. -# +# # Returns: # newim - The enhanced image # # See also: RIDGEORIENT, RIDGEFREQ, RIDGESEGMENT -# Reference: +# Reference: # Hong, L., Wan, Y., and Jain, A. K. Fingerprint image enhancement: # Algorithm and performance evaluation. IEEE Transactions on Pattern # Analysis and Machine Intelligence 20, 8 (1998), 777 789. -### REFERENCES +# REFERENCES -# Peter Kovesi +# Peter Kovesi # School of Computer Science & Software Engineering # The University of Western Australia # pk at csse uwa edu au # http://www.csse.uwa.edu.au/~pk - import numpy as np -import scipy; -def ridge_filter(im, orient, freq, kx, ky): - angleInc = 3; - im = np.double(im); - rows,cols = im.shape; - newim = np.zeros((rows,cols)); - - freq_1d = np.reshape(freq,(1,rows*cols)); - ind = np.where(freq_1d>0); - - ind = np.array(ind); - ind = ind[1,:]; - +from numpy import ndarray as NumpyArray +import scipy + + +def ridge_filter(im: NumpyArray, orient: NumpyArray, freq: NumpyArray, + kx: float, ky: float): + + angleInc = 3 + im = np.double(im) + rows, cols = im.shape + newim = np.zeros((rows, cols)) + + freq_1d = np.reshape(freq, (1, rows * cols)) + ind = np.where(freq_1d > 0) + + ind = np.array(ind) + ind = ind[1, :] + # Round the array of frequencies to the nearest 0.01 to reduce the - # number of distinct frequencies we have to deal with. - - non_zero_elems_in_freq = freq_1d[0][ind]; - non_zero_elems_in_freq = np.double(np.round((non_zero_elems_in_freq*100)))/100; - - unfreq = np.unique(non_zero_elems_in_freq); + # number of distinct frequencies we have to deal with. + + non_zero_elems_in_freq = freq_1d[0][ind] + non_zero_elems_in_freq = np.double( + np.round((non_zero_elems_in_freq * 100))) / 100 + + unfreq = np.unique(non_zero_elems_in_freq) # Generate filters corresponding to these distinct frequencies and # orientations in 'angleInc' increments. - - sigmax = 1/unfreq[0]*kx; - sigmay = 1/unfreq[0]*ky; - - sze = np.round(3*np.max([sigmax,sigmay])); - - x,y = np.meshgrid(np.linspace(-sze,sze,(2*sze + 1)),np.linspace(-sze,sze,(2*sze + 1))); - - reffilter = np.exp(-(( (np.power(x,2))/(sigmax*sigmax) + (np.power(y,2))/(sigmay*sigmay)))) * np.cos(2*np.pi*unfreq[0]*x); # this is the original gabor filter - - filt_rows, filt_cols = reffilter.shape; - - gabor_filter = np.array(np.zeros((180/angleInc,filt_rows,filt_cols))); - - for o in range(0,180/angleInc): - + + sigmax = 1/unfreq[0]*kx + sigmay = 1/unfreq[0]*ky + + sze = np.round(3 * np.max([sigmax, sigmay])) + + x, y = np.meshgrid(np.linspace(-sze, sze, int(2 * sze + 1)), + np.linspace(-sze, sze, int(2 * sze + 1))) + + reffilter = np.exp( + -(np.power(x, 2) / (sigmax * sigmax) + +(np.power(y, 2)) / (sigmay * sigmay)) + ) * np.cos(2*np.pi * unfreq[0] * x) # this is the original gabor filter + + filt_rows, filt_cols = reffilter.shape + + angleFin = int(180 / angleInc) + + gabor_filter = np.array(np.zeros((angleFin, filt_rows, filt_cols))) + + for o in range(0, angleFin): + # Generate rotated versions of the filter. Note orientation # image provides orientation *along* the ridges, hence +90 # degrees, and imrotate requires angles +ve anticlockwise, hence - # the minus sign. - - rot_filt = scipy.ndimage.rotate(reffilter,-(o*angleInc + 90),reshape = False); - gabor_filter[o] = rot_filt; - + # the minus sign. + + rot_filt = scipy.ndimage.rotate( + reffilter, -(o * angleInc + 90), reshape=False) + gabor_filter[o] = rot_filt + # Find indices of matrix points greater than maxsze from the image # boundary - - maxsze = int(sze); - - temp = freq>0; - validr,validc = np.where(temp) - - temp1 = validr>maxsze; - temp2 = validrmaxsze; - temp4 = validc 0 + validr, validc = np.where(temp) + + temp1 = validr > maxsze + temp2 = validr < rows - maxsze + temp3 = validc > maxsze + temp4 = validc < cols - maxsze + + final_temp = temp1 & temp2 & temp3 & temp4 + + finalind = np.where(final_temp) + # Convert orientation matrix values from radians to an index value - # that corresponds to round(degrees/angleInc) - - maxorientindex = np.round(180/angleInc); - orientindex = np.round(orient/np.pi*180/angleInc); - - #do the filtering - - for i in range(0,rows): - for j in range(0,cols): + # that corresponds to round(degrees/angleInc) + + maxorientindex = np.round(180 / angleInc) + orientindex = np.round(orient / np.pi * 180 / angleInc) + + # do the filtering + + for i in range(0, rows): + for j in range(0, cols): if(orientindex[i][j] < 1): - orientindex[i][j] = orientindex[i][j] + maxorientindex; + orientindex[i][j] = orientindex[i][j] + maxorientindex if(orientindex[i][j] > maxorientindex): - orientindex[i][j] = orientindex[i][j] - maxorientindex; - finalind_rows,finalind_cols = np.shape(finalind); - sze = int(sze); - for k in range(0,finalind_cols): - r = validr[finalind[0][k]]; - c = validc[finalind[0][k]]; - - img_block = im[r-sze:r+sze + 1][:,c-sze:c+sze + 1]; - - newim[r][c] = np.sum(img_block * gabor_filter[int(orientindex[r][c]) - 1]); - - return(newim); + orientindex[i][j] = orientindex[i][j] - maxorientindex + finalind_rows, finalind_cols = np.shape(finalind) + sze = int(sze) + for k in range(0, finalind_cols): + r = validr[finalind[0][k]] + c = validc[finalind[0][k]] + + img_block = im[r - sze:r + sze + 1][:, c - sze:c + sze + 1] + + newim[r][c] = np.sum( + img_block * gabor_filter[int(orientindex[r][c]) - 1]) + + return newim diff --git a/src/ridge_freq.py b/src/utils/ridge_freq.py similarity index 63% rename from src/ridge_freq.py rename to src/utils/ridge_freq.py index 7521e0e..cbd4b25 100644 --- a/src/ridge_freq.py +++ b/src/utils/ridge_freq.py @@ -14,12 +14,12 @@ # im - Image to be processed. # mask - Mask defining ridge regions (obtained from RIDGESEGMENT) # orientim - Ridge orientation image (obtained from RIDGORIENT) -# blksze - Size of image block to use (say 32) +# blksze - Size of image block to use (say 32) # windsze - Window length used to identify peaks. This should be # an odd integer, say 3 or 5. # minWaveLength, maxWaveLength - Minimum and maximum ridge # wavelengths, in pixels, considered acceptable. -# +# # Returns: # freqim - An image the same size as im with values set to # the estimated ridge spatial frequency within each @@ -36,44 +36,52 @@ # See also: RIDGEORIENT, FREQEST, RIDGESEGMENT -# Reference: +# Reference: # Hong, L., Wan, Y., and Jain, A. K. Fingerprint image enhancement: # Algorithm and performance evaluation. IEEE Transactions on Pattern # Analysis and Machine Intelligence 20, 8 (1998), 777 789. -### REFERENCES +# REFERENCES -# Peter Kovesi +# Peter Kovesi # School of Computer Science & Software Engineering # The University of Western Australia # pk at csse uwa edu au # http://www.csse.uwa.edu.au/~pk - +from typing import Tuple import numpy as np -from frequest import frequest +from numpy import ndarray as NumpyArray -def ridge_freq(im, mask, orient, blksze, windsze,minWaveLength, maxWaveLength): - rows,cols = im.shape; - freq = np.zeros((rows,cols)); - - for r in range(0,rows-blksze,blksze): - for c in range(0,cols-blksze,blksze): - blkim = im[r:r+blksze][:,c:c+blksze]; - blkor = orient[r:r+blksze][:,c:c+blksze]; - - - freq[r:r+blksze][:,c:c+blksze] = frequest(blkim,blkor,windsze,minWaveLength,maxWaveLength); - - freq = freq*mask; - freq_1d = np.reshape(freq,(1,rows*cols)); - ind = np.where(freq_1d>0); - - ind = np.array(ind); - ind = ind[1,:]; +from .frequest import frequest + + +def ridge_freq(im: NumpyArray, mask: NumpyArray, orient: NumpyArray, + blksze: int, windsze: int, minWaveLength: int, + maxWaveLength: int) -> Tuple[NumpyArray, float]: + + rows, cols = im.shape + freq = np.zeros((rows, cols)) + + for r in range(0, rows-blksze, blksze): + for c in range(0, cols-blksze, blksze): + blkim = im[r:r+blksze][:, c:c+blksze] + blkor = orient[r:r+blksze][:, c:c+blksze] + + freq[r:r+blksze][:, c:c + + blksze] = frequest(blkim, blkor, windsze, minWaveLength, maxWaveLength) + + freq: NumpyArray = freq*mask - non_zero_elems_in_freq = freq_1d[0][ind]; + freq_1d = np.reshape(freq, (1, rows*cols)) + ind = np.where(freq_1d > 0) + + ind = np.array(ind) + ind = ind[1, :] + + non_zero_elems_in_freq = freq_1d[0][ind] + + meanfreq: float = np.mean(non_zero_elems_in_freq) + medianfreq = np.median(non_zero_elems_in_freq) - meanfreq = np.mean(non_zero_elems_in_freq); - medianfreq = np.median(non_zero_elems_in_freq); - return(freq,meanfreq) + return (freq, meanfreq) diff --git a/src/utils/ridge_freq_2.py b/src/utils/ridge_freq_2.py new file mode 100644 index 0000000..ef12f91 --- /dev/null +++ b/src/utils/ridge_freq_2.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- + +import numpy as np +from .frequest import frequest + +def rifdge_freq(im, mask, orient, blksze, windsze,minWaveLength, maxWaveLength): + rows,cols = im.shape + freq = np.zeros((rows,cols)) + + for r in range(0,rows-blksze,blksze): + for c in range(0,cols-blksze,blksze): + blkim = im[r:r+blksze][:,c:c+blksze] + blkor = orient[r:r+blksze][:,c:c+blksze] + + + freq[r:r+blksze][:,c:c+blksze] = frequest(blkim,blkor,windsze,minWaveLength,maxWaveLength) + + freq = freq * mask + freq_1d = np.reshape(freq,(1,rows * cols)) + ind = np.where(freq_1d>0) + + ind = np.array(ind) + ind = ind[1,:] + + non_zero_elems_in_freq = freq_1d[0][ind] + + medianfreq = np.median(non_zero_elems_in_freq) + + return(medianfreq) diff --git a/src/ridge_orient.py b/src/utils/ridge_orient.py similarity index 53% rename from src/ridge_orient.py rename to src/utils/ridge_orient.py index 0e7e73f..709cf43 100644 --- a/src/ridge_orient.py +++ b/src/utils/ridge_orient.py @@ -12,9 +12,9 @@ # blocksigma - Sigma of the Gaussian weighting used to # sum the gradient moments. # orientsmoothsigma - Sigma of the Gaussian used to smooth -# the final orientation vector field. +# the final orientation vector field. # Optional: if ommitted it defaults to 0 -# +# # Returns: orientim - The orientation image in radians. # Orientation values are +ve clockwise # and give the direction *along* the @@ -23,22 +23,22 @@ # orientation measure. This is a value # between 0 and 1. I think a value above # about 0.5 can be considered 'reliable'. -# reliability = 1 - Imin./(Imax+.001); +# reliability = 1 - Imin./(Imax+.001) # coherence - A measure of the degree to which the local # area is oriented. -# coherence = ((Imax-Imin)./(Imax+Imin)).^2; +# coherence = ((Imax-Imin)./(Imax+Imin)).^2 # # With a fingerprint image at a 'standard' resolution of 500dpi suggested # parameter values might be: # -# [orientim, reliability] = ridgeorient(im, 1, 3, 3); +# [orientim, reliability] = ridgeorient(im, 1, 3, 3) # # See also: RIDGESEGMENT, RIDGEFREQ, RIDGEFILTER -### REFERENCES +# REFERENCES -# May 2003 Original version by Raymond Thai, -# January 2005 Reworked by Peter Kovesi +# May 2003 Original version by Raymond Thai, +# January 2005 Reworked by Peter Kovesi # October 2011 Added coherence computation and orientsmoothsigma made optional # # School of Computer Science & Software Engineering @@ -47,55 +47,60 @@ # http://www.csse.uwa.edu.au/~pk -import numpy as np; -import cv2; -from scipy import ndimage; +import numpy as np +from numpy import ndarray as NumpyArray +import cv2 +from scipy import ndimage from scipy import signal -def ridge_orient(im, gradientsigma, blocksigma, orientsmoothsigma): - - rows,cols = im.shape; - sze = np.fix(6*gradientsigma); - if np.remainder(sze,2) == 0: - sze = sze+1; - - gauss = cv2.getGaussianKernel(np.int(sze),gradientsigma); - f = gauss * gauss.T; - - fy,fx = np.gradient(f); #Gradient of Gaussian - - Gx = signal.convolve2d(im,fx,mode='same'); - Gy = signal.convolve2d(im,fy,mode='same'); - - Gxx = np.power(Gx,2); - Gyy = np.power(Gy,2); - Gxy = Gx*Gy; - - #Now smooth the covariance data to perform a weighted summation of the data. - - sze = np.fix(6*blocksigma); - - gauss = cv2.getGaussianKernel(np.int(sze),blocksigma); - f = gauss * gauss.T; - - Gxx = ndimage.convolve(Gxx,f); - Gyy = ndimage.convolve(Gyy,f); - Gxy = 2*ndimage.convolve(Gxy,f); - - denom = np.sqrt(np.power(Gxy,2) + np.power((Gxx - Gyy),2)) + np.finfo(float).eps; - - sin2theta = Gxy/denom; # Sine and cosine of doubled angles - cos2theta = (Gxx-Gyy)/denom; - - + +def ridge_orient(im: NumpyArray, gradientsigma: int, blocksigma: int, + orientsmoothsigma: int) -> NumpyArray: + + rows, cols = im.shape + sze = np.fix(6*gradientsigma) + if np.remainder(sze, 2) == 0: + sze = sze+1 + + gauss = cv2.getGaussianKernel(np.int(sze), gradientsigma) + f = gauss * gauss.T + + fy, fx = np.gradient(f) # Gradient of Gaussian + + Gx = signal.convolve2d(im, fx, mode='same') + Gy = signal.convolve2d(im, fy, mode='same') + + Gxx = np.power(Gx, 2) + Gyy = np.power(Gy, 2) + Gxy = Gx*Gy + + # Now smooth the covariance data to perform a weighted summation of the data. + + sze = np.fix(6*blocksigma) + + gauss = cv2.getGaussianKernel(np.int(sze), blocksigma) + f = gauss * gauss.T + + Gxx = ndimage.convolve(Gxx, f) + Gyy = ndimage.convolve(Gyy, f) + Gxy = 2*ndimage.convolve(Gxy, f) + + denom = np.sqrt(np.power(Gxy, 2) + np.power((Gxx - Gyy), 2) + ) + np.finfo(float).eps + + sin2theta = Gxy/denom # Sine and cosine of doubled angles + cos2theta = (Gxx-Gyy)/denom + if orientsmoothsigma: - sze = np.fix(6*orientsmoothsigma); - if np.remainder(sze,2) == 0: - sze = sze+1; - gauss = cv2.getGaussianKernel(np.int(sze),orientsmoothsigma); - f = gauss * gauss.T; - cos2theta = ndimage.convolve(cos2theta,f); # Smoothed sine and cosine of - sin2theta = ndimage.convolve(sin2theta,f); # doubled angles - - orientim = np.pi/2 + np.arctan2(sin2theta,cos2theta)/2; - return(orientim); + sze = np.fix(6*orientsmoothsigma) + if np.remainder(sze, 2) == 0: + sze = sze + 1 + gauss = cv2.getGaussianKernel(np.int(sze), orientsmoothsigma) + f = gauss * gauss.T + # Smoothed sine and cosine of + cos2theta = ndimage.convolve(cos2theta, f) + sin2theta = ndimage.convolve(sin2theta, f) # doubled angles + + orientim: NumpyArray = np.pi / 2 + np.arctan2(sin2theta, cos2theta) / 2 + + return orientim \ No newline at end of file diff --git a/src/ridge_segment.py b/src/utils/ridge_segment.py similarity index 59% rename from src/ridge_segment.py rename to src/utils/ridge_segment.py index a7f2cf4..e89eb4c 100644 --- a/src/ridge_segment.py +++ b/src/utils/ridge_segment.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - # RIDGESEGMENT - Normalises fingerprint image and segments ridge region # # Function identifies ridge regions of a fingerprint image and returns a @@ -24,9 +22,9 @@ # # Returns: normim - Image where the ridge regions are renormalised to # have zero mean, unit standard deviation. -# mask - Mask indicating ridge-like regions of the image, +# mask - Mask indicating ridge-like regions of the image, # 0 for non ridge regions, 1 for ridge regions. -# maskind - Vector of indices of locations within the mask. +# maskind - Vector of indices of locations within the mask. # # Suggested values for a 500dpi fingerprint image: # @@ -34,50 +32,54 @@ # # See also: RIDGEORIENT, RIDGEFREQ, RIDGEFILTER -### REFERENCES +# REFERENCES -# Peter Kovesi +# Peter Kovesi # School of Computer Science & Software Engineering # The University of Western Australia # pk at csse uwa edu au # http://www.csse.uwa.edu.au/~pk +from typing import Tuple - +from numpy import ndarray as NumpyArray # typing import numpy as np -def normalise(img,mean,std): - normed = (img - np.mean(img))/(np.std(img)); - return(normed) - -def ridge_segment(im,blksze,thresh): - - rows,cols = im.shape; - - im = normalise(im,0,1); # normalise to get zero mean and unit standard deviation - - - new_rows = np.int(blksze * np.ceil((np.float(rows))/(np.float(blksze)))) - new_cols = np.int(blksze * np.ceil((np.float(cols))/(np.float(blksze)))) - - padded_img = np.zeros((new_rows,new_cols)); - stddevim = np.zeros((new_rows,new_cols)); - - padded_img[0:rows][:,0:cols] = im; - - for i in range(0,new_rows,blksze): - for j in range(0,new_cols,blksze): - block = padded_img[i:i+blksze][:,j:j+blksze]; - - stddevim[i:i+blksze][:,j:j+blksze] = np.std(block)*np.ones(block.shape) - - stddevim = stddevim[0:rows][:,0:cols] - - mask = stddevim > thresh; - - mean_val = np.mean(im[mask]); - - std_val = np.std(im[mask]); - - normim = (im - mean_val)/(std_val); - - return(normim,mask) + +def normalise(img: NumpyArray, mean, std): + normed = (img - np.mean(img)) / (np.std(img)) + return normed + + +def ridge_segment(im: NumpyArray, blksze: int, thresh: float) -> Tuple[NumpyArray]: + + rows, cols = im.shape + + # normalise to get zero mean and unit standard deviation + im = normalise(im, 0, 1) + + new_rows = np.int(blksze * np.ceil((np.float(rows)) / (np.float(blksze)))) + new_cols = np.int(blksze * np.ceil((np.float(cols)) / (np.float(blksze)))) + + padded_img = np.zeros((new_rows, new_cols)) + stddevim = np.zeros((new_rows, new_cols)) + + padded_img[0:rows][:, 0:cols] = im + + for i in range(0, new_rows, blksze): + for j in range(0, new_cols, blksze): + block = padded_img[i:i+blksze][:, j:j+blksze] + + stddevim[i:i+blksze][:, j:j + + blksze] = np.std(block)*np.ones(block.shape) + + stddevim = stddevim[0:rows][:, 0:cols] + + mask: NumpyArray = stddevim > thresh + + mean_val = np.mean(im[mask]) + + std_val = np.std(im[mask]) + + normim: NumpyArray = (im - mean_val) / std_val + + return normim, mask