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:
定义输入图片
,初始化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实现相关推荐
- python描述符(descriptor)、属性(property)、函数(类)装饰器(decorator )原理实例详解
2019独角兽企业重金招聘Python工程师标准>>> 1.前言 Python的描述符是接触到Python核心编程中一个比较难以理解的内容,自己在学习的过程中也遇到过很多的疑惑,通过 ...
- python中的函数、生成器的工作原理
1.python中函数的工作原理 def foo():bar()def bar():pass python的解释器,也就是python.exe(c编写)会用PyEval_EvalFramEx(c函数) ...
- python 生成器 原理_你知道python中的函数、生成器的工作原理吗?
1.python中函数的工作原理 python的解释器,也就是python.exe(c编写)会用PyEval_EvalFramEx(c函数)运行foo()函数 首先会创建一个栈帧(stack Fram ...
- python字典实现原理-哈希函数-解决哈希冲突方法
python字典实现原理-哈希函数-解决哈希冲突方法 参考文章: (1)python字典实现原理-哈希函数-解决哈希冲突方法 (2)https://www.cnblogs.com/guyannanfe ...
- python构造callable_Python callable内置函数原理解析
python内置函数 callable用于检查一个对象是否是可调用的,如果函数返回True,object 仍然可能调用失败:但如果返回 False,调用对象 object 绝对不会成功. 一.call ...
- 详解Python生成器函数和生成器对象的原理和用法
包含yield语句的函数可以用来创建生成器对象,这样的函数也称生成器函数.yield语句与return语句的作用相似,都是用来从函数中返回值.与return语句不同的是,return语句一旦执行会立刻 ...
- python random函数原理_Python random() 函数
描述 random() 方法返回随机生成的一个实数,它在[0,1)范围内. 语法 以下是 random() 方法的语法: import random random.random() 注意:random ...
- mysql 自定义函数实例_mysql自定义函数原理与用法实例分析
本文实例讲述了mysql自定义函数原理与用法.分享给大家供大家参考,具体如下: 本文内容: 什么是函数 函数的创建 函数的调用 函数的查看 函数的修改 函数的删除 首发日期:2018-04-18 什么 ...
- SAP UI5和Angular的函数防抖(Debounce)和函数节流(Throttle)实现原理介绍
这是Jerry 2021年的第 11 篇文章,也是汪子熙公众号总共第 282 篇原创文章. Jerry之前的文章 SAP UI5 OData谣言粉碎机:极短时间内发送两个Odata request, ...
- 创建的函数带有编译错误。_AST实现函数错误的自动上报(原理到实践)
来源:哈啰出行-杭州团队 (作者授权转载) https://segmentfault.com/a/1190000037630766 前言 之前有身边有人问我在错误监控中,如何能实现自动为函数自动添加错 ...
最新文章
- Linux命令查看服务器信息
- Windows Server 2016 部署Hyper-V
- 转 容器生态系统 (续) - 每天5分钟玩转容器技术(3)
- JVM的内存结构,Eden和Survivor比例;JVM中一次完整的GC流程,对象如何晋升到老年代,说说你知道的几种主要的JVM参数;CMS 常见参数解析;.你知道哪几种垃圾收集器,各自的优缺点
- java当数值超过byte时_java试题及答案
- python的内置函数string_Python错误:内置函数或方法对象没有属性“StringIO”
- 【PHP学习】—创建PHP文件(一)
- P1541 乌龟棋 线性dp
- 判断可逆素数的c语言程序,C语言可逆素数教程
- 新的vulkan的SDK很难下载
- quartz定时任务properties
- 投射式触摸屏自电容与互电容工作原理基础(未完待续)
- 化学计算机模拟计算,计算机化学与分子设计课件.ppt
- 阿里技术专家楚衡:架构制图的工具与方法论
- Winedit 下载第三方库
- 点击链接跳转到微信公众号关注页、微信关注链接
- 今日研究UCosiii,研究安富莱电子的示波器程序,
- 项目经理之项目经理的必备能力
- 小实践之网络打印机的设置(实际操作篇)
- 计算机设置鼠标关灯,鼠标灯怎么关?通过BIOS设置即可解决!
热门文章
- 异步操作之后让await后续的代码能够继续执行
- reboot 重启系统命令
- php前端代码隐藏,php – Yii2 htaccess – 如何隐藏前端/ web和后台/ web完全
- div在html中的好处,详解DIV+CSS布局的好处和意义
- python贪吃蛇源代码_python实现贪吃蛇游戏源码
- 基础连接已关闭解决办法_解决|罗技蓝牙键盘连接ipad后打不出字?
- java迭代遍历_JAVA集合中的迭代器的遍历
- JAVA集合系列(3):ArrayList扩容原理分析
- HTTP缓存机制在iOS中的应用和体现
- vue选项卡切换,某个组件缓存数据keep-alive demo