题目描述:
编写一个程序实现如下功能:读入指纹图像,进行形态学骨架提取与基于距离变换的骨架提取,并实现裁剪算法
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)