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寻找最外围轮廓没写)

一些重要定义图一 边界关系示例

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)两种条件的分别为外轮廓和孔轮廓起始点图二 开始点

找到起始点后,根据上一个轮廓的编号(LNBD)判断父轮廓。看table1,如果当前的轮廓与LNBD代表的轮廓是同一个类型的轮廓,则当前轮廓的父轮廓是LNBD代表的轮廓的父轮廓。

最后进行border following找到该轮廓的所有点,参考APPENDIX1:

定义输入图片

equation?tex=F+%3D+f%5Cleft%5C%7B+i%2Cj+%5Cright%5C%7D ,初始化NBD为1,LNBD为1.并且每一行扫描开始,LNBD重设为1

(1)情况一:如果

equation?tex=f%28i%2Cj%29+%3D1 并且

equation?tex=f%28i%2Cj-1%29+%3D0,则(i,j)是外轮廓的起始点,NBD+1,

equation?tex=%28i2%2Cj2%29+%3D+%28i%2Cj-1%29.

情况二:如果

equation?tex=f%28i%2Cj%29+%3E%3D1 并且

equation?tex=f%28i%2Cj%2B1%29+%3D0, 则(i,j)是孔轮廓的起始点,NBD+1,

equation?tex=%28i2%2Cj2%29+%3D+%28i%2Cj%2B1%29.

其他情况跳到(4)

(2)基于轮廓种类决定父轮廓

(3.1)从

equation?tex=%28i2%2Cj2%29开始,以

equation?tex=%28i%2Cj%29 为中心顺时针找到一个非零点为

equation?tex=%28i1%2Cj1%29 ,如果没有吧-NBD赋值给

equation?tex=f%28i%2Cj%29 ,跳到步骤(4)

(3.2)

equation?tex=%28i2%2Cj2%29+%3D+%28i1%2Cj1%29

equation?tex=%28i3%2Cj3%29+%3D+%28i%2Cj%29.

(3.3)从

equation?tex=%28i2%2Cj2%29开始,以

equation?tex=%28i3%2Cj3%29 为中心逆时针找到一个非零点为

equation?tex=%28i4%2Cj4%29

(3.4)根据

equation?tex=%28i3%2Cj3%29,即当前扫描到的pixel,改变

equation?tex=f%28i%2Cj%29的值,如果

equation?tex=f%28i3%2Cj3%2B1%29%3D0 ,则

equation?tex=f%28i3%2Cj3%29+%3D+-NBD, 如果

equation?tex=f%28i3%2Cj3%2B1%29%5Cne0 (可能为正或者负数)并且

equation?tex=f%28i3%2Cj3%29+%3D+1 ,则

equation?tex=f%28i3%2Cj3%29+%3D+NBD, 其他情况不改变值

(3.5)如果

equation?tex=%28i4+%2Cj4%29+%3D+%28i%2Cj%29+and+%28i3%2Cj3%29+%3D+%28i1%2Cj1%29 代表回到了原点,跳到(4)。否则,

equation?tex=%28i3%2Cj3%29+%3D+%28i4%2Cj4%29

equation?tex=%28i2%2Cj2%29+%3D+%28i3%2Cj3%29.

(4)如果

equation?tex=f%28i%2Cj%29%5Cne1 那么

equation?tex=LNBD+%3D+%5Cleft%7C+f%28i%2Cj%29+%5Cright%7C, 从(i,j+1)开始继续扫描直到最右下角的像素

整个算法通俗来说就是不断更新当前点(i3,j3),然后绕着该点逆时针旋转找下一点并且不断更新像素值的过程,下面以文章中给的例子讲解

从图三看,第一次扫描到(a)中的打圈的1,根据(3.4),改变像素为2,然后逆时针寻找,发现到了左边边缘的2根据(3.4)应该是-2。这样下去结果不对啊!

后来想了一段时间,这里对像素左边和右边同时为0的情况,应该做特殊处理。因为轮廓是逆时针寻找,那么可以通过寻找的方位判断该赋值NBD还是-NBD,如果是从上往下扫的,则为NBD,如果是从下往上扫描的,则赋值-NBD。(具体实现可以参考代码)

修正后最后结果和文章一致了!有兴趣的朋友可以看下代码~

结果图,第一个index为轮廓编号,1为frame边缘,接着是son子轮廓,parent父轮廓,start_point轮廓开始的index,contour_type轮廓类型是否为孔结果图

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

"""Created on Wed May 27 15:01:45 2020@author: 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.com4b70deef7_ipico.jpg

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值