写点什么

Python OpenCV 轮廓检测与轮廓特征,加图像金字塔知识补充一点点

发布于: 19 分钟前

Python OpenCV 365 天学习计划,与橡皮擦一起进入图像领域吧。本篇博客是这个系列的第 48 篇。

学在前面

图像金字塔学习的时候,就要想着有个金字塔在你眼前,这个金字塔最底部是你的原图像(源图像)。


关于图像金字塔的基本知识,可以翻阅咱们之前的博客 Python OpenCV 之图像金字塔,高斯金字塔与拉普拉斯金字塔 学习。


学习高斯金字塔首先接触的概念是,向下采样方法,注意在金字塔,向下是缩小图片的含义,越靠近金字塔顶部,图像越小。相应的向上采样法,是方法图像。


好了,图像金字塔一点点的补充已经完毕。

轮廓检测与轮廓特征

轮廓检测的基础学习,请参照 Python OpenCV 基于图像边缘提取的轮廓发现函数 这篇博客,今天要补充的内容是在进行图像轮廓检测的时候,cv2.findContours 函数中轮廓检索模式参数,一般情况下建议使用 RETR_TREE 也就是检测所有轮廓。


查看一下测试代码吧。


import cv2 as cv
src = cv.imread("./t223.jpg")
gray = cv.cvtColor(src, cv.COLOR_BGR2GRAY)gaussian = cv.GaussianBlur(gray, (3, 3), 0)
edges = cv.Canny(gaussian, 70, 210)# 寻找轮廓contours, hierarchy = cv.findContours( edges, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
# 绘制轮廓cv.drawContours(src, contours, -1, (0, 0, 255), 1)
cv.imshow('src', src)cv.waitKey(0)
复制代码


寻找边缘使用的是 Canny 函数,找到所有边缘之后,通过 cv2.drawContours 函数绘制轮廓。



在学习 cv2.drawContours 函数的时候需要注意,有的博客中会提示使用该函数在原图绘制轮廓之后,会将原图进行覆盖,但是在橡皮擦使用的这个 opencv 版本中,并未出现上述情况,可能是不同版本导致的,注意下即可。

cv2.findContours 函数

该函数有两个返回值,contourshierarchy,其中一个是轮廓本身,另一个就是每条轮廓对应的属性。整体测试代码与图像使用下述内容。


import cv2 as cv
src = cv.imread("./t22331.jpg")
gray = cv.cvtColor(src, cv.COLOR_BGR2GRAY)gaussian = cv.GaussianBlur(gray, (3, 3), 0)
edges = cv.Canny(gaussian, 50, 150)# 寻找轮廓contours, hierarchy = cv.findContours( edges, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
# 绘制轮廓cv.drawContours(src, contours, -1, (0, 0, 255), 2)
cv.imshow('src', src)cv.waitKey(0)
复制代码


返回值 contours

通过下述代码,先确定一下该参数的基本数据。


# 轮廓详情print(type(contours))print(type(contours[0]))print(len(contours))
复制代码


得到的结果是:


<class 'list'><class 'numpy.ndarray'>46
复制代码


contours 参数是 list 类型、其中每一项都是 numpy.ndarray 类型,列表的长度等于轮廓数量。合计获取到 46 个轮廓,可以根据索引去绘制制定轮廓,例如:


# 绘制轮廓cv.drawContours(src, contours, 1, (0, 0, 255), 2)cv.drawContours(src, contours, 45, (0, 0, 255), 2)
复制代码


返回值 hierarchy 暂时略过,因为和接下来的内容关联性不强。

轮廓特征

这部分内容又叫做对象测量,在Python OpenCV 对象检测,图像处理取经之旅第 37 篇 进行了最基础的学习,本篇继续对其进行扩展。


咱们的首要目标是学会通过调用方法检测轮廓的不同特征,例如面积、周长、质心、边界框。


这部分在学习的时候,会用到大量的常见场景和数学知识,由于咱们现在还没有办法将这些内容直接应用到真实的案例中,所以数学相关知识与应用场景后置,先学习应用层,了解不同函数实现的结果即可。


接下来抛出不同的概念吧。

在这个阶段,只需要知道矩指的是图像的矩,它可以帮助我们计算图像的质心,面积等内容,如果你想提前学习一下数学相关的内容,我也帮你把目前橡皮擦能找到的几篇不错的博客贴了出来,你可以先学习一下,后面我们也会迭代学习到。



大佬们还是咱们努力学习的方向呀,相信不久就能再见面了。


以上内容你可以直接略过,进入正题


先在工具中输入如下代码,获取运行结果,方便后面的学习:


import cv2 as cv
src = cv.imread("./t22331.jpg")
gray = cv.cvtColor(src, cv.COLOR_BGR2GRAY)gaussian = cv.GaussianBlur(gray, (3, 3), 0)
edges = cv.Canny(gaussian, 50, 150)# 寻找轮廓contours, hierarchy = cv.findContours( edges, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
# 绘制轮廓cv.drawContours(src, contours, 1, (0, 0, 255), 2)cv.drawContours(src, contours, 45, (0, 0, 255), 2)# 选中的第一个轮廓cnt = contours[0]# 通过 moments 函数计算的所有矩值的字典M = cv.moments(cnt)print(M)
复制代码


运行之后展示的内容如下:


{'m00': 51.0, 'm10': 12277.833333333332, 'm01': 13028.166666666666, 'm20': 2956012.333333333, 'm11': 3136429.25, 'm02': 3328298.333333333, 'm30': 711743823.85, 'm21': 755128136.4666667, 'm12': 801262936.2, 'm03': 850328986.1500001, 'mu20': 224.26742919441313, 'mu11': 4.5642701531760395, 'mu02': 197.80991285433993, 'mu30': 23.924903512001038, 'mu21': 30.072836493171053, 'mu12': -27.494554918412177, 'mu03': -25.694735527038574, 'nu20': 0.0862235406360681, 'nu11': 0.0017548135921476508, 'nu02': 0.0760514851419992, 'nu30': 0.0012880263706323274, 'nu21': 0.0016190078435839353, 'nu12': -0.0014802029093220657, 'nu03': -0.0013833074364813173}
复制代码


注意看都是以 m.. 或者 nu.. 开始的各种数据。

轮廓面积

轮廓的面积可以使用函数 cv2.contourArea() 计算得到,也可以使用矩(0 阶矩), 即刚才结果中的 M["m00"] 获取。


# 轮廓面积area = cv.contourArea(cnt)print(area)# 0 阶矩print(M['m00'])
复制代码

轮廓周长

也称为弧长,使用函数 cv.arcLength() 计算得到,该函数的第二参数可以用来指定对象的形状是闭合的(True),还是打开的(一条曲线)。如果曲线闭合,那以上 2 种方法计算结果一致,如果是开曲线,则两者计算结果不同,其中闭合的方法,会在最后将起始点和终止点连一起的长度加进去。


# 轮廓周长perimeter = cv.arcLength(cnt,True)print(perimeter)
复制代码


后面的内容因为涉及到不同的轮廓,为了检测出希望操作的轮廓,我遍历了所有轮廓,找到了周长合适的那个圆形。


for index in range(len(contours)):    print("索引是:",index)    cnt = contours[index]    # 通过 moments 函数计算的所有矩值的字典    M = cv.moments(cnt)    # print(M)
# 轮廓面积 area = cv.contourArea(cnt) print(area) # 0 阶矩 print(M['m00']) # 轮廓周长 perimeter = cv.arcLength(cnt,True) print(perimeter)
cv.imshow("image",src)cv.waitKey()
复制代码


外接矩形

通过下述代码获取上图黄色圆形的外界矩形,外接矩形也叫做边界矩形或者包围盒。


它需要找到图形对象最高点、最低点、最左点、最右点,画出一个矩形边界。使用函数 cv2.boundingRect 计算之后,将结果进行返回,其中(x,y)为矩形左上角的坐标,(w,h)是矩形的宽和高。


# 外接矩形x, y, w, h = cv.boundingRect(cnt)cv.rectangle(src, (x, y), (x + w, y + h), (0, 255, 0), 2)
cv.imshow("src",src)cv.waitKey()
复制代码


还可以绘制圆形的最小外接矩形,也叫做旋转边界包围盒,例如下述代码,返回 Box2D 结构,包含左上角坐标(x,y),矩形宽,高 (w,h),以及旋转角度。


# 最小外接矩形rect = cv.minAreaRect(cnt)box = np.int0(cv.boxPoints(rect))cv.drawContours(src, [box], 0, (255, 0, 0), 2)cv.imshow("src",src)
复制代码


整体运行结果,略微存在差异。


其余补充学习

有了上述的基本知识概念之后,剩下的就非常容易学习了


最小外接圆代码如下,这里我切换了一张图片,毕竟用圆形做外接圆不太合适。


(x,y),radius = cv.minEnclosingCircle(cnt)center = (int(x),int(y))radius = int(radius)cv.circle(src,center,radius,(0,255,0),2)cv.imshow("src",src)cv.waitKey()
复制代码



椭圆拟合


ellipse = cv.fitEllipse(cnt)cv.ellipse(src,ellipse,(0,255,0),2)
复制代码


拟合一条线


# 拟合一条直线rows, cols = src.shape[:2][vx, vy, x, y] = cv.fitLine(cnt, cv.DIST_L2, 0, 0.01, 0.01)lefty = int((-x*vy/vx) + y)righty = int(((cols-x)*vy/vx)+y)cv.line(src, (cols-1, righty), (0, lefty), (0, 255, 0), 2)
复制代码



以上所有方法的前提都是找到轮廓,如果没有找到轮廓,所有函数都不会有结果展示。


轮廓近似与凸包相关知识点,依旧后置,这些知识的学习没有应用场景,很容易被遗忘。


相关资料提前学习,可以检索 cv2.approxPolyDPcv2.convexHull 函数。


轮廓性质可以由轮廓特征计算得出,包括但不限于,宽高比、轮廓面积与边界矩形面积的比、 轮廓面积与凸包面积的比、获取与轮廓面积相等的圆形直径、方向、轮廓的掩膜与像素点、最大值和最小值及它们的位置、平均颜色及平均灰度、极点、凸缺陷、形状匹配

橡皮擦的小节

希望今天的 1 个小时你有所收获,我们下篇博客见~


发布于: 19 分钟前阅读数: 2
用户头像

爬虫 100 例作者,蓝桥签约作者,博客专家 2021.02.06 加入

6 年产品经理+教学经验,3 年互联网项目管理经验; 互联网资深爱好者; 沉迷各种技术无法自拔,导致年龄被困在 25 岁; CSDN 爬虫 100 例作者。 个人公众号“梦想橡皮擦”。

评论

发布
暂无评论
Python OpenCV 轮廓检测与轮廓特征,加图像金字塔知识补充一点点