zoukankan      html  css  js  c++  java
  • 《图像处理实例》 之 拓扑重建


    拓扑重建

    这是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  (英文文档)

  • 相关阅读:
    《c程序设计语言》读书笔记-5.8-天数和日期转换错误检查
    《c程序设计语言》读书笔记-5.6-指针重写getline等函数
    《c程序设计语言》读书笔记-5.5-指针实现strncpy,strncat,strncmp
    《c程序设计语言》读书笔记-5.4-指针实现strend
    《c程序设计语言》读书笔记-5.3-指针实现strcat
    《c程序设计语言》读书笔记-4.14-定义宏交换两个参数
    《c程序设计语言》读书笔记-4.13-递归版本reverse函数
    《c程序设计语言》读书笔记-4.12-递归整数转字符串
    递归调用顺序问题
    JavaScript跨站脚本攻击
  • 原文地址:https://www.cnblogs.com/wjy-lulu/p/7242788.html
Copyright © 2011-2022 走看看