《圖像處理實例》 之 拓撲重建


 


 

拓撲重建

這是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  (英文文檔)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM