听百家之言,集百家智慧,站在巨人肩上攀登


我的心太乱,有一些空白
我的心太乱,不敢再贪更多爱。
刚刚过了论文答辩,现在接着整理指纹识别的代码,Raffaele教授优雅的代码折服了我。



文章目录

  • 指纹的分割
  • 预估局部脊线的方向
  • 参考文献和学习资料


指纹的分割

首先加载一个指纹图片看看,使用numpy去存储:

fingerprint = cv.imread('samples/sample_1_1.png', cv.IMREAD_GRAYSCALE)
show(fingerprint, f'Fingerprint with size (w,h): {fingerprint.shape[::-1]}')

指纹识别 Python 库 指纹识别源代码_像素点


分割的目标主要是将指纹(前景)从背景中分割出来。图片的前景通过观察可知是由条状或者圆形的一些组成,而背景就只是一个均匀的底色而已。我们使用非常简单的手段,基于局部梯度就可以很容易实现我们的目标。那么使用sobel滤波是一个非常好的选择。Sobel 算子是一个离散微分算子 (discrete differentiation operator)。 它结合了高斯平滑和微分求导,用来计算图像灰度函数的近似梯度。在图像边缘,相素值会发生显著的变化了。表示这一改变的一个方法是使用 导数 。 梯度值的大变预示着图像中内容的显著变化。用更加形象的图像来解释,假设我们有一张一维图形。下图1中灰度值的”跃升”表示边缘的存在,图2中使用一阶微分求导我们可以更加清晰的看到边缘”跃升”的存在。

指纹识别 Python 库 指纹识别源代码_像素点_02


图1 灰度值的”跃升”表示边缘的存在

指纹识别 Python 库 指纹识别源代码_灰度_03


图2 一阶微分求导:边缘”跃升”

具体的计算算法为:

指纹识别 Python 库 指纹识别源代码_指纹识别 Python 库_04


指纹识别 Python 库 指纹识别源代码_指纹识别 Python 库_05


Sobel算子根据像素点上下、左右邻点灰度加权差,在边缘处达到极值这一现象检测边缘。对噪声具有平滑作用,提供较为精确的边缘方向信息,边缘定位精度不够高。当对精度要求不是很高时,是一种较为常用的边缘检测方法。

我们使用sobel在指纹上:

# Calculate the local gradient (using Sobel filters)
gx, gy = cv.Sobel(fingerprint, cv.CV_32F, 1, 0), cv.Sobel(fingerprint, cv.CV_32F, 0, 1)
show((gx, 'Gx'), (gy, 'Gy'))

指纹识别 Python 库 指纹识别源代码_像素点_06

可以看到两个方向的梯度在指纹的条纹上展现了很明显的特征,横向Gx可以看到左右部分明显的轮廓。纵向Gy可以看到上下部分明显的轮廓。我们可以计算一下梯度的平方来看一下效果,平方就不管是正负了。

# Calculate the magnitude of the gradient for each pixel
gx2, gy2 = gx**2, gy**2
gm = np.sqrt(gx2 + gy2)
show((gx2, 'Gx**2'), (gy2, 'Gy**2'), (gm, 'Gradient magnitude'))

指纹识别 Python 库 指纹识别源代码_像素点_07


这里,gm是两个相加的结果。可以看到相加之后,主要的纹理特征已经可以很明显的提取出来了。Perfect!

接下来,使用一个积分模块,在一个方形区域进行积分:

# Integral over a square window
sum_gm = cv.boxFilter(gm, -1, (25, 25), normalize = False)
show(sum_gm, 'Integral of the gradient magnitude')

指纹识别 Python 库 指纹识别源代码_像素点_08

可以看到大部分指纹区域都变白色,而没有指纹区域还是黑色。我们可以使用一个简单的阈值来把背景和前景区分开:

# Use a simple threshold for segmenting the fingerprint pattern
thr = sum_gm.max() * 0.2
mask = cv.threshold(sum_gm, thr, 255, cv.THRESH_BINARY)[1].astype(np.uint8)
show(fingerprint, mask, cv.merge((mask, fingerprint, fingerprint)))

指纹识别 Python 库 指纹识别源代码_灰度_09


左边是原始的指纹图像,中间是mask,右边则是将指纹和mask合并之后得到的图像。

预估局部脊线的方向

位置(x,y)的局部脊线的方向在【0-180】之间,是脊线和水平轴形成的。对于每一个像素点,我们计算他的梯度Gx,Gy,也就是上面我们计算的。那么某一个像素点的方向就可以计算为这一点的梯度方向的正交方向,这个角度在一个预定的窗口中进行平均。

指纹识别 Python 库 指纹识别源代码_像素点_10, 指纹识别 Python 库 指纹识别源代码_指纹识别 Python 库_11, 指纹识别 Python 库 指纹识别源代码_像素点_12

指纹识别 Python 库 指纹识别源代码_OpenCV_13

对于每一个方向,我们也会去计算他的置信度,也就是计算在W 中有多少梯度有同样的方向,自然数量越多,计算的结果越可靠:
指纹识别 Python 库 指纹识别源代码_OpenCV_14

phase函数根据函数:

指纹识别 Python 库 指纹识别源代码_指纹识别 Python 库_15


来计算角度,计算精度大约为0.3弧度,当x,y相等时,angle为0。

数学上函数atan2为:

指纹识别 Python 库 指纹识别源代码_灰度_16

该函数的值域为(-pi,pi],可以通过对负数结果加2pi的方法,将函数的结果映射到[0,2pi)范围内。

参考:

  • OpenCV中phase函数计算方向场(此博客包含opencv很多教程,推荐)
  • 盒式滤波器Box Filter
W = (23, 23)#我们定义一个23x23的窗口
gxx = cv.boxFilter(gx2, -1, W, normalize = False)# 在给定的滑动窗口大小下,对每个窗口内的像素值进行快速相加求和
gyy = cv.boxFilter(gy2, -1, W, normalize = False)
gxy = cv.boxFilter(gx * gy, -1, W, normalize = False) # gx * gy 
gxx_gyy = gxx - gyy
gxy2 = 2 * gxy

orientations = (cv.phase(gxx_gyy, -gxy2) + np.pi) / 2 # '-' to adjust for y axis direction phase函数计算方向场
sum_gxx_gyy = gxx + gyy
strengths = np.divide(cv.sqrt((gxx_gyy**2 + gxy2**2)), sum_gxx_gyy, out=np.zeros_like(gxx), where=sum_gxx_gyy!=0)#  计算置信度,也就是计算在W 中有多少梯度有同样的方向,自然数量越多,计算的结果越可靠
show(draw_orientations(fingerprint, orientations, strengths, mask, 1, 16), 'Orientation image')

指纹识别 Python 库 指纹识别源代码_指纹识别 Python 库_17


可以看到脊线方向被正确识别出来。

Charles@Shenzhen

参考文献和学习资料

分割:
local gradient (Sobel filters)

[1] Mehtre, Babu M., and B. Chatterjee. “Segmentation of fingerprint images—a composite method.” Pattern recognition 22.4 (1989): 381-385.

[2] OpenCV: Sobel Filter

[3] https://docs.opencv.org/3.4/d2/d2c/tutorial_sobel_derivatives.html

local ridge orientation

[4] A.M. Bazen and S.H. Gerez, “Systematic methods for the computation of the directional fields and singular points of fingerprints,” in IEEE tPAMI, July 2002