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:

定义输入图片

,初始化NBD为1,LNBD为1.并且每一行扫描开始,LNBD重设为1

(1)情况一:如果

并且

,则(i,j)是外轮廓的起始点,NBD+1,

.

情况二:如果

并且

, 则(i,j)是孔轮廓的起始点,NBD+1,

.

其他情况跳到(4)

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

(3.1)从

开始,以

为中心顺时针找到一个非零点为

,如果没有吧-NBD赋值给

,跳到步骤(4)

(3.2)

.

(3.3)从

开始,以

为中心逆时针找到一个非零点为

(3.4)根据

,即当前扫描到的pixel,改变

的值,如果

,则

, 如果

(可能为正或者负数)并且

,则

, 其他情况不改变值

(3.5)如果

代表回到了原点,跳到(4)。否则,

.

(4)如果

那么

, 从(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.com

python find函数原理_Opencv findcontours函数原理,以及python numpy实现相关推荐

  1. python描述符(descriptor)、属性(property)、函数(类)装饰器(decorator )原理实例详解

    2019独角兽企业重金招聘Python工程师标准>>> 1.前言 Python的描述符是接触到Python核心编程中一个比较难以理解的内容,自己在学习的过程中也遇到过很多的疑惑,通过 ...

  2. python中的函数、生成器的工作原理

    1.python中函数的工作原理 def foo():bar()def bar():pass python的解释器,也就是python.exe(c编写)会用PyEval_EvalFramEx(c函数) ...

  3. python 生成器 原理_你知道python中的函数、生成器的工作原理吗?

    1.python中函数的工作原理 python的解释器,也就是python.exe(c编写)会用PyEval_EvalFramEx(c函数)运行foo()函数 首先会创建一个栈帧(stack Fram ...

  4. python字典实现原理-哈希函数-解决哈希冲突方法

    python字典实现原理-哈希函数-解决哈希冲突方法 参考文章: (1)python字典实现原理-哈希函数-解决哈希冲突方法 (2)https://www.cnblogs.com/guyannanfe ...

  5. python构造callable_Python callable内置函数原理解析

    python内置函数 callable用于检查一个对象是否是可调用的,如果函数返回True,object 仍然可能调用失败:但如果返回 False,调用对象 object 绝对不会成功. 一.call ...

  6. 详解Python生成器函数和生成器对象的原理和用法

    包含yield语句的函数可以用来创建生成器对象,这样的函数也称生成器函数.yield语句与return语句的作用相似,都是用来从函数中返回值.与return语句不同的是,return语句一旦执行会立刻 ...

  7. python random函数原理_Python random() 函数

    描述 random() 方法返回随机生成的一个实数,它在[0,1)范围内. 语法 以下是 random() 方法的语法: import random random.random() 注意:random ...

  8. mysql 自定义函数实例_mysql自定义函数原理与用法实例分析

    本文实例讲述了mysql自定义函数原理与用法.分享给大家供大家参考,具体如下: 本文内容: 什么是函数 函数的创建 函数的调用 函数的查看 函数的修改 函数的删除 首发日期:2018-04-18 什么 ...

  9. SAP UI5和Angular的函数防抖(Debounce)和函数节流(Throttle)实现原理介绍

    这是Jerry 2021年的第 11 篇文章,也是汪子熙公众号总共第 282 篇原创文章. Jerry之前的文章 SAP UI5 OData谣言粉碎机:极短时间内发送两个Odata request, ...

  10. 创建的函数带有编译错误。_AST实现函数错误的自动上报(原理到实践)

    来源:哈啰出行-杭州团队 (作者授权转载) https://segmentfault.com/a/1190000037630766 前言 之前有身边有人问我在错误监控中,如何能实现自动为函数自动添加错 ...

最新文章

  1. Linux命令查看服务器信息
  2. Windows Server 2016 部署Hyper-V
  3. 转 容器生态系统 (续) - 每天5分钟玩转容器技术(3)
  4. JVM的内存结构,Eden和Survivor比例;JVM中一次完整的GC流程,对象如何晋升到老年代,说说你知道的几种主要的JVM参数;CMS 常见参数解析;.你知道哪几种垃圾收集器,各自的优缺点
  5. java当数值超过byte时_java试题及答案
  6. python的内置函数string_Python错误:内置函数或方法对象没有属性“StringIO”
  7. 【PHP学习】—创建PHP文件(一)
  8. P1541 乌龟棋 线性dp
  9. 判断可逆素数的c语言程序,C语言可逆素数教程
  10. 新的vulkan的SDK很难下载
  11. quartz定时任务properties
  12. 投射式触摸屏自电容与互电容工作原理基础(未完待续)
  13. 化学计算机模拟计算,计算机化学与分子设计课件.ppt
  14. 阿里技术专家楚衡:架构制图的工具与方法论
  15. Winedit 下载第三方库
  16. 点击链接跳转到微信公众号关注页、微信关注链接
  17. 今日研究UCosiii,研究安富莱电子的示波器程序,
  18. 项目经理之项目经理的必备能力
  19. 小实践之网络打印机的设置(实际操作篇)
  20. 计算机设置鼠标关灯,鼠标灯怎么关?通过BIOS设置即可解决!

热门文章

  1. 异步操作之后让await后续的代码能够继续执行
  2. reboot 重启系统命令
  3. php前端代码隐藏,php – Yii2 htaccess – 如何隐藏前端/ web和后台/ web完全
  4. div在html中的好处,详解DIV+CSS布局的好处和意义
  5. python贪吃蛇源代码_python实现贪吃蛇游戏源码
  6. 基础连接已关闭解决办法_解决|罗技蓝牙键盘连接ipad后打不出字?
  7. java迭代遍历_JAVA集合中的迭代器的遍历
  8. JAVA集合系列(3):ArrayList扩容原理分析
  9. HTTP缓存机制在iOS中的应用和体现
  10. vue选项卡切换,某个组件缓存数据keep-alive demo