掌纹识别 Online Palmprint Identification 论文代码复现

184 阅读6分钟

论文简介

论文数据是根据接触式掌纹图片进行掌纹识别。主要分为三大步骤:

  1. 预处理:从整个掌纹图片中获取感兴趣的区域ROI
  2. 特征提取:对ROI使用Gabor滤波器提取出特征图
  3. 对比:对特征进行逐帧对比得到汉明距离

掌纹图使用香港中文大学公开数据集

代码复现

预处理

根据论文需要获取手指指缝的两个谷点,并构建直角坐标系,截取ROI 该部分方法参考了 Opencv-Python提取掌纹图片ROI 基于图像相位及方向特征的掌纹识别的c++实现(二)

获取手掌轮廓

先二值化整个图像,找到所有轮廓中的最大面积部分,一般该部分为手掌

def binarize_image(img, blur_kernel_size=(15, 15), blur_sigma=2, threshold=20):
    """二值化图像"""
    # 使用高斯滤波进行平滑处理,去除噪声
    blur = cv2.GaussianBlur(img, blur_kernel_size, blur_sigma)
    # 使用阈值二值化图像
    _, thresh = cv2.threshold(blur, threshold, 255, cv2.THRESH_BINARY)
    return thresh

def extract_contour(img, thresh, output_path=None):
    """提取并填充最大轮廓"""
    # 寻找所有轮廓
    contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
    
    # 计算每个轮廓的面积,找到最大的
    areas = [cv2.contourArea(c) for c in contours]
    max_index = np.argmax(areas)
    max_cnt = contours[max_index]
    
    # 创建结果图
    contour_img = np.zeros(img.shape)
    # 填充轮廓
    cv2.fillPoly(contour_img, pts=[max_cnt], color=(255, 255, 255))
    
    # 保存结果
    if output_path:
        cv2.imwrite(output_path, contour_img)
    
    return contour_img, max_cnt

最后得到的图片如图所示

获取关键点

根据该手掌轮廓获取文中小拇指与无名指、食指和中指直接的谷点

def find_key_points(contour_img, img_height, img_width):
    """查找关键点"""
    # 初始化点
    point_in_top = [0, 0]
    point_out_bottom = [0, 0]
    
    # 从上到下查找第一个白点
    for row in range(img_height):
        for col in range(img_width):
            if contour_img[row][col] == 255.0:
                point_in_top = [col, row]
                break
        if point_in_top != [0, 0]:
            break
    
    # 从下到上查找第一个白点
    for row in range(img_height-1, -1, -1):
        for col in range(img_width):
            if contour_img[row][col] == 255.0:
                point_out_bottom = [col, row]
                break
        if point_out_bottom != [0, 0]:
            break
    
    # 查找中间的缝隙
    gap_x = 0
    for col in range(img_width):
        gap_width = 0
        for row in range(img_height):
            if contour_img[row][col] == 0:
                gap_width += 1
        if gap_width < 200:
            gap_x = col
            break
    
    # 查找上下点
    point_Out_top = (gap_x, 0)
    point_In_bottom = (gap_x, 0)
    center_y = img_height // 2
    
    for row in range(center_y, -1, -1):
        if contour_img[row][gap_x] == 255:
            point_Out_top = (gap_x, row)
            break
    
    for row in range(center_y, img_height):
        if contour_img[row][gap_x] == 255:
            point_In_bottom = (gap_x, row)
            break
    
    # 查找边缘点
    Top_x = Bottom_x = 0.0
    Top_y_vector = []
    Bottom_y_vector = []
    
    # 查找上边缘最右点
    for i in range(point_in_top[1], point_Out_top[1]+1):
        for j in range(img_width):
            if contour_img[i][j] == 255:
                if j > Top_x:
                    Top_x = j
                break
    
    # 查找下边缘最右点
    for i in range(point_In_bottom[1], point_out_bottom[1]+1):
        for j in range(img_width):
            if contour_img[i][j] == 255:
                if j > Bottom_x:
                    Bottom_x = j
                break
    
    # 收集上边缘点的y坐标
    for i in range(point_in_top[1], point_Out_top[1]+1):
        for j in range(img_width):
            if contour_img[i][j] == 255:
                if j == Top_x:
                    Top_y_vector.append(i)
                break
    
    # 收集下边缘点的y坐标
    for i in range(point_In_bottom[1], point_out_bottom[1]+1):
        for j in range(img_width):
            if contour_img[i][j] == 255:
                if j == Bottom_x:
                    Bottom_y_vector.append(i)
                break
    
    # 计算平均y坐标
    Top_y = sum(Top_y_vector) / float(len(Top_y_vector))
    Bottom_y = sum(Bottom_y_vector) / float(len(Bottom_y_vector))
    
    point_a = (Top_x, Top_y)
    point_b = (Bottom_x, Bottom_y)
    
    return point_a, point_b

最后大致获得的关键点如图所示,图源于基于图像相位及方向特征的掌纹识别的c++实现(二)

裁剪ROI

使用Top和Bottom点进行构建直角坐标系,并截取出ROI区域

def extract_roi(point_a, point_b, img, output_path=None):
    """提取ROI区域"""
    Top = (point_a[0], point_a[1])
    Bottom = (point_b[0], point_b[1])
    Origin_X = (Top[0] + Bottom[0]) / 2.0
    Origin_Y = (Top[1] + Bottom[1]) / 2.0
    Origin = (Origin_X, Origin_Y)
    
    # 计算旋转角度
    if Top[0] == Bottom[0]:
        angle = 0
    else:
        Slope_y_axis = (Top[1] - Bottom[1]) / (Top[0] - Bottom[0])
        angle = -1 * math.atan(1 / Slope_y_axis) * (180 / math.pi)
    
    # 旋转图像
    center = (Origin_X, Origin_Y)
    rot_mat = cv2.getRotationMatrix2D(center, angle, 1.0)
    rotated_img = cv2.warpAffine(img, rot_mat, (img.shape[1], img.shape[0]), flags=cv2.INTER_LINEAR)
    
    # 裁剪ROI
    Uleft = (int(Origin_X + 36), int(Origin_Y - 128 / 2))
    roi = rotated_img[Uleft[1]:Uleft[1]+128, Uleft[0]:Uleft[0]+128]
 
    # 保存结果
    if output_path:
        cv2.imwrite(output_path, roi_blur)
    
    return roi_blur

图源于基于图像相位及方向特征的掌纹识别的c++实现(二)

最后得到的ROI图片

特征提取

使用论文提到的Gabor滤波器对ROI区域进行卷积,提取特征

创建论文需求的Gabor滤波器,角度theta设置为π/4
def create_gabor_filter(theta, u, sigma, size):
    """创建Gabor滤波器"""
    g = np.zeros((2 * size + 1, 2 * size + 1), dtype=complex)
    
    for x in range(-size, size + 1):
        for y in range(-size, size + 1):
            g[x + size, y + size] = ((1 / (2 * np.pi * sigma ** 2)) *
                                   np.exp(-0.5 * ((x / sigma) ** 2 + (y / sigma) ** 2) +
                                          2 * np.pi * 1j * (u * np.cos(theta) * x + u * np.sin(theta) * y)))
    # 零均值化
    g = g - np.mean(g)
    
    return g
应用滤波器,并分离实部虚部
def apply_gabor_filter(img, theta=GABOR_THETA, u=GABOR_U, sigma=GABOR_SIGMA, size=GABOR_SIZE):
    """应用Gabor滤波器"""
    gabor_filter = create_gabor_filter(theta, u, sigma, size)
    features = signal.convolve2d(img, gabor_filter, mode='same')
    return features

def separate_complex_features(features):
    """分离复数特征为实部和虚部"""
    real_part = np.real(features)
    imag_part = np.imag(features)
    return real_part, imag_part

def binarize_features(features, threshold=0):
    """二值化特征"""
    binary_features = np.zeros_like(features)
    binary_features[features >= threshold] = 255
    binary_features[features < threshold] = 0
    return binary_features
    
def downsample_features(features, block_size=(4, 4), threshold=128):
    """下采样特征"""
    downsampled = skimage.measure.block_reduce(features, block_size, np.mean)
    binary = np.where(downsampled >= threshold, 255, 0)
    return binary

最后的得到的实部虚部类似于

对比

对不同的手掌使用二值化后的实部虚部进行对比,相减得到汉明距离

创建掩膜,主要为了去除背景

def create_palm_mask(roi_path):
    """创建掌纹掩膜"""
    img = read_grayscale_image(roi_path)
    return create_binary_mask(img, BINARY_THRESHOLD_FEATURE)

使用实部虚部以及掩膜计算汉明距离

def calculate_hamming_distance(img1_real, img1_imag, img1_mask, 
                              img2_real, img2_imag, img2_mask, 
                              offset_x, offset_y):
    """计算汉明距离"""
    # 裁剪图像以匹配偏移后的区域
    img1_real_crop = crop_image(img1_real, offset_x, offset_y)
    img1_imag_crop = crop_image(img1_imag, offset_x, offset_y)
    img1_mask_crop = crop_image(img1_mask, offset_x, offset_y)
    img2_real_crop = crop_image(img2_real, offset_x, offset_y)
    img2_imag_crop = crop_image(img2_imag, offset_x, offset_y)
    img2_mask_crop = crop_image(img2_mask, offset_x, offset_y)
    
    # 确保掩膜与图像尺寸一致
    if img1_real_crop.shape != img1_mask_crop.shape or img2_real_crop.shape != img2_mask_crop.shape:
        img1_mask_crop = cv2.resize(img1_mask_crop, (img1_real_crop.shape[1], img1_real_crop.shape[0]))
        img2_mask_crop = cv2.resize(img2_mask_crop, (img2_real_crop.shape[1], img2_real_crop.shape[0]))
    
    # 计算掩膜交集
    mask_intersection = img1_mask_crop & img2_mask_crop
    
    # 计算特征异或
    real_xor = np.logical_xor(img1_real_crop, img2_real_crop).astype(np.uint8)
    imag_xor = np.logical_xor(img1_imag_crop, img2_imag_crop).astype(np.uint8)
    
    # 计算分子
    numerator_real = np.sum(mask_intersection & real_xor)
    numerator_imag = np.sum(mask_intersection & imag_xor)
    numerator = numerator_real + numerator_imag
    
    # 计算分母
    denominator = 2 * np.sum(mask_intersection)
    
    # 避免除零错误
    if denominator == 0:
        return 1.0
    
    # 计算汉明距离
    hamming_dist = numerator / denominator
    
    return hamming_dist

使用偏移量减小图片中手掌位置的影响

 	img1_mask = create_palm_mask(roi_path1)
    img2_mask = create_palm_mask(roi_path2)
    
    # 尝试不同的偏移量,找到最小距离
    min_distance = float('inf')
    
    for x in range(*TRANSLATION_RANGE):
        for y in range(*TRANSLATION_RANGE):
            # 移动第一个图像
            img1_real_moved = move_image(img1_real, x, y)
            img1_imag_moved = move_image(img1_imag, x, y)
            
            # 计算距离
            distance = calculate_hamming_distance(
                img1_real_moved, img1_imag_moved, img1_mask,
                img2_real, img2_imag, img2_mask,
                x, y
            )
            
            # 更新最小距离
            min_distance = min(min_distance, distance)

最后得到两个掌纹间的汉明距离

感谢大家的支持,希望大家能够关注公众号 Prune的开发小记 457a4b817578ae99eabc0a13e507354.jpg

我会不断在公众号上更新新内容,谢谢大家!