天天看點

python find函數實作原理_Opencv findcontours函數原理,以及python numpy實作

OpenCV Findcontours( ) 函數原理出自于該論文的算法:Topological Structural Analysis of Digitized Binary Images by Border Following

文章傳送門:http://pdf-s3.xuebalib.com:1262/1ftg5E69C3uX.pdf

最近讀了這篇論文并嘗試複現,并填了論文裡面沒提到的一個小坑,整理了一下算法論文和思路,并附上python代碼,如果有錯誤希望各位大佬批評指正(目前隻做了Algorithm1,Algorithm2尋找最外圍輪廓沒寫)

一些重要定義

python find函數實作原理_Opencv findcontours函數原理,以及python numpy實作

圖一 邊界關系示例

1,輪廓點(border point):如果一個像素1在4-或者8-鄰域找到一個像素為0的點,為一個輪廓點,如上圖的B1,B2,B3,B4,其中陰影部分為1,白色部分為0

2,連通區域的環繞(surroundness among connected components),對于兩個相鄰的連通區域S1和S2,如果對于S1上任意一個點的4個方向,都能達到S2,那麼S2b環繞S1

3,關于外輪廓(outer border)和孔輪廓(hole border),外輪廓是像素為1連通域内像素為0連通域環繞的輪廓點(如圖一B4),孔輪廓是像素為0的連通區域被像素為1的連通區域環繞的輪廓點(如圖一 B2)。

4,父輪廓(parent border),定義了層級關系,假設有像素為1的連通區域S1和像素為0的連通區域S2,并且S2環繞S1

(1)外輪廓S1的父輪廓為環繞S2的值為1的像素,如B3的父輪廓為B2

(2)如果S2是背景,父輪廓為frame 如B4父輪廓為frame

輪廓掃描

開始點(starting point):文章中掃描的方式為從左到右,從上到下的順序,當找到是邊界起始點的時候,根據下圖判斷輪廓類型。如圖二,滿足(a),(b)兩種條件的分别為外輪廓和孔輪廓起始點

python find函數實作原理_Opencv findcontours函數原理,以及python numpy實作

圖二 開始點

找到起始點後,根據上一個輪廓的編号(LNBD)判斷父輪廓。看table1,如果目前的輪廓與LNBD代表的輪廓是同一個類型的輪廓,則目前輪廓的父輪廓是LNBD代表的輪廓的父輪廓。

python find函數實作原理_Opencv findcontours函數原理,以及python numpy實作

最後進行border following找到該輪廓的所有點,參考APPENDIX1:

定義輸入圖檔

python find函數實作原理_Opencv findcontours函數原理,以及python numpy實作

,初始化NBD為1,LNBD為1.并且每一行掃描開始,LNBD重設為1

(1)情況一:如果

python find函數實作原理_Opencv findcontours函數原理,以及python numpy實作

并且

python find函數實作原理_Opencv findcontours函數原理,以及python numpy實作

,則(i,j)是外輪廓的起始點,NBD+1,

python find函數實作原理_Opencv findcontours函數原理,以及python numpy實作

.

情況二:如果

python find函數實作原理_Opencv findcontours函數原理,以及python numpy實作

并且

python find函數實作原理_Opencv findcontours函數原理,以及python numpy實作

, 則(i,j)是孔輪廓的起始點,NBD+1,

python find函數實作原理_Opencv findcontours函數原理,以及python numpy實作

.

其他情況跳到(4)

(2)基于輪廓種類決定父輪廓

(3.1)從

python find函數實作原理_Opencv findcontours函數原理,以及python numpy實作

開始,以

python find函數實作原理_Opencv findcontours函數原理,以及python numpy實作

為中心順時針找到一個非零點為

python find函數實作原理_Opencv findcontours函數原理,以及python numpy實作

,如果沒有吧-NBD指派給

python find函數實作原理_Opencv findcontours函數原理,以及python numpy實作

,跳到步驟(4)

(3.2)

python find函數實作原理_Opencv findcontours函數原理,以及python numpy實作

python find函數實作原理_Opencv findcontours函數原理,以及python numpy實作

.

(3.3)從

python find函數實作原理_Opencv findcontours函數原理,以及python numpy實作

開始,以

python find函數實作原理_Opencv findcontours函數原理,以及python numpy實作

為中心逆時針找到一個非零點為

python find函數實作原理_Opencv findcontours函數原理,以及python numpy實作

(3.4)根據

python find函數實作原理_Opencv findcontours函數原理,以及python numpy實作

,即目前掃描到的pixel,改變

python find函數實作原理_Opencv findcontours函數原理,以及python numpy實作

的值,如果

python find函數實作原理_Opencv findcontours函數原理,以及python numpy實作

,則

python find函數實作原理_Opencv findcontours函數原理,以及python numpy實作

, 如果

python find函數實作原理_Opencv findcontours函數原理,以及python numpy實作

(可能為正或者負數)并且

python find函數實作原理_Opencv findcontours函數原理,以及python numpy實作

,則

python find函數實作原理_Opencv findcontours函數原理,以及python numpy實作

, 其他情況不改變值

(3.5)如果

python find函數實作原理_Opencv findcontours函數原理,以及python numpy實作

代表回到了原點,跳到(4)。否則,

python find函數實作原理_Opencv findcontours函數原理,以及python numpy實作

python find函數實作原理_Opencv findcontours函數原理,以及python numpy實作

.

(4)如果

python find函數實作原理_Opencv findcontours函數原理,以及python numpy實作

那麼

python find函數實作原理_Opencv findcontours函數原理,以及python numpy實作

, 從(i,j+1)開始繼續掃描直到最右下角的像素

整個算法通俗來說就是不斷更新目前點(i3,j3),然後繞着該點逆時針旋轉找下一點并且不斷更新像素值的過程,下面以文章中給的例子講解

python find函數實作原理_Opencv findcontours函數原理,以及python numpy實作

從圖三看,第一次掃描到(a)中的打圈的1,根據(3.4),改變像素為2,然後逆時針尋找,發現到了左邊邊緣的2根據(3.4)應該是-2。這樣下去結果不對啊!

後來想了一段時間,這裡對像素左邊和右邊同時為0的情況,應該做特殊處理。因為輪廓是逆時針尋找,那麼可以通過尋找的方位判斷該指派NBD還是-NBD,如果是從上往下掃的,則為NBD,如果是從下往上掃描的,則指派-NBD。(具體實作可以參考代碼)

修正後最後結果和文章一緻了!有興趣的朋友可以看下代碼~

結果圖,第一個index為輪廓編号,1為frame邊緣,接着是son子輪廓,parent父輪廓,start_point輪廓開始的index,contour_type輪廓類型是否為孔

python find函數實作原理_Opencv findcontours函數原理,以及python numpy實作

結果圖

# -*- coding: utf-8 -*-

"""Created on Wed May 27 15:01:45 [email protected]: 73766"""

import matplotlib.pyplot as plt

import numpy as np

#import cv2

#class Contour:

# def __init__(self,parent,cur_num,contour_type):

# self.parent = parent

# self.contour_num = cur_num

# self.contour_type = contour_type #Hole/Outer

class FindContours:

def __init__(self):

self.grid = np.array([[1,1,1,1,1,1,1,0,0],

[1,0,0,1,0,0,1,0,1],

[1,0,0,1,0,0,1,0,0],

[1,1,1,1,1,1,1,0,0]])

self.reset()

def reset(self):

self.grid = np.pad(self.grid, ((1, 1), (1, 1)), 'constant', constant_values=0)

self.LNBD = 1

self.NBD = 1

self.Disp_with_number = True

self.MAX_BODER_NUMBER = self.grid.shape[0]*self.grid.shape[1]

self.contours_dict = {}

self.contours_dict[1] = self.Contour(-1,"Hole")

def Contour(self,parent,contour_type,start_point = [-1,-1]):

contour = {"parent":parent,

"contour_type":contour_type,

"son":[],

"start_point":start_point}#Hole/Outer

return contour

def load_map_from_array(self,grid):

self.grid = grid.copy().astype("int32")

self.reset()

def trans_number_to_char(self,num):

if self.Disp_with_number:

return str(num)

if num >1:

return chr(63 + num)

if num <0:

return chr(95 - num)

else:

return str(num)

'''display gridd '''

def disp_grid(self):

for i in range(self.grid.shape[0]):

num = '\033[0;37m' + '['

print(num,end = ' ')

for j in range(self.grid.shape[1]):

if self.grid[i][j] == 0:

num = '\033[0;37m' + self.trans_number_to_char(self.grid[i][j])

print(num,end = ' ')

else:

num = '\033[1;31m' + self.trans_number_to_char(self.grid[i][j])

print(num,end = ' ')

num = '\033[0;37m' + ']'

print(num)

print("\033[0;37m")

def find_neighbor(self,center,start,clock_wise = 1):

weight = -1

if clock_wise == 1:

weight = 1

#direction = np.array([[1,0],[0,-1],[0,-1],[-1,0],[-1,0],[0,1],[0,1]])

neighbors = np.array([[0,0],[0,1],[0,2],[1,2],[2,2],[2,1],[2,0],[1,0]])

indexs = np.array([[0,1,2],

[7,9,3],

[6,5,4]])

#print(center,start)

start_ind = indexs[start[0] - center[0]+1][start[1] - center[1]+1]

# print(start_ind)

for i in range(1,len(neighbors)+1):

cur_ind = (start_ind + i*weight+8)%8

#print(cur_ind)

x = neighbors[cur_ind][0] + center[0] - 1

y = neighbors[cur_ind][1] + center[1] - 1

# grid[x][y] = a

# a+=1

if self.grid[x][y] != 0:

return [x,y]

return [-1,-1]

def board_follow(self,center_p,start_p,mode):

ij = center_p

ij2 = start_p

ij1 = self.find_neighbor(ij,ij2,1)

x = ij1[0]

y = ij1[1]

if ij1 == [-1,-1]:

self.grid[ij[0]][ij[1]] = -self.NBD

return

ij2 = ij1

ij3 = ij

for k in range(self.MAX_BODER_NUMBER):

#step 3.3

ij4 = self.find_neighbor(ij3,ij2,0)

x = ij3[0]

y = ij3[1]

if ij4[0] - ij2[0] <=0:

weight = -1

else:

weight = 1

if self.grid[x][y] < 0:

self.grid[x][y] = self.grid[x][y]

elif self.grid[x][y-1] == 0 and self.grid[x][y+1] ==0:

self.grid[x][y] = self.NBD*weight

elif self.grid[x][y+1]== 0:

self.grid[x][y] = -self.NBD

elif self.grid[x][y]== 1 and self.grid[x][y+1] != 0:

self.grid[x][y] = self.NBD

else:

self.grid[x][y] = self.grid[x][y]

if ij4 == ij and ij3 ==ij1:

return

ij2 = ij3

ij3 = ij4

def raster_scan(self):

#self.disp_grid()

for i in range(self.grid.shape[0]):

self.LNBD = 1

for j in range(self.grid.shape[1]):

if abs(self.grid[i][j]) > 1:

self.LNBD = abs(self.grid[i][j])

if self.grid[i][j] >= 1:

if self.grid[i][j] == 1 and self.grid[i][j-1] == 0:

self.NBD += 1

self.board_follow([i,j],[i,j-1],1)

border_type = "Outer"

elif self.grid[i][j] > 1 and self.grid[i][j+1] == 0:

border_type = "Hole"

#print(i,j)

self.NBD += 1

self.board_follow([i,j],[i,j+1],1)

#self.contours_dict[self.NBD] = self.Contour(self.LNBD,border_type)

#self.disp_grid()

else:

continue

parent = self.LNBD

if self.contours_dict[self.LNBD]["contour_type"] == border_type:

parent = self.contours_dict[self.LNBD]["parent"]

self.contours_dict[self.NBD] = self.Contour(parent,border_type,[i-1,j-1])

self.contours_dict[parent]["son"].append(self.NBD)

#print("NBD",self.NBD,"LNBD",self.LNBD)

self.grid = self.grid[1:-1,1:-1]

def main():

fc = FindContours()

fc.raster_scan()

fc.disp_grid()

print(fc.contours_dict)

grid1 = np.array([[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],

[0,0,0,1,0,0,0,0,0,0,0,0,0],

[0,0,1,1,1,1,1,1,1,0,0,0,0],

[0,0,1,0,0,1,0,0,0,1,1,0,0],

[0,0,1,0,0,1,0,0,1,0,0,0,0],

[0,0,1,0,0,1,0,0,1,0,0,0,0],

[0,0,1,1,1,1,1,1,1,0,0,0,0],

[0,0,0,1,0,0,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,0,0,0,0,0,0],])

fc.load_map_from_array(grid1)

fc.raster_scan()

fc.disp_grid()

print(fc.contours_dict)

#

# img1 = cv2.imread("D:\\datas\\luoxuan1.png")

# img = np.mean(np.float32(img1), axis=2)

# img[img<130] = 0

# img[img>0] = 1

# img = 1-img

#

# fc.load_map_from_array(img)

# fc.raster_scan()

# ret =abs(fc.grid)

# ret[ret<2] = 0

# ret[ret>0] = 1

# plt.figure()

# plt.imshow(img,"gray") # 顯示圖檔

# plt.axis('off') # 不顯示坐标軸

# plt.show()

# plt.figure()

# plt.imshow(ret,"gray") # 顯示圖檔

# plt.axis('off') # 不顯示坐标軸

# plt.show()

if __name__ == "__main__":

main()

歡迎大家關注我的專欄,也歡迎大家投稿,我們可以一起研究openCV的原理,分享知識共同進步!OpenCV算法原理了解和numpy實作​zhuanlan.zhihu.com

python find函數實作原理_Opencv findcontours函數原理,以及python numpy實作