大家好,欢迎收听小木的第六讲,第六讲我们主要讲的内容是轮廓检测。说道轮廓检测。轮廓怎么检测呢,那么我们就要引入一个轮廓检测算法:
这个算法是日本的铃木桑发明的。这位日本人写了一篇论文,叫做:
Topological structural analysis of digitized binary images by border following,
百度文库上面有下载地址:
Topological structural analysis of digitized binary images by border following - 百度文库
说道百度文库,我要吐槽一下,我觉得CNKI上面的文章都不如百度文库上面网友写的好。今天吐槽的话题太多了,首先说一下天朝不合理的科研评优制度。现在考一个博士,比如哈工大、同济,大连理工等学校竟然要一篇SCI,同济甚至是2篇。我就想请问你们是在TMD考老师呢还是在考学生?你们同济可能有设备,有经费。但是其它普通学校有吗?你以为学生是巧妇?巧妇都难做无米之炊,你们要求真是无理。我就请问你们985学校的筛选制度,真的能筛选出NB的学生吗?这个升学坑爹。还有坑爹的是大家喜欢到知网去灌水,结果知网上面全是垃圾文,而且瞎乱引用,引用次数高的还不一定是好的。所以大家找一篇好文实在是太TMD难了。我就想请问,什么国家奖学金那几万块钱,真的这么重要么,就不能发几篇好文章上去吗?
说完这个,还得吐槽一下这个日本人铃木先生,铃木先生这篇文章是1985年写的,也就是说我们现在是2018年,我们足足落后了小日本20多年。美帝就更不用说了。所以我想对现在的年轻人说,我们历史必须铭记,但是不是无脑黑,也不能无脑当HJ。这点应该学习罗永浩老师,把小日本的好东西都学到手,这个才是最重要的。不能学洁洁良,更不能学那些水军。如果认为我说的对,你们必须得转发我的文章!
好了,废话说完了,进入正题。这个日本人发明了一种能够检测轮廓的算法,这个轮廓检测是在一张只有1和只有0的二值图片中执行的,而这个算法定义了几个概念:
(1)框架:框架就是指图片的第一列、最后一列、第一行、最后一行像素,这四个长条组成的一个矩形框框,我们这些点是黑点,也就是0。
(2)背景:除了框架之外,和框架属于同一层级的像素点,这些点也是黑点,也就是0。
(3)外轮廓:外轮廓就是指包裹了一堆白点的最外围的白点。
(4)内轮廓:内轮廓是指在包裹了一堆白点的最内部的白点。
用一张图片表示会很清晰:
在这张图片中,S1是背景,也是框架层。S2是包裹的一堆白点,这堆白点的最外层和框架层紧密相连,B1就是S2的外轮廓,B2就是S2的内轮廓。
那么我们怎么用计算机判断一张图片上的点是外轮廓还是内轮廓呢?
那么就得进行如下的步骤:
(1)首先,我们设定外部框架的边界值NBD为1。
(2)接着忽略最外层的框架,然后从图片的第二行的最左侧开始,判断:
如果这个数字是1,并且它的左侧的像素是0,那么我们令(i,j)为这个像素,然后令(i2,j2)=(i,j-1)为它左侧的像素(如果是第一次运算,那么坐标就是(2,2),它要是1,(2,1)是0,那么(i,j)=(2,2)、(i2,j2)=(i,j-1)=(2,1))。然后我们令NBD=NBD+1,并且认为这个点是外轮廓点。
如果这个数字是大于等于1的,并且它的右侧像素是0,那么我们令(i,j)为这个像素,然后令(i2,j2)=(i,j+1)为它右侧的像素。(如果是第一次运算,那么坐标就是(2,2),它要是1,(2,3)是0,那么那么(i,j)=(2,2)、(i2,j2)=(i,j+1)=(2,3)),并且认为这个点是内轮廓点。
如果是其它的情况,那么,令直接跳到最后一步。
(3)下一步是判断这个点到底是内轮廓还是外轮廓,我们用这个表格来判断:
这里面B’表示的是上一个按照我们这个步骤走的点是什么轮廓。B表示我们当前的点是什么轮廓。如果这是第一次,没有上一次的点,那么直接认为是外轮廓就好了。
(4)从点(i,j)开始,它的邻近地方有8个点(3*3)的格子之内,然后我们以(i2,j2)点为起始点,然后顺时针的沿着这8个点移动,一直走走走走,唉,发现了一个不为0的像素点,并且令这个点为(i1,j1)。如果这8个点都为0的话,令中心点(i,j)为-NBD,然后直接跳到最后一步。
(5)如果(i1,j1)存在的话,那么我们令(i2,j2)=(i1,j1),(i3,j3)=(i,j)。
(6)然后我们从点(i3,j3)开始,以(i2,j2)为起始点,然后逆时针移动遍历(i,j)邻近地方的8个点。找到第一个不为0的点((i2,j2)这个点不算)。然后令它为(i4,j4)。注意这里肯定不会存在没有不为0点的情况,因为(i3,j3)=(i,j),上一次循环都循环一次了,没有早退出了。
(7)然后我们判断:如果(i3,j3)前面的点(i3,j3+1)为0的话,那么我们把(i3,j3)这个点的像素值赋予-NBD。
如果(i3,j3)点前面的点不为0,且(i3,j3)这个点的像素为1的话,那么把(i3,j3)的像素值赋予NBD。
如果是其它情况的话,这个值保持不变。
(8)判断,如果(i4,j4)=(i,j)也就是我们最开始的那个值,且(i3,j3)=(i1,j1),那么我们跳到最后一步。如果要不符合条件,我们返回到(6)继续计算直到满足条件。
(9)我们回到我们最开始选择的那个点,也就是(i,j)然后我们往右边移动一格,如果已经到达末尾了,换到下一行的左边。如果我们于(i,j)那点的像素不为1的话,我们令LNBD等于(i,j)那点的像素的绝对值。
这九步,我们就把轮廓计算完毕了。在铃木桑的论文中,它举了一个例子来说明这个算法,如下所示:
这个是它最后得到的结果,但是我有点不信,下面我按照它同样的算法,自己手算了一遍(我算错了,这部分直接略过,正确的推导过程在文章最底部!!!!):
我的最后结果是分为4部分,4个外轮廓,没有内轮廓,而铃木的是5部分,有一个内轮廓。我认为我的里面那个-3不是内轮廓,因为内轮廓应该是八方向都封闭起来的,而这两个-3只有2个方向是封闭,两个方向直接和背景连接。所以我认为是外轮廓。
谁对谁错我不好说,你们自己分析,然后对这两个人的案例对错评论一下,告诉我哈!
这样,我们的轮廓就检测完毕了。我们用OPENCV检测也非常的简单:
(1)首先我们导入一张图片。
(2)将图片二值化变成黑白图像
(3)运用轮廓搜索算法找出轮廓
(4)显示轮廓。
代码如下:
import cv2
import numpy as np
#导入图片
img = cv2.imread("D:/xiaomu/opencv6-1.jpg")
#显示图片
cv2.imshow("orgin",img)
#定义一个卷积核的尺寸
kernel = np.ones((3,3))
#导入图片,进行开闭运算去噪声
img=cv2.morphologyEx(img,cv2.MORPH_OPEN,kernel)
img=cv2.morphologyEx(img,cv2.MORPH_CLOSE,kernel)
#将图像进行二值化处理,小于10的为0,大于的255
ret, thresh = cv2.threshold(cv2.cvtColor(img.copy(), cv2.COLOR_BGR2GRAY), 10, 255, cv2.THRESH_BINARY)
#找出图片的轮廓
image, contours, hier = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
#绘制轮廓
cv2.drawContours(img, contours, -1, (255, 0, 0), 3)
#显示带轮廓的图片
cv2.imshow("contours", img)
#等待按键退出
cv2.waitKey()
cv2.destroyAllWindows()
结果:
下节课讲什么还没决定好,到时候再说哦!
过了5年了,我重新推导了一下,还是小日子论文里面的对,我是错的。下面是正确的推导过程:
———————————————
如果对我的课程感兴趣的话,欢迎关注小木希望学园-微信公众号:
mutianwei521
也可以扫描二维码哦!