轮廓提取算法理解,python实现

概述

本轮廓提取算法来自opencv,findContours()。
根据论文:

Satoshi Suzuki and others. Topological structural analysis of digitized binary images by border following. Computer Vision, Graphics, and Image Processing, 30(1):32–46, 1985.

轮廓的边界分为外边界和孔边界,这个暂时没有理清,看其他人的说法实现起来怪怪的。
此外轮廓还分为父轮廓和子轮廓,父轮廓包裹子轮廓,但似乎不要求父轮廓是一个完整的环

原理步骤

初始化
  • 假设输入图像为 f ( i , j ) f(i,j) f(i,j) i i i为行, j j j为列。
  • 边界序号为NBD,初始化为1,上一个边界序号为LNBD,同样初始化为1
  • 每次到达新的一行,需要将LNBD置1
  • 严格来说,需要先将图像向外扩展1个像素,填充为非轮廓的值,否则应当从点(1,1)开始(初始点为(0,0)),但是这样会导致最边缘的轮廓线没有识别
  • 计算过程中会将图中的值逐渐替换为轮廓标记
开始遍历全图

1.搜索轮廓起点

 当 f ( i , j ) = 0 f(i,j)=0 f(i,j)=0,无操作
 当 f ( i , j ) = 1 f(i,j)=1 f(i,j)=1,分为两种情况:

  • f ( i , j ) = 1 f(i,j)=1 f(i,j)=1 f ( i , j − 1 ) = 0 f(i,j-1)=0 f(i,j1)=0,则 i , j i,j i,j是外边界开始点,NBD+=1, ( i 2 , j 2 ) = ( i , j − 1 ) (i2,j2) = (i,j-1) (i2,j2)=(i,j1)

  • f ( i , j ) > = 1 f(i,j)>=1 f(i,j)>=1 f ( i , j + 1 ) = 0 f(i,j+1)=0 f(i,j+1)=0,则 i , j i,j i,j是孔边界开始点,NBD+=1, ( i 2 , j 2 ) = ( i , j + 1 ) (i2,j2) = (i,j+1) (i2,j2)=(i,j+1),且如果 f ( i , j ) > 1 f(i,j)>1 f(i,j)>1,说明是查找过的点,则 L N B D = f ( i , j ) LNBD = f(i,j) LNBD=f(i,j)

  • 根据下表判断当前边界的父边界:
    在这里插入图片描述
    实际上就是上一个边界类型和当前边界类型不同,则父是上一个,反之若类型相同,则父与上一个相同。

    注意,子边界只记录第一个子边界。

    关于轮廓(边界)的层级:hierarchy轮廓层级详解

    OPENCV中将轮廓信息记录为一串数组,由四个数描述,这四个数分别代表后一个轮廓的序号、前一个轮廓的序号、子轮廓的序号、父轮廓的序号,第一层轮廓固定为[-1,-1,1,-1]

  • 其它情况,到第4步

2.顺时针检查轮廓

( i , j ) (i,j) (i,j)为中心, ( i 2 , j 2 ) (i2,j2) (i2,j2)为起点,按顺时针方向查找 ( i , j ) (i,j) (i,j)的领域内是否存在非0像素点,若找到,则记为 ( i 1 , j 1 ) (i1,j1) (i1,j1)。否则令 f ( i , j ) = − N B D f(i,j)=-NBD f(i,j)=NBD,转到第4步

( i 2 , j 2 ) = ( i 1 , j 1 ) , ( i 3 , j 3 ) = ( i , j ) (i2,j2)=(i1,j1),(i3,j3)=(i,j) (i2,j2)=(i1,j1)(i3,j3)=(i,j)

在这里插入图片描述

先计算顺时针可能是为了防止触发后面步骤中

3.逆时针跟踪轮廓

( i 3 , j 3 ) (i3,j3) (i3,j3)为中心,逆时针方向, ( i 2 , j 2 ) (i2,j2) (i2,j2)的下一个点为起点,查找 ( i 3 , j 3 ) (i3,j3) (i3,j3)邻域内的非0像素点,记为 ( i 4 , j 4 ) (i4,j4) (i4,j4)
在这里插入图片描述

  • 如果检查过 ( i 3 , j 3 + 1 ) (i3,j3+1) (i3,j3+1),且该点是0,则 f ( i 3 , j 3 ) = − N B D f(i3,j3) = -NBD f(i3,j3)=NBD
  • 如果 ( i 3 , j 3 + 1 ) (i3,j3+1) (i3,j3+1)不是已经检查过的0像素点,则 f ( i 3 , j 3 ) = N B D f(i3,j3) = NBD f(i3,j3)=NBD
  • 其它情况不改变 f ( i 3 , j 3 ) f(i3,j3) f(i3,j3)

此时,如果 ( i 4 , j 4 ) = ( i , j ) (i4,j4)=(i,j) (i4,j4)=(i,j) ( i 3 , j 3 ) = ( i 1 , j 1 ) (i3,j3)=(i1,j1) (i3,j3)=(i1,j1),说明回到起点,转到第4步。否则令 ( i 2 , j 2 ) = ( i 3 , j 3 ) , ( i 3 , j 3 ) = ( i 4 , j 4 ) (i2,j2)=(i3,j3),(i3,j3)=(i4,j4) (i2,j2)=(i3,j3)(i3,j3)=(i4,j4),回到第3步开始循环
在这里插入图片描述

4.其它

如果 f ( i , j ) ! = 1 f(i,j)!=1 f(i,j)!=1,则 L N B D = ∣ f ( i , j ) ∣ LNBD=|f(i,j)| LNBD=f(i,j),然后继续遍历图像像素

注:以上步骤主要来自这篇文章:https://zhuanlan.zhihu.com/p/107257870

Python实现

下面代码可以运行,就是可能有缩进的问题

import cv2
import numpy as np
import matplotlib.pyplot as plt

img = cv2.imread('xxxxx')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
gray = (gray/255).astype(np.int)
plt.figure(3)
plt.imshow(gray,cmap=plt.cm.gray)
#graycp = gray.copy()
NBD = 1
sursize = 8 #8连通

hierarchy = [[-1,-1,-1,-1],[-1,-1,1,-1]] #第0个随便填了全-1

dir_8 = [[0,-1],[-1,-1],[-1,0],[-1,1],[0,1],[1,1],[1,0],[1,-1],[0,-1],[-1,-1],[-1,0],[-1,1],[0,1],[1,1],[1,0],[1,-1]]#8连通域的8个方向,重复一次

for i in range(1,gray.shape[0] - 1):
	LNBD = 1
	for j in range(1,gray.shape[1] - 1):
		if gray[i,j] == 0:# 0不在考虑范围内
			continue
		is_outline = 0
#第一步------------------------------------
		if gray[i,j] == 1 and gray[i,j-1] == 0:
			NBD = NBD + 1
			[i2,j2] = dir_8[0]
			dir2 = 0
			is_outline = 1
			
			#与上一个边界不同,那么父轮廓就是上一个边界
			if LNBD < 0:
				hierarchy = hierarchy + [[NBD + 1,NBD - 1, -1, -LNBD]]#后一个,前一个,子轮廓,父轮廓,外/孔,关于后一个和前一个,目前还不知道具体指什么
	            if hierarchy[-LNBD][2] == -1:
		            hierarchy[-LNBD][2] = NBD
	        else:
	        	hierarchy = hierarchy + [[NBD + 1,NBD - 1, -1, hierarchy[LNBD][3]]]
        elif gray[i,j]>=1 and gray[i,j+1] == 0:
        	NBD=NBD+1
        	[i2,j2] = dir_8[4]
        	dir2 = 4
        	is_outline = 2
			
			if LNBD > 0:
				hierarchy = hierarchy + [[NBD + 1,NBD - 1, -1, LNBD]]
	            if hierarchy[LNBD][2] == -1:
	            	hierarchy[LNBD][2] = NBD
	        else:
	        	hierarchy = hierarchy + [[NBD + 1,NBD - 1, -1, hierarchy[-LNBD][3]]] 

			if gray[i,j] > 1:
				LNBD = gray[i,j]
		
		if is_outline > 0:
			print('newOutline',[i,j])
			[i1,j1] = [i,j]
			[i3,j3] = [i,j]
			[i4,j4] = [i,j]
			dir2 = dir2
			get = 0
			breakwhile = 0

#第二步 ------------------------------------------------------
			for surround_i in range(1,8):
				if gray[dir_8[dir2 + surround_i][0] + i,dir_8[dir2 + surround_i][1] + j] != 0:
					[i1,j1] = [dir_8[dir2 + surround_i][0] + i,dir_8[dir2 + surround_i][1] + j]
					[i2,j2] = [i1,j1]
					[i3,j3] = [i,j]
					get = 1
					dir2 = dir2 + surround_i
					if dir2 < 7: #将下标往后推一个周期,为了做逆时针查找
						dir2 = dir2 + 8
					break

#第三步 ---------------------------------------------------
			if get == 0:   #没找到
				 gray[i,j] = -NBD
				 break
			else:
				while 1:
					neg = 0
					for surround_i2 in range(-1,-8,-1):
						#防止越界(严格说应该把图像边界向外扩展1个像素)
						if dir_8[dir2 + surround_i2][0] + i3 < 0 or  dir_8[dir2 + surround_i2][0] + i3 >= gray.shape[0] or dir_8[dir2 + surround_i2][1] + j3 < 0 or  dir_8[dir2 + surround_i2][1] + j3 >= gray.shape[1]:
							breakwhile = 1
							continue
						#检查[i,j+1]是否是已检查过的0值点
						if dir_8[dir2 + surround_i2]] == [0,1] and gray[dir_8[dir2 + surround_i2][0] + i3,dir_8[dir2 + surround_i2][1] + j3] == 0:
							neg = 1
							
						#查找[i3,j3]邻域,以i2,j2为起点
						if gray[dir_8[dir2 + surround_i2][0] + i3,dir_8[dir2 + surround_i2][1] + j3] != 0:
							[i4,j4] = [dir_8[dir2 + surround_i2][0] + i3,dir_8[dir2 + surround_i2][1] + j3]
							break
					print("first point",[i,j],"find",[i4,j4])
					#接触到边界
					if breakwhile == 1:
						break

					if neg == 1:
						gray[i3,j3] = -NBD

					elif gray[i3,j3] == 1:
						gray[i3,j3] = NBD
						
					if [i4,j4] == [i,j] and [i3,j3] == [i1,j1]:#回到了起点
						break
					else:
						dir2 = dir2 + surround_i2 + (int)(sursize/2) #保存[i3,j3]指向[i2,j2]的方向,也就是直接把找到的方向翻过来就可以
						if dir2 > 15:	#保证dir2不越界
							dir2 = dir2 - 8
						if dir2 < 7:
							dir2 = dir2 + 8
						[i2,j2]=[i3,j3]
						[i3,j3]=[i4,j4]#继续找

		if gray[i,j] != 1:
			LNBD = gray[i,j]  #考虑到父轮廓的判别,我没有取绝对值,但总觉得怪怪的,这里应该有坑,或者有别的方法

plt.figure(4)
plt.imshow(gray,cmap=plt.cm.gray)			

效果

原图
在这里插入图片描述
赋值结果
在这里插入图片描述14里面为啥有个15?感觉代码还是有问题

结果
在这里插入图片描述

目前代码的父子轮廓关系还比较混乱,暂时没有想到好的修复方向

  • 1
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
要在Python中提取轮廓,你可以按照以下步骤进行操作: 1. 首先,加载图像并将其转换为灰度图像。你可以使用OpenCV的`cv2.imread()`函数来加载图像,并使用`cv2.cvtColor()`函数将其转换为灰度图像。 2. 接下来,使用阈值方法将灰度图像二值化。你可以使用`cv2.threshold()`函数来将灰度图像转换为二进制图像。 3. 然后,使用`cv2.findContours()`函数来查找图像中的轮廓。该函数将返回包含轮廓点集的列表。 4. 如果需要,可以选择性地对轮廓进行一些处理,例如应用滤波器或调整轮廓形状。 5. 最后,可以使用`cv2.drawContours()`函数在原始图像上绘制轮廓。该函数将在指定颜色和线宽下绘制轮廓。 下面是一个示例代码,展示了如何在Python中进行轮廓提取: ``` import cv2 # 加载图像并转换为灰度图像 img = cv2.imread("image.png") gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 将灰度图像二值化 ret, binary = cv2.threshold(gray, 128, 255, cv2.THRESH_BINARY) # 查找轮廓 contours, hierarchy = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) # 在原始图像上绘制轮廓 cv2.drawContours(img, contours, -1, (0, 0, 255), 3) # 显示结果图像 cv2.imshow("result", img) cv2.waitKey(0) cv2.destroyAllWindows() ``` 这段代码加载了一张图像,将其转换为灰度图像,并将其二值化。然后,它使用`cv2.findContours()`函数查找轮廓,并使用`cv2.drawContours()`函数在原始图像上绘制轮廓。最后,它显示了结果图像。 希望这对你有帮助!<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [Python OpenCV提取物体轮廓](https://blog.csdn.net/wzw12315/article/details/120882364)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [轮廓提取算法理解python实现](https://blog.csdn.net/sinat_29018995/article/details/116697416)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

进不去

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值