You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

照片指纹提取:线条细化后噪声与断连问题的滤波优化问询

指纹纹线提取与滤波优化求助

我正在完成生物识别课程项目,需要从方向场中提取指纹类型(包括弓形、倾向弓形、环形、涡形、双环形),并从纹线里提取细节点(minutiae)。

目前已成功从样本照片中提取出纹线,但尝试高斯滤波器、双边滤波器、Gabor滤波器等各类滤波方法后,结果仍存在大量噪声,且纹线不连续,无法用于细节点提取,现寻求滤波相关的优化建议。

以下是完整可运行的Python代码、效果示例图及样本照片:

import cv2
import numpy as np
import math
import matplotlib.pyplot as plt

# Hardcoded path to input image
IMAGE_PATH = '20241108_171310.jpg'  # Replace with the path to your image


# Extracts the finger
def crop_image(image):
    hsv_image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
    
    # I use skin tone to get the boundaries. There may be better options.
    lower_skin = np.array([0, 0, 80], dtype=np.uint8)
    upper_skin = np.array([7, 255, 255], dtype=np.uint8)
    lower_skin2 = np.array([170, 0, 80], dtype=np.uint8)
    upper_skin2 = np.array([179, 255, 255], dtype=np.uint8)

    skin_mask = cv2.bitwise_or(cv2.inRange(hsv_image, lower_skin, upper_skin),
                               cv2.inRange(hsv_image, lower_skin2, upper_skin2))
    skin_mask = cv2.GaussianBlur(skin_mask, (51, 51), 20)
    skin_mask = cv2.inRange(skin_mask, 200, 255)
    contours, _ = cv2.findContours(skin_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

    if not contours:
        return image

    largest_contour = max(contours, key=cv2.contourArea)
    blacked = cv2.bitwise_and(image, image, mask=skin_mask)

    x, y, w, h = cv2.boundingRect(largest_contour)
    h = min(int(1.5 * w), h)
    cropped_image = blacked[y:y + h, x:x + w]

    return cropped_image

# Scales to target resolution
def scale_image(image, final_width):
    coefficient = final_width / image.shape[1]
    return cv2.resize(image, (int(image.shape[1] * coefficient), int(image.shape[0] * coefficient)))

# Calculates and draws the direction field out of sobel gradients
def draw_directions(directions, image, tile_size):
    for y in range(directions.shape[0]):
        for x in range(directions.shape[1]):
            cx = x * tile_size + tile_size // 2
            cy = y * tile_size + tile_size // 2
            dir_x, dir_y = np.cos(directions[y, x]), np.sin(directions[y, x])
            magnitude = math.sqrt(dir_x ** 2 + dir_y ** 2)
            dir_x /= magnitude
            dir_y /= magnitude
            line_length = 15
            x_end = cx + int(dir_x * line_length / 2)
            y_end = cy + int(dir_y * line_length / 2)
            x_start = cx - int(dir_x * line_length / 2)
            y_start = cy - int(dir_y * line_length / 2)
            cv2.line(image, (x_start, y_start), (x_end, y_end), color=(0, 0, 255), thickness=2)

def get_directions(grad_x, grad_y, image_shape, tile_size):
    directions = np.zeros((image_shape[0] // tile_size, image_shape[1] // tile_size), dtype=np.float32)
    for y in range(image_shape[0] // tile_size):
        for x in range(image_shape[1] // tile_size):
            tile_grad_x = grad_x[y * tile_size:(y + 1) * tile_size, x * tile_size:(x + 1) * tile_size]
            tile_grad_y = grad_y[y * tile_size:(y + 1) * tile_size, x * tile_size:(x + 1) * tile_size]

            a = 2 * np.sum(tile_grad_x * tile_grad_y)
            b = np.sum(tile_grad_x ** 2 - tile_grad_y ** 2)
            directions[y, x] = 0.5 * np.arctan2(a, b) + np.pi / 2

    return directions

def extract_lines(grayscale_image):
    # Enhance contrast
    clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
    enhanced = clahe.apply(grayscale_image)

    # Apply stronger Gaussian blur
    enhanced = cv2.GaussianBlur(enhanced, (5, 5), 3)

    # Adaptive thresholding with adjusted parameters
    thresholded = cv2.adaptiveThreshold(enhanced, 255,
                                        cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
                                        cv2.THRESH_BINARY,
                                        31, -2)

    # Morphological operations
    kernel = np.ones((2, 2), np.uint8)
    thresholded = 255 - cv2.erode(255 - thresholded, kernel, iterations=1)

    # Enhance continuity
    thresholded = enhance_continuity(thresholded)

    return thresholded

def enhance_continuity(binary_image):
    kernel = np.ones((3, 3), np.uint8)
    # Close small gaps
    closed = cv2.morphologyEx(binary_image, cv2.MORPH_CLOSE, kernel)
    # Remove small noise
    opened = cv2.morphologyEx(closed, cv2.MORPH_OPEN, kernel)
    return opened

def thin_lines(binary_image):
    # Ensure ridges are white, valleys are black for thinning operation
    thinned = cv2.ximgproc.thinning(binary_image)
    return thinned

def connect_ridges(thinned_image, max_gap=5):
    result = thinned_image.copy()

    # Find endpoints
    kernel = np.array([[1, 1, 1],
                       [1, 10, 1],
                       [1, 1, 1]], dtype=np.uint8)
    conv = cv2.filter2D(result.astype(np.float32), -1, kernel)
    endpoints = np.where((conv == 11) & (result == 255))

    # Connect nearby endpoints
    for i in range(len(endpoints[0])):
        y1, x1 = endpoints[0][i], endpoints[1][i]
        for j in range(i + 1, len(endpoints[0])):
            y2, x2 = endpoints[0][j], endpoints[1][j]
            dist = np.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
            if 0 < dist <= max_gap:
                cv2.line(result, (x1, y1), (x2, y2), 255, 1)

    return result

if __name__ == "__main__":
    # Load image
    image = cv2.imread(IMAGE_PATH)
    if image is None:
        print(f"Error: Unable to load image at {IMAGE_PATH}")
        exit()

    # Process image
    cropped_image = crop_image(image)
    scaled_image = scale_image(cropped_image, 768)
    grayscale_image = cv2.cvtColor(scaled_image, cv2.COLOR_BGR2GRAY)

    # Enhanced preprocessing
    grayscale_image = cv2.GaussianBlur(grayscale_image, (5, 5), 3)

    # Sobel gradients
    sobel_x = cv2.Sobel(grayscale_image, cv2.CV_32F, 1, 0, ksize=3)
    sobel_y = cv2.Sobel(grayscale_image, cv2.CV_32F, 0, 1, ksize=3)

    # Get and draw directions
    directions = get_directions(sobel_x, sobel_y, grayscale_image.shape, 32)
    image_directions = scaled_image.copy()
    draw_directions(directions, image_directions, 32)

    # Extract and enhance lines
    extracted_lines = extract_lines(grayscale_image)
    enhanced_lines = enhance_continuity(extracted_lines)

    # Thin lines and connect ridges
    thinned_lines = thin_lines(enhanced_lines)
    connected_lines = connect_ridges(thinned_lines, max_gap=30)
    
    
    # So that the first two displayed images are not blue
    image = cv2.cvtColor(image,cv2.COLOR_BGR2RGB)
    cropped_image = cv2.cvtColor(cropped_image, cv2.COLOR_BGR2RGB)

    # Display results
    images = [image, cropped_image, image_directions, 255 - extracted_lines, 255 - enhanced_lines, 255 - thinned_lines, 255 - connected_lines]
    titles = ["Original Image", "Cropped Image", "Orientation Field", "Extracted Lines", "Enhanced Lines", "Thinned Lines", "Connected Lines"]

    plt.figure(figsize=(35, 10))
    for i in range(len(images)):
        plt.subplot(1, 7, i + 1)
        plt.imshow(images[i], cmap='gray' if i > 1 else None)
        plt.title(titles[i])
        plt.axis('off')
    plt.tight_layout()
    plt.show()

样本照片与处理效果

样本照片
处理效果示例

内容的提问来源于stack exchange,提问作者ampersander

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 13:28:11