拓扑重建
这是http://www.imagepy.org/的作者原创,我只是对其理解之后改进和说明,欢迎大家使用这个小软件!
第一个用Python写的小项目,不算自创的,大部分都是参考别人的,由于第一次用python写opencv遇到了无数的问题,最后也算完成了。
opencv的入门我就不写了,以前都学过差不多,在此记录一下基础!
基础操作
首先说一下python条件下用opencv,重点记录,不重点看文档就好!
1.如何创建一个空白图片
A.
1 test = np.array([[0,255,0],[0,0,0],[0,0,0]])
2 np.array([[0,255,0],[0,0,0],[0,0,0]],np.uint8)#千万别写成这种格式,他把格式也存进去了
B.
1 showImage = np.zeros((inputImage.shape[0],inputImage.shape[1],3),np.uint8)
C.
1 showImage = inputImage.copy()
2 showImage = cv2.cvtColor(showImage,cv2.COLOR_GRAY2BGR)
2.如何读写一个图片
A.
1 img[j,i] = 255#单通道读写
B.
1 #三通道读写
2 img[j,i,0]= 255
3 img[j,i,1]= 255
4 img[j,i,2]= 255
C.
1 img.itemset((10,10,2),100)#写入
2 img.item(10,10,2)#读
3.画图形
1 cv2.circle(showImage,(j+1,i+1), 5, (0,0,255), -1)#画圆注意圆心是X,Y
4.python大部分函数都有返回值
#python的函数貌似都有返回值 img = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) ret2,img = cv2.threshold(img,0,255,cv2.THRESH_BINARY | cv2.THRESH_OTSU)
5.
1 Shape of image is accessed by img.shape. It returns a tuple of number of rows, columns and channels
1 Total number of pixels is accessed by img.size:
1 Image datatype is obtained by img.dtype:
骨架提取
废话说了不少开始实战了:
在此说明:
本程序和算法主要参考:http://www.cnblogs.com/xianglan/archive/2011/01/01/1923779.html写的非常好,大家初学直接看大佬的博客就行了。
本文后续拓普重建主要是:https://github.com/yxdragon/sknw没有看这位大佬的程序,但是他的思想帮助我很多。
因为得到别人帮助才有我自己的收获,所以乐于分享其他初学者!
以下是和大佬的对话,没经过本人同意所以打了马,非常乐于助人的大佬!
1 import cv2 2 import numpy as np 3 from matplotlib import pyplot as plt 4 5 array = [0,0,1,1,0,0,1,1,1,1,0,1,1,1,0,1,\ 6 1,1,0,0,1,1,1,1,0,0,0,0,0,0,0,1,\ 7 0,0,1,1,0,0,1,1,1,1,0,1,1,1,0,1,\ 8 1,1,0,0,1,1,1,1,0,0,0,0,0,0,0,1,\ 9 1,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0,\ 10 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,\ 11 1,1,0,0,1,1,0,0,1,1,0,1,1,1,0,1,\ 12 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,\ 13 0,0,1,1,0,0,1,1,1,1,0,1,1,1,0,1,\ 14 1,1,0,0,1,1,1,1,0,0,0,0,0,0,0,1,\ 15 0,0,1,1,0,0,1,1,1,1,0,1,1,1,0,1,\ 16 1,1,0,0,1,1,1,1,0,0,0,0,0,0,0,0,\ 17 1,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0,\ 18 1,1,0,0,1,1,1,1,0,0,0,0,0,0,0,0,\ 19 1,1,0,0,1,1,0,0,1,1,0,1,1,1,0,0,\ 20 1,1,0,0,1,1,1,0,1,1,0,0,1,0,0,0] 21 22 def Thin(image,array): 23 '''未改进算法''' 24 #midImage = image.copy() 25 for i in range(image.shape[0]-2): 26 for j in range(image.shape[1]-2): 27 if image[i+1,j+1] == 0: 28 a = [1]*9 #定义list[9] 29 for k in range(3): 30 for l in range(3): 31 a[k*3+l] = 0 if image[i+k,j+l]==0 else 1 32 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 33 image[i+1,j+1] = array[sum]*255 34 return image 35 36 def HThin(image,array): 37 flag = True #如果该点被删除,跳过下一个点 38 midImage = image.copy() 39 for i in range(image.shape[0]-2): 40 for j in range(image.shape[1]-2): 41 if flag == False: 42 flag == True 43 else: 44 if image[i+1,j+1] == 0 and (image[i+1,j] != 0 or image[i+1,j+2] != 0):#左右都为黑点不处理 45 a = [1]*9 #定义list[9] 46 for k in range(3): 47 for l in range(3): 48 a[k*3+l] = 0 if midImage[i+k,j+l]==0 else 1 49 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 50 midImage[i+1,j+1] = array[sum]*255 51 return midImage 52 def VThin(image,array): 53 flag = True #如果该点被删除,跳过下一个点 54 midImage = image.copy() 55 for i in range(image.shape[1]-2): 56 for j in range(image.shape[0]-2): 57 if flag == False: 58 flag == True 59 else: 60 if image[j+1,i+1] == 0 and (image[j,i+1] != 0 or image[j+2,i+1] != 0):#左右都为黑点不处理 61 a = [1]*9 #定义list[9] 62 for k in range(3): 63 for l in range(3): 64 a[k*3+l] = 0 if midImage[j+k,i+l]==0 else 1 65 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 66 midImage[j+1,i+1] = array[sum]*255 67 return midImage 68 69 def wjy_Bone(inputImage,num=100): 70 '''改进算法''' 71 for i in range(num): 72 inputImage = VThin(HThin(inputImage,array),array) 73 return inputImage 74 75 def ThredImage(image,thred): 76 '''二值化图像''' 77 imageGray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY) 78 midimage = np.zeros((imageGray.shape[0],imageGray.shape[1]),imageGray.dtype) 79 for i in range(imageGray.shape[0]): 80 for j in range(imageGray.shape[1]): 81 midimage[i,j] = 0 if imageGray[i,j] < int(thred) else 255 82 return midimage 83 84 def drawBone(inputImage): 85 #showImage = np.zeros((inputImage.shape[0],inputImage.shape[1],3),np.uint8) 86 showImage = inputImage.copy() 87 showImage = cv2.cvtColor(showImage,cv2.COLOR_GRAY2BGR) 88 for i in range(inputImage.shape[0]-2): 89 for j in range(inputImage.shape[1]-2): 90 if inputImage[i+1,j+1] == 255 : continue 91 flag = -1 92 for J in range(3): 93 for K in range(3): 94 if inputImage[i+J,j+K] == 0: 95 flag += 1 96 if(flag==1 or flag>=3): 97 showImage = cv2.circle(showImage,(j+1,i+1), 5, (0,0,255), -1) 98 showImage[i+1,j+1,0] = 255 99 showImage[i+1,j+1,1] = 0 100 showImage[i+1,j+1,2] = 0 101 return showImage 102 103 if __name__ == '__main__': 104 img = cv2.imread("5.jpg") 105 #test = np.array([[0,255,0],[0,0,0],[0,0,0]]) 106 #千万写写成np.array([[0,255,0],[0,0,0],[0,0,0]],np.uint8) 107 img = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) 108 ret2,img = cv2.threshold(img,0,255,cv2.THRESH_BINARY | cv2.THRESH_OTSU) 109 #testImage = Thin(img,array) 110 #testImage = wjy_Bone(img) 111 testImage = drawBone(wjy_Bone(img,10)) 112 plt.imshow(testImage,cmap='gray',interpolation = 'bicubic') 113 plt.show() 114
轮廓追溯
第一步:
对骨架提取之后的图片变换成(0,1)图,骨架为0,背景为1
第二步:
提取骨架角点,方法在骨架提取的时候有说明。
第三步:
在提取角点的基础上标记骨架图,对角点标记为2,对轮廓标记为1,对背景标记为0
第四步:
对角点进行标记区分,10、11、12、13.......
第五步:(此处是对程序的另行说明,是整个程序核心点)
对标号点进行路径追溯操作
先说本程序使用的方法:
先从角点进行周围检测,找边缘
以找到的边界点为中心向四周寻找边界(角点)
在寻找路径的过程中把路径的点记录下来。
防止寻找重复,就是在寻找之后把路径清除。
找的结果就是10和11这两个点
再说说我自己的想法:
我的想法比本程序的稍微简单一些,思路基本是相同的。就是直接以角点为基础向四周寻找角点。
第六步:
把得到的角点和路径存放到一个“图”中,这样使用的时候就非常方便了
PS:图我还没有学过,这里不过多说明。
上代码:
1 from cv2 import * 2 import numpy as np 3 import matplotlib.pyplot as plt 4 import networkx as nx 5 from numba import jit 6 7 @jit# get neighbors d index 8 #得到领域的坐标(如果是二维直接算可以,但如果是三维就不一样了) 9 #这里是作者的改进适合多维图像,每句话的含义我没有弄懂 10 def neighbors(shape):#传进来的是二值图,所以维度是2 11 '''答案是:[-1921 -1920 -1919 -1 1 1919 1920 1921]''' 12 dim = len(shape) 13 block = np.ones([3]*dim) #维度为dim,数据3X3 14 block[tuple([1]*dim)] = 0 #取[1,1]位置为0 15 idx = np.where(block>0) #非零区域的位置信息 16 idx = np.array(idx, dtype=np.uint8).T #idx转换成矩阵,并且转置 17 idx = np.array(idx-[1]*dim) #全部值减一 18 acc = np.cumprod((1,)+shape[::-1][:-1]) #叠乘 19 return np.dot(idx, acc[::-1]) #点乘 20 21 @jit # my mark 22 #标记骨架图每个点的含义 23 #0:不是骨架 24 #1:骨架路径上的点 25 #2:端点或者角点 26 def mark(img): # mark the array use (0, 1, 2) 27 nbs = neighbors(img.shape) 28 img = img.ravel() 29 for p in range(len(img)): 30 if img[p]==0:continue 31 s = 0 32 '''判断中心点领域八个点的情况''' 33 for dp in nbs: 34 if img[p+dp]!=0:s+=1 35 if s==2:img[p]=1 36 else:img[p]=2 37 38 39 @jit # trans index to r, c... 40 # 坐标转换,图像被转换成一行处理,现在得把关键点转换回来(x,y) 41 def idx2rc(idx, acc): 42 rst = np.zeros((len(idx), len(acc)), dtype=np.int16) 43 for i in range(len(idx)): 44 for j in range(len(acc)): 45 rst[i, j] = idx[i] // acc[j] 46 idx[i] -= rst[i, j] * acc[j] 47 return rst 48 49 50 @jit # fill a node (may be two or more points) 51 # 标记节点,把节点打上不同的编码img.copy()=(2,2,2,2.....)----->>>>(10,11,12,13......) 52 # 并且把行坐标转换成(x,y)类型的坐标存放 53 def fill(img, p, num, nbs, acc, buf): 54 back = img[p] # 位置点的值存放 55 img[p] = num # 计数值 56 buf[0] = p # 位置点存放 57 cur = 0; 58 s = 1; 59 while True: 60 p = buf[cur] # 61 for dp in nbs: 62 cp = p + dp 63 if img[cp] == back: 64 img[cp] = num 65 buf[s] = cp 66 s += 1 67 cur += 1 68 if cur == s: break 69 return idx2rc(buf[:s], acc) 70 71 72 @jit # trace the edge and use a buffer, then buf.copy, if use [] numba not works 73 #路径跟踪找边缘 74 def trace(img, p, nbs, acc, buf): 75 '''这里的c1和c2是找的一条路径的两个端点,存放的端点标记>=10''' 76 c1 = 0 77 c2 = 0 78 newp = 0 #更新之后的位置 79 cur = 0 #计数,用来计算路径上点的数量 80 81 b = p==97625 #b = (p==97625) 82 #while找的是一条路径(具体说明请看附图) 83 while True: 84 buf[cur] = p#位置存储 85 img[p] = 0 #当前位置置0(搜索过得路径置0,防止重复搜索) 86 cur += 1 #光标加一(移动一个位置) 87 for dp in nbs: 88 cp = p + dp 89 if img[cp] >= 10:#判断是否为端点 90 if c1==0: c1=img[cp]#找的第一个端点() 91 else: c2 = img[cp]# 92 if img[cp] == 1: 93 newp = cp 94 p = newp 95 if c2!=0:break 96 return (c1-10, c2-10, idx2rc(buf[:cur], acc)) 97 98 99 @jit # parse the image then get the nodes and edges 100 # 找节点和边缘 101 def parse_struc(img): 102 nbs = neighbors(img.shape) 103 acc = np.cumprod((1,) + img.shape[::-1][:-1])[::-1] # (1,img.shape[0]) 104 img = img.ravel() 105 pts = np.array(np.where(img == 2))[0] 106 buf = np.zeros(20, dtype=np.int64) 107 num = 10 108 nodes = [] 109 for p in pts: 110 if img[p] == 2: 111 nds = fill(img, p, num, nbs, acc, buf) 112 num += 1 113 nodes.append(nds) 114 115 buf = np.zeros(10000, dtype=np.int64) 116 edges = [] 117 # 这个for循环是找一个点上对应的多个路径 118 for p in pts: 119 for dp in nbs: 120 if img[p + dp] == 1: # 找路径,img路径点值为1 121 edge = trace(img, p + dp, nbs, acc, buf) 122 edges.append(edge) 123 return nodes, edges 124 125 126 # use nodes and edges build a networkx graph 127 #将建立的节点和路径存放到NX的图中 128 def build_graph(nodes, edges): 129 graph = nx.Graph() 130 for i in range(len(nodes)): 131 graph.add_node(i, pts=nodes[i], o=nodes[i].mean(axis=0)) 132 for s,e,pts in edges: 133 l = np.linalg.norm(pts[1:]-pts[:-1], axis=1).sum() 134 graph.add_edge(s,e, pts=pts, weight=l) 135 return graph 136 #建立一个图 137 def build_sknw(ske): 138 mark(ske) 139 nodes, edges = parse_struc(ske.copy()) 140 return build_graph(nodes, edges) 141 #画出一个'图' 142 # draw the graph 143 def draw_graph(img, graph): 144 for idx in graph.nodes(): 145 pts = graph.node[idx]['pts'] 146 img[pts[:,0], pts[:,1]] = 255 147 for (s, e) in graph.edges(): 148 pts = graph[s][e]['pts'] 149 img[pts[:,0], pts[:,1]] = 128 150 151 array = [0,0,1,1,0,0,1,1,1,1,0,1,1,1,0,1, 152 1,1,0,0,1,1,1,1,0,0,0,0,0,0,0,1, 153 0,0,1,1,0,0,1,1,1,1,0,1,1,1,0,1, 154 1,1,0,0,1,1,1,1,0,0,0,0,0,0,0,1, 155 1,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0, 156 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 157 1,1,0,0,1,1,0,0,1,1,0,1,1,1,0,1, 158 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 159 0,0,1,1,0,0,1,1,1,1,0,1,1,1,0,1, 160 1,1,0,0,1,1,1,1,0,0,0,0,0,0,0,1, 161 0,0,1,1,0,0,1,1,1,1,0,1,1,1,0,1, 162 1,1,0,0,1,1,1,1,0,0,0,0,0,0,0,0, 163 1,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0, 164 1,1,0,0,1,1,1,1,0,0,0,0,0,0,0,0, 165 1,1,0,0,1,1,0,0,1,1,0,1,1,1,0,0, 166 1,1,0,0,1,1,1,0,1,1,0,0,1,0,0,0] 167 168 @jit#横向细化 169 def HThin(image,array): 170 flag = True #如果该点被删除,跳过下一个点 171 midImage = image.copy() 172 for i in range(image.shape[0]-2): 173 for j in range(image.shape[1]-2): 174 if flag == False: 175 flag = True 176 else: 177 if image[i+1,j+1] == 0 and (image[i+1,j] != 0 or image[i+1,j+2] != 0):#左右都为黑点不处理 178 a = [0]*9 #定义list[9] 179 for k in range(3): 180 for l in range(3): 181 a[k*3+l] = 0 if midImage[i+k,j+l]==0 else 1 182 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 183 midImage[i+1,j+1] = array[sum]*255 184 return midImage 185 @jit#纵向细化 186 def VThin(image,array): 187 flag = True #如果该点被删除,跳过下一个点 188 midImage = image.copy() 189 for i in range(image.shape[1]-2): 190 for j in range(image.shape[0]-2): 191 if flag == False: 192 flag = True 193 else: 194 if image[j+1,i+1] == 0 and (image[j,i+1] != 0 or image[j+2,i+1] != 0):#左右都为黑点不处理 195 a = [0]*9 #定义list[9] 196 for k in range(3): 197 for l in range(3): 198 a[k*3+l] = 0 if midImage[j+k,i+l]==0 else 1 199 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 200 midImage[j+1,i+1] = array[sum]*255 201 return midImage 202 @jit#横向和纵向合并 203 def wjy_Bone(inputImage,num=100): 204 '''改进算法''' 205 for i in range(num): 206 inputImage = VThin(HThin(inputImage,array),array) 207 return inputImage 208 @jit#骨架提取 209 def drawBone(inputImage): 210 #showImage = np.zeros((inputImage.shape[0],inputImage.shape[1],3),np.uint8) 211 showImage = inputImage.copy() 212 showImage = cvtColor(showImage,COLOR_GRAY2BGR) 213 for i in range(inputImage.shape[0]-2): 214 for j in range(inputImage.shape[1]-2): 215 if inputImage[i+1,j+1] > 0 : continue 216 flag = -1 217 for J in range(3): 218 for K in range(3): 219 if inputImage[i+J,j+K] == 0: 220 flag += 1 221 if(flag==1 or flag>=3): 222 showImage = circle(showImage,(j+1,i+1), 5, (0,0,255), -1) 223 showImage[i+1,j+1,0] = 255 224 showImage[i+1,j+1,1] = 0 225 showImage[i+1,j+1,2] = 0 226 return showImage 227 228 if __name__ == '__main__': 229 img = imread('5.jpg') 230 img = cvtColor(img,COLOR_BGR2GRAY) 231 ret2, img = threshold(img, 0, 255, THRESH_BINARY | THRESH_OTSU) 232 #wjy2 = drawBone(wjy_Bone(img, num = 500)) 233 img = wjy_Bone(img,num=500).astype(np.uint16)#细化之后从uint8转换到uint16 234 img = np.where(img > 0, np.uint16(img/255),0) 235 img = np.where(img > 0, img - 1 , img + 1) 236 graph = build_sknw(img)#这里传进来的图像数值为(0 or 1) ,类型是uint16 237 238 plt.imshow(img, cmap='gray') 239 for (s, e) in graph.edges(): 240 ps = graph[s][e]['pts'] 241 plt.plot(ps[:, 1], ps[:, 0], 'green') 242 243 node, nodes = graph.node, graph.nodes() 244 ps = np.array([node[i]['o'] for i in nodes]) 245 plt.plot(ps[:, 1], ps[:, 0], 'r.') 246 plt.title('Build Graph') 247 plt.show()
不是太完美的图,应该是细化出问题了
推广一下大佬的开源项目:http://www.imagepy.org/开源的精神值得我们学习,问问题的时候尽量发个红包,不在多少在心意。
参考:
http://blog.csdn.net/sunny2038/article/details/9080047 (基础操作)
http://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_core/py_basic_ops/py_basic_ops.html#basic-ops (英文文档)