【数字图像处理】骨架提取与裁剪算法——Python实现

题目描述:

编写一个程序实现如下功能:读入指纹图像,进行形态学骨架提取与基于距离变换的骨架提取,并实现裁剪算法

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

# opencv自带大津法 ret, threshold = cv2.threshold(img,0,255,cv2.THRESH_BINARY|cv2.THRESH_OTSU)
# 大津法图像二值化:自己实现
def Dajin(img):
    g = []
    hist = cv2.calcHist([img],[0],None,[256],[0,256])
    N = len(img)
    for k in range(np.max(img)):
        c1 = hist[0:k+1]
        c2 = hist[k+1:]
        w1 = np.sum(c1)/N
        w2 = np.sum(c2)/N
        u1 = np.sum([i*c1[i][0] for i in range(len(c1))])/np.sum(c1)
        u2 = np.sum([(i+k+1)*c2[i][0] for i in range(len(c2))])/np.sum(c2)
        g.append(w1*w2*(u1-u2)**2)
    T = np.argmax(g) # 大津法阈值
    ret,thres = cv2.threshold(img,T,255,cv2.THRESH_BINARY)
    return thres

# """
# ((1,1),(2,2))表示在二维数组array第一维(此处便是行)前面填充1行,最后面填充1行;
#                  在二维数组array第二维(此处便是列)前面填充2列,最后面填充2列
# constant_values=(0,4) 表示第一维填充0,第二维填充4
# """


# 基于距离的骨架提取算法:就是串行距离变换算法,本质上:以谁为目标集合,就令那个集合为1,其他集合为无穷。边界填充总是无穷
# 他体现了一种星火燎原的思想:就是充分利用之前的数据
def distance_method(bin_img):
    bin_img = copy.copy(bin_img)
    # 前景,即指纹是黑色0,背景是255
    # bin_img = np.abs(bin_img-255)*255
    ''' Step1: 求取二值图像的边界 '''
    # 边界 = A - 腐蚀(A)
    # kernel = np.ones((3,3),np.uint8)
    # erode = cv2.erode(bin_img,kernel,iterations=1,borderType=cv2.BORDER_CONSTANT,borderValue=0)
    # loc = np.where(erode==255)
    # bin_img[loc] = 0
    pad = np.pad(bin_img,((1,1),(1,1)),'constant',constant_values=255)
    erode = copy.deepcopy(bin_img)
    for i in range(1,bin_img.shape[0]+1):
        for j in range(1,bin_img.shape[1]+1):
            if np.sum(pad[i-1:i+2,j-1:j+2]) != 0:
                erode[i-1,j-1] = 255
    loc = np.where(erode==0)  # 腐蚀后的位置
    edge = copy.deepcopy(bin_img)
    edge[loc] = 255   # edge: 边界图像
    # #可视化
    # plt.subplot(1,3,1)
    # plt.imshow(bin_img,cmap='gray')
    # plt.subplot(1,3,2)
    # plt.imshow(erode,cmap='gray')
    # plt.subplot(1,3,3)
    # plt.imshow(edge,cmap='gray')
    # plt.show()
    
    ''' Step2: 对边界二值图像求取距离变换 '''
    obj = np.where(edge==0) # 二值化图像中,黑色的是指纹
    obj = [ i+1 for i in obj]
    F = np.ones((img.shape[0]+2,img.shape[1]+2)) * math.inf
    F[obj] = 1
    # 距离因子     # 2 1 2
                #   1
                #       1
                #   2 1 2
    for i in range(1,img.shape[0]+1):
        for j in range(1,img.shape[1]+1):
            F[i,j] = np.min([ F[i,j] , 1+F[i,j-1] , 2+F[i-1,j-1] , 1+F[i-1,j] , 2+F[i-1,j+1] ])
    for i in range(img.shape[0],0,-1):
        for j in range(img.shape[1],0,-1):
            F[i,j] = np.min([ F[i,j] , 1+F[i,j+1] , 2+F[i+1,j+1] , 1+F[i+1,j] , 2+F[i+1,j-1]])

    ''' Step3: 落入原二值图像中的局部极大值 '''
    loc = np.where(bin_img == 0) # 二值化图像中的目标点
    loc = np.array([[loc[0][i],loc[1][i]] for i in range(len(loc[0])) \
        if F[loc[0][i]+1,loc[1][i]+1] == np.max(F[loc[0][i]:loc[0][i]+3,loc[1][i]:loc[1][i]+3])])
    ''' Step4: 图像骨架 '''
    skeleton = np.ones_like(bin_img) * 255
    skeleton[loc[:,0],loc[:,1]] = 0
    return skeleton

# 形态学骨架提取方法
def morphology_method(bin_img):
    bin_img = copy.copy(bin_img)
    bin_img = np.abs(bin_img-255)*255  # 原图中指纹为黑色:要想用形态学方法,需要黑白颠倒:最大值为1 * 255
    # cv2内置的腐蚀函数:反正背景需要是0,前景可以随意1,255,25等;对前景进行腐蚀
    k = 0
    kernel = np.ones((3,3),np.uint8)
    loc = []
    while 1:
        erode = cv2.erode(bin_img,kernel,iterations=k,borderType=cv2.BORDER_CONSTANT,borderValue=0)
        if (erode == 0).all():
            break
        S = cv2.morphologyEx(erode,cv2.MORPH_OPEN,kernel,iterations=1)
        loc1 = np.argwhere(erode==255).tolist()
        loc2 = np.argwhere(S==255).tolist()
        res = [i for i in loc1 if not i in loc2]
        loc.extend(res)
        k += 1
    loc = np.array(loc)
    skeleton = np.zeros_like(bin_img,dtype=np.uint8)
    skeleton[loc[:,0],loc[:,1]] = 1
    return skeleton


# 击中击不中变换
def hitornot(img,SE): # 目标是1,背景是0
    img = copy.copy(img)
    r,c = img.shape[0],img.shape[1]
    img = np.pad(img,((1,1),(1,1)),'constant',constant_values=0)
    img = img.astype(np.float64) 
    res = np.zeros_like(img,dtype=np.uint8)
    for p in range(1,r+1):
        for q in range(1,c+1):
            area = copy.copy(img[p-1:p+2,q-1:q+2])
            if len(SE) == 2:
                area[SE[1][:,0],SE[1][:,1]] = math.inf
            if (area == SE[0]).all():
                res[p,q] = 1
    return res[1:-1,1:-1]

# 细化操作
def refinement(img,B):
    img = copy.copy(img)
    hit = hitornot(img,B)
    loc1 = np.argwhere(img==1).tolist()
    loc2 = np.argwhere(hit==1).tolist()
    loc = np.array([k for k in loc1 if k in loc2])
    if len(loc)!=0:
        img[loc[:,0],loc[:,1]] = 0
    return img

# 裁剪
def crop(img): # 需要目标是1,背景是0
    SE1 = [np.array([[math.inf,0,0],[1,1,0],[math.inf,0,0]]),np.array([[0,0],[2,0]])]
    SE2 = [np.array([[math.inf,1,math.inf],[0,1,0],[0,0,0]]),np.array([[0,0],[0,2]])]
    SE3 = [np.array([[0,0,math.inf],[0,1,1],[0,0,math.inf]]),np.array([[0,2],[2,2]])]
    SE4 = [np.array([[0,0,0],[0,1,0],[math.inf,1,math.inf]]),np.array([[2,0],[2,2]])]
    SE5 = [np.array([[1,0,0],[0,1,0],[0,0,0]])]
    SE6 = [np.array([[0,0,1],[0,1,0],[0,0,0]])]
    SE7 = [np.array([[0,0,0],[0,1,0],[0,0,1]])]
    SE8 = [np.array([[0,0,0],[0,1,0],[1,0,0]])]
    ''' Step1: 删除3个即以下长度的分枝, 得到X1 '''
    X1 = copy.copy(img)
    for _ in range(3): # 连续3次操作
        for i in range(8): # 结构元序列长度为8
            SE = eval(f'SE{i+1}')
            X1 = refinement(X1,SE)
    ''' Step2: 得到X1中端点集合X2 '''
    g = []
    for i in range(8):
        SE = eval(f'SE{i+1}')
        kk = hitornot(X1,SE)
        res = np.argwhere(kk==1)
        g.extend(res)
    X2 = np.zeros_like(img,dtype=np.uint8)
    if len(g)!=0:
        g = np.array(g)
        X2[g[:,0],g[:,1]] = 1
    ''' Step3: 对端点连续进行3次膨胀 '''
    H = np.ones((3,3),dtype=np.uint8)
    X3 = copy.copy(X2)
    loc2 = np.argwhere(img==1).tolist() # 原图A
    for _ in range(3):
        X3 = cv2.dilate(X3,H,iterations=1)
        loc1 = np.argwhere(X3==1).tolist()
        loc = np.array([k for k in loc1 if not k in loc2]) # 与原图A做交集
        if len(loc)!=0:
            X3[loc[:,0],loc[:,1]] = 0
    ''' Step4: 取并集 '''
    X4 = np.zeros_like(img,dtype=np.uint8)
    loc1 = np.argwhere(X1==1).tolist()
    loc2 = np.argwhere(X3==1).tolist()
    loc1.extend(loc2)
    loc1 = np.array(loc1)
    X4[loc1[:,0],loc1[:,1]] = 1
    return X4


    # 测试crop函数
    # 《数字图像处理》第三版 P421 图9.25(a)
    img = np.zeros((12,16),dtype=np.uint8)
    black =[[2,7],[2,8],[2,9],[3,6],[3,10],[4,2],[4,5],[5,2],[5,4],[5,11],[6,2],[6,3],[6,11],[6,12],\
            [7,3],[7,10],[7,12],[8,4],[8,10],[8,12],[9,3],[9,10],[9,12],[10,3],[10,9],[10,12],[10,15],\
            [11,4],[11,5],[11,6],[11,7],[11,8],[11,13],[11,14]]
    black = np.array([[i[0]-1,i[1]-1] for i in black])
    img[black[:,0],black[:,1]] = 1
    # print(img)
    crop(img)
    

if __name__ == '__main__':
    img_path = r'C:\Users\Lenovo\Desktop\fingerprint2.png'
    img = cv2.imread(img_path,0) # 原始灰度图像
    bin_img = Dajin(img)  # 大津法后的二值图像:0和255
    skeleton1 = distance_method(bin_img) # 基于距离变换的骨架图像
    skeleton1 = np.abs(skeleton1 - 255)
    # 基于形态学骨架提取的图像
    skeleton2 = morphology_method(bin_img) # 指纹部分是白色的,[0,1]
    
    plt.imshow(skeleton1,'gray')
    plt.title('skeleton1')
    plt.show()
    plt.imshow(skeleton2,'gray')
    plt.title('skeleton2')
    plt.show()
    skeleton1_crop = crop(skeleton1)
    skeleton2_crop = crop(skeleton2)
    plt.imshow(skeleton1_crop,'gray')
    plt.title('skeleton1_crop')
    plt.show()
    plt.imshow(skeleton2_crop,'gray')
    plt.title('skeleton2_crop')
    plt.show()
    
    
    # plt.subplot(2,3,1)
    # plt.imshow(img,cmap='gray')
    # plt.subplot(2,3,4)
    # plt.imshow(bin_img,cmap='gray')
    # plt.subplot(2,3,2)
    # plt.imshow(skeleton1,cmap='gray')
    # plt.subplot(2,3,5)
    # plt.imshow(skeleton2,cmap='gray')
    # plt.subplot(2,3,3)
    # plt.imshow(skeleton1_crop,'gray')
    # plt.subplot(2,3,6)
    # plt.imshow(skeleton2_crop,'gray')
    # plt.show()
    
    # opencv自带的提取骨架函数
    # from skimage import morphology
    # bin_img = np.abs(bin_img-255)
    # skeleton3 = morphology.skeletonize(bin_img)
    # skeleton3 = np.abs(skeleton3-255)
    # plt.imshow(skeleton3,'gray')
    # plt.show()




# 腐蚀函数用法示例
    # a = np.ones((5,5),np.uint8)
    # print(a)
    # kernel = np.ones((3,3),dtype=np.uint8)
    # b = cv2.erode(a,kernel,iterations=1,borderType=cv2.BORDER_CONSTANT,borderValue=0)
    # print(b)


你可能感兴趣的:(python,算法,opencv)