Hilditch算法参看资料:http://cis.k.hosei.ac.jp/~wakahara/Hilditch.c
Hilditch第二种算法:http://cgm.cs.mcgill.ca/~godfried/teaching/projects97/azar/skeleton.html#algorithm
图像细化:
图像细化主要是针对二值图而言,所谓骨架,可以理解为图像的中轴,,一个长方形的骨架,是它的长方向上的中轴线,圆的骨架是它的圆心,直线的骨架是它自身,孤立点的骨架也是自身。我们来看看典型的图形的骨架(用粗线表示)
细化的算法有很多种,但比较常用的算法是查表法。细化是从原来的图中去掉一些点,但仍要保持原来的形状。实际上是保持原图的骨架。判断一个点是否能去掉是以8个相邻点(八连通)的情况来作为判据的,具体判据为:
1,内部点不能删除
2,鼓励点不能删除
3,直线端点不能删除
4,如果P是边界点,去掉P后,如果连通分量不增加,则P可删除
假设当前被处理的像素为p0,我们使用下图所示的8邻域表示方式。
我们处理的为二值图像,背景为黑色=0,要细化的前景物体像素值=255。
对于Hilditch算法来说,它并不是一个完全的并行算法,而是串行并行相结合。当前像素是否是能够删除的骨架点,不仅是由它周围的8邻域决定,而且和前面像素的判定结果有关。一个像素判定为可以删除,我们并不直接删除它,而是在目地图像中设置像素值为GRAY=128,这个信息可能会影响之后其它像素的判定。
当图像一次扫描迭代完成后,我们把所有置为GRAY的像素设置为0,从而删除它。
算法的描述如下。
迭代扫描当前图像
对于当前像素点,扫描它的8邻域:
如果邻域的像素值为255,则b[i]=1(i=0…8),像素值为128(GRAY,表示该像素点在前面的循环中被标记为删除),b[i]=-1;如果像素值为0,则b[i]=0。
下面会根据b[i]的值进行6个条件判断,如果条件满足,则会标记该像素值为GRAY(128)。
b[0]=1,即当前像素必须为前景点。
1-abs(b1) + 1 – abs(b3) + 1 – abs(b5) + 1 – abs(b7) >= 1,该条件表示当前像素为边界点,即东西南北四个点至少有一个b[i]=0。
abs(b1)+…+abs(b8)>=2, 该条件表示不能删除端点,即p0点周围只有一个点为1或-1的情况。
统计b1到b8等于1的数量,该数量值必须大于1,该条件表示不能删除端点。
连通性检测,使用下面的公式:
首先根据当前像素周围3*3域的值,记录d[9]数组,如果b[i]等于0,则d[i]=0, 否则d[i]=1,最后计算 d1-d1*d2*d3+d3-d3*d4*d5+d5-d5*d6*d7+d7-d7*d8*d1是否为1,为1则满足连通性,可以删除。
6.最后一个条件保证当轮廓是2个像素宽时,只删除一边。统计sum的值,当值为8时候,可以删除。
当这6个条件都满足时候,标记当前像素值为GRAY(128),然后在判断别的像素。当所有像素都扫描一遍后,完成一次迭代。
此时我们会把刚才标记为GARY的像素,都设置为0,真正的删除它,如果上一次循环已经没有标记删除的像素,则退出迭代,否则进行下一次迭代。
第二种算法描述和上面的并行算法很相似,特别是前2个条件一模一样,不同的是3,4两个条件,还有就是该描述算法并没有像上面算法那样,把一次迭代分成2个阶段。
下面看下它的算法描述:
代码参看https://blog.csdn.net/hehedadaq/article/details/80303218
测试原图(684*342):
# -*- coding: utf-8 -*-
import cv2
# 细化函数,输入需要细化的图片(经过二值化处理的图片)和映射矩阵array
# 这个函数将根据算法,运算出中心点的对应值
def Thin(image, array):
h, w = image.shape
iThin = image
for i in range(h):
for j in range(w):
if image[i, j] == 0:
a = [1] * 9
for k in range(3):
for l in range(3):
# 如果3*3矩阵的点不在边界且这些值为零,也就是黑色的点
if -1 < (i - 1 + k) < h and -1 < (j - 1 + l) < w and iThin[i - 1 + k, j - 1 + l] == 0:
a[k * 3 + l] = 0
sum = a[0] * 1 + a[1] * 2 + a[2] * 4 + a[3] * 8 + a[5] * 16 + a[6] * 32 + a[7] * 64 + a[8] * 128
iThin[i, j] = array[sum] * 255
return iThin
# 映射表
array = [0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1,\
1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1,\
0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1,\
1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1,\
1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\
1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1,\
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\
0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1,\
1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1,\
0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1,\
1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,\
1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\
1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,\
1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0,\
1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0]
if __name__ == '__main__':
src = cv2.imread(r'C:\Users\xxx\Desktop\001.png', 0)
Gauss_img = cv2.GaussianBlur(src, (3,3), 0)
cv2.imshow('image', Gauss_img)
cv2.waitKey(0)
# 自适应二值化函数,需要修改的是55那个位置的数字,越小越精细,细节越好,噪点更多,最大不超过图片大小
adap_binary = cv2.adaptiveThreshold(Gauss_img, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 55, 2)
iThin = Thin(adap_binary.copy(), array)
cv2.imshow('adaptive', adap_binary)
cv2.imshow('adaptive_iThin', iThin)
cv2.waitKey(0)
# 获取简单二值化的细化图,并显示
ret, binary = cv2.threshold(Gauss_img, 130, 255, cv2.THRESH_BINARY)
iThin_2 = Thin(binary.copy(), array)
cv2.imshow('binary', binary)
cv2.imshow('binary_iThin', iThin_2)
cv2.waitKey(0)
cv2.destroyAllWindows()
在每行水平扫描的过程中,先判断每一点的左右邻居,如果都是黑点,则该点不做处理。另外,如果某个黑店被删除了,则跳过它的右邻居,处理下一点。对矩形这样做完一遍,水平方向会减少两像素。
然后我们再改垂直方向扫描,方法一样。
这样做一次水平扫描和垂直扫描,原图会“瘦”一圈
多次重复上面的步骤,知道图形不在变化为止算法优化后如下:
import cv2
def VThin(image, array):
h,w= image.shape[:2]
NEXT = 1
for i in range(h):
for j in range(w):
if NEXT == 0:
NEXT = 1
else:
M = image[i, j-1] + image[i,j] + image[i, j+1] if 0<j<w-1 else 1
if image[i, j] == 0 and M != 0:
a = [0] * 9
for k in range(3):
for l in range(3):
if-1<(i-1+k)<h and -1<(j-1+l)<w and image[i-1+k, j-1+l] == 255:
a[k*3 + l] = 1
sum = a[0]*1 + a[1]*2 + a[2]*4 + a[3]*8 + a[5]*16 + a[6]*32 + a[7]*64 + a[8]*128
image[i,j] = array[sum]*255
if array[sum] == 1:
NEXT = 0
return image
def HThin(image, array):
h, w = image.shape[:2]
NEXT = 1
for j in range(w):
for i in range(h):
if NEXT == 0:
NEXT = 1
else:
M = image[i-1, j] + image[i, j] + image[i+1, j] if 0<i<h-1 else 1
if image[i, j] == 0 and M != 0:
a = [0] * 9
for k in range(3):
for l in range(3):
if -1<(i-1+k)<h and -1<(j-1+l)<w and image[i-1+k, j-1+l] == 255:
a[k*3 + l] = 1
sum = a[0]*1 + a[1]*2 + a[2]*4 + a[3]*8 + a[5]*16 + a[6]*32 + a[7]*64 + a[8]*128
image[i, j] = array[sum] * 255
if array[sum] == 1:
NEXT = 0
return image
def Xihua(binary, array, num=10):
iXihua = binary.copy()
for i in range(num):
VThin(iXihua, array)
HThin(iXihua, array)
return iXihua
array = [0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1,\
1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1,\
0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1,\
1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1,\
1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\
1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1,\
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\
0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1,\
1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1,\
0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1,\
1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,\
1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\
1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,\
1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0,\
1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0]
if __name__ == '__main__':
image = cv2.imread(r'C:\Users\xxx\Desktop\002.png', 0)
ret, binary = cv2.threshold(image, 70, 255, cv2.THRESH_BINARY)
cv2.imshow('image', image)
cv2.imshow('binary', binary)
cv2.waitKey(0)
t1 = time.time()
iThin = Xihua(binary, array)
t2 = time.time()
print('cost time:',t2-t1)
cv2.imshow('iThin', iThin)
cv2.waitKey(0)
cv2.destroyAllWindows()
'''
cost time: 21.486935138702393
'''
显然,比之前效果好些了。使用 测试原图(684*342) 耗时有点久(处理器i5-8300H)
测试图片2
代码运算速度优化,减少了运行时间,使用测试原图(684*342)(上面的图片)
import cv2,time
import numpy as np
def Three_element_add(array):
array0 = array[:]
array1 = np.append(array[1:],np.array([0]))
array2 = np.append(array[2:],np.array([0, 0]))
arr_sum = array0 + array1 + array2
return arr_sum[:-2]
def VThin(image, array):
NEXT = 1
height, width = image.shape[:2]
for i in range(1,height):
M_all = Three_element_add(image[i])
for j in range(1,width):
if NEXT == 0:
NEXT = 1
else:
M = M_all[j-1] if j<width-1 else 1
if image[i, j] == 0 and M != 0:
a = np.zeros(9)
if height-1 > i and width-1 > j:
kernel = image[i - 1:i + 2, j - 1:j + 2]
a = np.where(kernel == 255, 1, 0)
a = a.reshape(1, -1)[0]
NUM = np.array([1,2,4,8,0,16,32,64,128])
sumArr = np.sum(a*NUM)
image[i, j] = array[sumArr] * 255
if array[sumArr] == 1:
NEXT = 0
return image
def HThin(image, array):
height, width = image.shape[:2]
NEXT = 1
for j in range(1,width):
M_all = Three_element_add(image[:,j])
for i in range(1,height):
if NEXT == 0:
NEXT = 1
else:
M = M_all[i-1] if i < height - 1 else 1
if image[i, j] == 0 and M != 0:
a = np.zeros(9)
if height - 1 > i and width - 1 > j:
kernel = image[i - 1:i + 2, j - 1:j + 2]
a = np.where(kernel == 255, 1, 0)
a = a.reshape(1, -1)[0]
NUM = np.array([1, 2, 4, 8, 0, 16, 32, 64, 128])
sumArr = np.sum(a * NUM)
image[i, j] = array[sumArr] * 255
if array[sumArr] == 1:
NEXT = 0
return image
def Xihua(binary, array, num=10):
binary_image = binary.copy()
image = cv2.copyMakeBorder(binary_image, 1, 0, 1, 0, cv2.BORDER_CONSTANT, value=0)
for i in range(num):
VThin(image, array)
HThin(image, array)
return image
array = [0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1,\
1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1,\
0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1,\
1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1,\
1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\
1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1,\
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\
0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1,\
1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1,\
0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1,\
1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,\
1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\
1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,\
1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0,\
1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0]
if __name__ == '__main__':
image = cv2.imread(r'C:\Users\xxx\Desktop\001.png', 0)
ret, binary = cv2.threshold(image, 70, 255, cv2.THRESH_BINARY)
cv2.imshow('image', image)
cv2.imshow('binary', binary)
cv2.waitKey(0)
t1 = time.time()
iThin = Xihua(binary, array)
t2 = time.time()
print('cost time:',t2-t1)
cv2.imshow('iThin', iThin)
cv2.waitKey(0)
cv2.destroyAllWindows()
'''
cost time: 7.425575494766235
'''
参考与鸣谢
https://www.cnblogs.com/xianglan/archive/2011/01/01/1923779.html
https://blog.csdn.net/FunnyWhiteCat/article/details/80670332
https://www.cnblogs.com/DOMLX/p/8672489.html
https://www.cnblogs.com/My-code-z/p/5712524.html
https://www.cnblogs.com/mikewolf2002/p/3327183.html
https://www.researchgate.net/signup.SignUp.html?ev=su_requestFulltext
https://blog.csdn.net/Lg_cc/article/details/50179469
优化算法:https://wenku.baidu.com/view/a74c003567ec102de2bd8915.html