Spaces:
Sleeping
Sleeping
| import numpy as np | |
| import cv2 | |
| from scipy import signal | |
| from scipy import ndimage | |
| import math | |
| import scipy | |
| class FingerprintImageEnhancer(object): | |
| def __init__(self): | |
| self.ridge_segment_blksze = 32 | |
| self.ridge_segment_thresh = 0.1 | |
| self.gradient_sigma = 1 | |
| self.block_sigma = 7 | |
| self.orient_smooth_sigma = 7 | |
| self.ridge_freq_blksze = 38 | |
| self.ridge_freq_windsze = 15 #width change (thick -> 15) (default -> 5) | |
| self.min_wave_length = 7 | |
| self.max_wave_length = 15 | |
| self.kx = 0.65 | |
| self.ky = 0.65 | |
| self.angleInc = 3 | |
| self.ridge_filter_thresh = -3 | |
| self._mask = [] | |
| self._normim = [] | |
| self._orientim = [] | |
| self._mean_freq = [] | |
| self._median_freq = [] | |
| self._freq = [] | |
| self._freqim = [] | |
| self._binim = [] | |
| def __normalise(self, img, mean, std): | |
| normed = (img - np.mean(img)) / (np.std(img)) | |
| return (normed) | |
| def __ridge_segment(self, img): | |
| # RIDGESEGMENT - Normalises fingerprint image and segments ridge region | |
| # | |
| # Function identifies ridge regions of a fingerprint image and returns a | |
| # mask identifying this region. It also normalises the intesity values of | |
| # the image so that the ridge regions have zero mean, unit standard | |
| # deviation. | |
| # | |
| # This function breaks the image up into blocks of size blksze x blksze and | |
| # evaluates the standard deviation in each region. If the standard | |
| # deviation is above the threshold it is deemed part of the fingerprint. | |
| # Note that the image is normalised to have zero mean, unit standard | |
| # deviation prior to performing this process so that the threshold you | |
| # specify is relative to a unit standard deviation. | |
| # | |
| # Usage: [normim, mask, maskind] = ridgesegment(im, blksze, thresh) | |
| # | |
| # Arguments: im - Fingerprint image to be segmented. | |
| # blksze - Block size over which the the standard | |
| # deviation is determined (try a value of 16). | |
| # thresh - Threshold of standard deviation to decide if a | |
| # block is a ridge region (Try a value 0.1 - 0.2) | |
| # | |
| # Ouput: normim - Image where the ridge regions are renormalised to | |
| # have zero mean, unit standard deviation. | |
| # 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. | |
| # | |
| # Suggested values for a 500dpi fingerprint image: | |
| # | |
| # [normim, mask, maskind] = ridgesegment(im, 16, 0.1) | |
| # | |
| # See also: RIDGEORIENT, RIDGEFREQ, RIDGEFILTER | |
| ### REFERENCES | |
| # 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 | |
| rows, cols = img.shape | |
| im = self.__normalise(img, 0, 1) # normalise to get zero mean and unit standard deviation | |
| new_rows = int(self.ridge_segment_blksze * np.ceil((float(rows)) / (float(self.ridge_segment_blksze)))) | |
| new_cols = int(self.ridge_segment_blksze * np.ceil((float(cols)) / (float(self.ridge_segment_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, self.ridge_segment_blksze): | |
| for j in range(0, new_cols, self.ridge_segment_blksze): | |
| block = padded_img[i:i + self.ridge_segment_blksze][:, j:j + self.ridge_segment_blksze] | |
| stddevim[i:i + self.ridge_segment_blksze][:, j:j + self.ridge_segment_blksze] = np.std(block) * np.ones(block.shape) | |
| stddevim = stddevim[0:rows][:, 0:cols] | |
| self._mask = stddevim > self.ridge_segment_thresh | |
| mean_val = np.mean(im[self._mask]) | |
| std_val = np.std(im[self._mask]) | |
| self._normim = (im - mean_val) / (std_val) | |
| def __ridge_orient(self): | |
| # RIDGEORIENT - Estimates the local orientation of ridges in a fingerprint | |
| # | |
| # Usage: [orientim, reliability, coherence] = ridgeorientation(im, gradientsigma,... | |
| # blocksigma, ... | |
| # orientsmoothsigma) | |
| # | |
| # Arguments: im - A normalised input image. | |
| # gradientsigma - Sigma of the derivative of Gaussian | |
| # used to compute image gradients. | |
| # 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. | |
| # Optional: if ommitted it defaults to 0 | |
| # | |
| # Output: orientim - The orientation image in radians. | |
| # Orientation values are +ve clockwise | |
| # and give the direction *along* the | |
| # ridges. | |
| # reliability - Measure of the reliability of the | |
| # 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); | |
| # coherence - A measure of the degree to which the local | |
| # area is oriented. | |
| # 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); | |
| # | |
| # See also: RIDGESEGMENT, RIDGEFREQ, RIDGEFILTER | |
| ### REFERENCES | |
| # 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 | |
| # The University of Western Australia | |
| # pk at csse uwa edu au | |
| # http://www.csse.uwa.edu.au/~pk | |
| rows,cols = self._normim.shape | |
| #Calculate image gradients. | |
| sze = np.fix(6*self.gradient_sigma) | |
| if np.remainder(sze,2) == 0: | |
| sze = sze+1 | |
| gauss = cv2.getGaussianKernel(int(sze),self.gradient_sigma) | |
| f = gauss * gauss.T | |
| fy,fx = np.gradient(f) #Gradient of Gaussian | |
| Gx = signal.convolve2d(self._normim, fx, mode='same') | |
| Gy = signal.convolve2d(self._normim, 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*self.block_sigma) | |
| gauss = cv2.getGaussianKernel(int(sze), self.block_sigma) | |
| f = gauss * gauss.T | |
| Gxx = ndimage.convolve(Gxx,f) | |
| Gyy = ndimage.convolve(Gyy,f) | |
| Gxy = 2*ndimage.convolve(Gxy,f) | |
| # Analytic solution of principal direction | |
| 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 self.orient_smooth_sigma: | |
| sze = np.fix(6*self.orient_smooth_sigma) | |
| if np.remainder(sze,2) == 0: | |
| sze = sze+1 | |
| gauss = cv2.getGaussianKernel(int(sze), self.orient_smooth_sigma) | |
| f = gauss * gauss.T | |
| cos2theta = ndimage.convolve(cos2theta,f) # Smoothed sine and cosine of | |
| sin2theta = ndimage.convolve(sin2theta,f) # doubled angles | |
| self._orientim = np.pi/2 + np.arctan2(sin2theta,cos2theta)/2 | |
| def __ridge_freq(self): | |
| # RIDGEFREQ - Calculates a ridge frequency image | |
| # | |
| # Function to estimate the fingerprint ridge frequency across a | |
| # fingerprint image. This is done by considering blocks of the image and | |
| # determining a ridgecount within each block by a call to FREQEST. | |
| # | |
| # Usage: | |
| # [freqim, medianfreq] = ridgefreq(im, mask, orientim, blksze, windsze, ... | |
| # minWaveLength, maxWaveLength) | |
| # | |
| # Arguments: | |
| # 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) | |
| # 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. | |
| # | |
| # Output: | |
| # freqim - An image the same size as im with values set to | |
| # the estimated ridge spatial frequency within each | |
| # image block. If a ridge frequency cannot be | |
| # found within a block, or cannot be found within the | |
| # limits set by min and max Wavlength freqim is set | |
| # to zeros within that block. | |
| # medianfreq - Median frequency value evaluated over all the | |
| # valid regions of the image. | |
| # | |
| # Suggested parameters for a 500dpi fingerprint image | |
| # [freqim, medianfreq] = ridgefreq(im,orientim, 32, 5, 5, 15); | |
| # | |
| # See also: RIDGEORIENT, FREQEST, RIDGESEGMENT | |
| # 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 | |
| # 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 | |
| rows, cols = self._normim.shape | |
| freq = np.zeros((rows, cols)) | |
| for r in range(0, rows - self.ridge_freq_blksze, self.ridge_freq_blksze): | |
| for c in range(0, cols - self.ridge_freq_blksze, self.ridge_freq_blksze): | |
| blkim = self._normim[r:r + self.ridge_freq_blksze][:, c:c + self.ridge_freq_blksze] | |
| blkor = self._orientim[r:r + self.ridge_freq_blksze][:, c:c + self.ridge_freq_blksze] | |
| freq[r:r + self.ridge_freq_blksze][:, c:c + self.ridge_freq_blksze] = self.__frequest(blkim, blkor) | |
| self._freq = freq * self._mask | |
| freq_1d = np.reshape(self._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] | |
| self._mean_freq = np.mean(non_zero_elems_in_freq) | |
| self._median_freq = np.median(non_zero_elems_in_freq) # does not work properly | |
| self._freq = self._mean_freq * self._mask | |
| def __frequest(self, blkim, blkor): | |
| # FREQEST - Estimate fingerprint ridge frequency within image block | |
| # | |
| # Function to estimate the fingerprint ridge frequency within a small block | |
| # of a fingerprint image. This function is used by RIDGEFREQ | |
| # | |
| # Usage: | |
| # freqim = freqest(im, orientim, windsze, minWaveLength, maxWaveLength) | |
| # | |
| # Arguments: | |
| # im - Image block to be processed. | |
| # orientim - Ridge orientation image of image block. | |
| # 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. | |
| # | |
| # Output: | |
| # freqim - An image block the same size as im with all values | |
| # set to the estimated ridge spatial frequency. If a | |
| # ridge frequency cannot be found, or cannot be found | |
| # within the limits set by min and max Wavlength | |
| # freqim is set to zeros. | |
| # | |
| # Suggested parameters for a 500dpi fingerprint image | |
| # freqim = freqest(im,orientim, 5, 5, 15); | |
| # | |
| # See also: RIDGEFREQ, RIDGEORIENT, RIDGESEGMENT | |
| ### REFERENCES | |
| # 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 | |
| rows, cols = np.shape(blkim) | |
| # Find mean orientation within the block. This is done by averaging the | |
| # sines and cosines of the doubled angles before reconstructing the | |
| # angle again. This avoids wraparound problems at the origin. | |
| cosorient = np.mean(np.cos(2 * blkor)) | |
| sinorient = np.mean(np.sin(2 * blkor)) | |
| orient = math.atan2(sinorient, cosorient) / 2 | |
| # Rotate the image block so that the ridges are vertical | |
| # ROT_mat = cv2.getRotationMatrix2D((cols/2,rows/2),orient/np.pi*180 + 90,1) | |
| # rotim = cv2.warpAffine(im,ROT_mat,(cols,rows)) | |
| rotim = scipy.ndimage.rotate(blkim, orient / np.pi * 180 + 90, axes=(1, 0), reshape=False, order=3, | |
| mode='nearest') | |
| # Now crop the image so that the rotated image does not contain any | |
| # invalid regions. This prevents the projection down the columns | |
| # from being mucked up. | |
| cropsze = int(np.fix(rows / np.sqrt(2))) | |
| offset = int(np.fix((rows - cropsze) / 2)) | |
| rotim = rotim[offset:offset + cropsze][:, offset:offset + cropsze] | |
| # Sum down the columns to get a projection of the grey values down | |
| # the ridges. | |
| proj = np.sum(rotim, axis=0) | |
| dilation = scipy.ndimage.grey_dilation(proj, self.ridge_freq_windsze, structure=np.ones(self.ridge_freq_windsze)) | |
| temp = np.abs(dilation - proj) | |
| peak_thresh = 2 | |
| maxpts = (temp < peak_thresh) & (proj > np.mean(proj)) | |
| maxind = np.where(maxpts) | |
| rows_maxind, cols_maxind = np.shape(maxind) | |
| # Determine the spatial frequency of the ridges by divinding the | |
| # distance between the 1st and last peaks by the (No of peaks-1). If no | |
| # peaks are detected, or the wavelength is outside the allowed bounds, | |
| # the frequency image is set to 0 | |
| if (cols_maxind < 2): | |
| return(np.zeros(blkim.shape)) | |
| else: | |
| NoOfPeaks = cols_maxind | |
| waveLength = (maxind[0][cols_maxind - 1] - maxind[0][0]) / (NoOfPeaks - 1) | |
| if waveLength >= self.min_wave_length and waveLength <= self.max_wave_length: | |
| return(1 / np.double(waveLength) * np.ones(blkim.shape)) | |
| else: | |
| return(np.zeros(blkim.shape)) | |
| def __ridge_filter(self): | |
| # RIDGEFILTER - enhances fingerprint image via oriented filters | |
| # | |
| # Function to enhance fingerprint image via oriented filters | |
| # | |
| # Usage: | |
| # newim = ridgefilter(im, orientim, freqim, kx, ky, showfilter) | |
| # | |
| # Arguments: | |
| # im - Image to be processed. | |
| # orientim - Ridge orientation image, obtained from RIDGEORIENT. | |
| # freqim - Ridge frequency image, obtained from RIDGEFREQ. | |
| # kx, ky - Scale factors specifying the filter sigma relative | |
| # to the wavelength of the filter. This is done so | |
| # that the shapes of the filters are invariant to the | |
| # scale. kx controls the sigma in the x direction | |
| # which is along the filter, and hence controls the | |
| # bandwidth of the filter. ky controls the sigma | |
| # across the filter and hence controls the | |
| # orientational selectivity of the filter. A value of | |
| # 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. | |
| # | |
| # Output: | |
| # newim - The enhanced image | |
| # | |
| # See also: RIDGEORIENT, RIDGEFREQ, RIDGESEGMENT | |
| # 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 | |
| # 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 | |
| im = np.double(self._normim) | |
| rows, cols = im.shape | |
| newim = np.zeros((rows, cols)) | |
| freq_1d = np.reshape(self._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) | |
| # Generate filters corresponding to these distinct frequencies and | |
| # orientations in 'angleInc' increments. | |
| sigmax = 1 / unfreq[0] * self.kx | |
| sigmay = 1 / unfreq[0] * self.ky | |
| sze = int(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 | |
| angleRange = int(180 / self.angleInc) | |
| gabor_filter = np.array(np.zeros((angleRange, filt_rows, filt_cols))) | |
| for o in range(0, angleRange): | |
| # 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 * self.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 = self._freq > 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 / self.angleInc) | |
| orientindex = np.round(self._orientim / np.pi * 180 / self.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 | |
| 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]) | |
| self._binim = newim < self.ridge_filter_thresh | |
| def save_enhanced_image(self, path): | |
| # saves the enhanced image at the specified path | |
| cv2.imwrite(path, 255 - (255 * self._binim)) | |
| # image = (255 * self._binim) | |
| # print(image) | |
| # print(255 - image) | |
| def enhance(self, img, resize=False): | |
| # main function to enhance the image. | |
| # calls all other subroutines | |
| if(resize): | |
| rows, cols = np.shape(img) | |
| aspect_ratio = np.double(rows) / np.double(cols) | |
| new_rows = 450 # randomly selected number | |
| new_cols = new_rows / aspect_ratio | |
| img = cv2.resize(img, (int(new_cols), int(new_rows))) | |
| self.__ridge_segment(img) # normalise the image and find a ROI | |
| self.__ridge_orient() # compute orientation image | |
| self.__ridge_freq() # compute major frequency of ridges | |
| self.__ridge_filter() # filter the image using oriented gabor filter | |
| return(self._binim) | |