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 函数
该函数有两个返回值,contours
与 hierarchy
,其中一个是轮廓本身,另一个就是每条轮廓对应的属性。整体测试代码与图像使用下述内容。
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.approxPolyDP
与 cv2.convexHull
函数。
轮廓性质可以由轮廓特征计算得出,包括但不限于,宽高比、轮廓面积与边界矩形面积的比、 轮廓面积与凸包面积的比、获取与轮廓面积相等的圆形直径、方向、轮廓的掩膜与像素点、最大值和最小值及它们的位置、平均颜色及平均灰度、极点、凸缺陷、形状匹配
橡皮擦的小节
希望今天的 1 个小时你有所收获,我们下篇博客见~
评论