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

712 阅读7分钟

小知识,大挑战!本文正在参与「程序员必备小知识」创作活动

本文已参与 「掘力星计划」 ,赢取创作大礼包,挑战创作激励金。

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 函数绘制轮廓。

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

在学习 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)

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

返回值 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()

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

外接矩形

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

它需要找到图形对象最高点、最低点、最左点、最右点,画出一个矩形边界。 使用函数 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)

整体运行结果,略微存在差异。 Python OpenCV 轮廓检测与轮廓特征,加图像金字塔知识补充一点点

其余补充学习

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

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

(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()

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

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)

Python OpenCV 轮廓检测与轮廓特征,加图像金字塔知识补充一点点 以上所有方法的前提都是找到轮廓,如果没有找到轮廓,所有函数都不会有结果展示。

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

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

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

橡皮擦的小节

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