Python OpenCV 对象检测,图像处理取经之旅第 37 篇
Python OpenCV 365 天学习计划,与橡皮擦一起进入图像领域吧。本篇博客是这个系列的第 37 篇。
基础知识铺垫
这篇文章需要配合上一篇一起观看,当然为了更好的学习效果,咱在一起复习一遍。
上篇博客重点学习了两个函数的用法,第一个就是 findContours
函数,用来检测轮廓,该函数的原型如下:
findContours(image, mode, method[, contours[, hierarchy[, offset]]]) -> contours, hierarchy
简单写一段代码测试一下:
import cv2 as cv
import numpy as np
img = cv.imread("./test1.jpg", 0)
img = cv.medianBlur(img, 5)
ret, thresh = cv.threshold(img, 0, 255, cv.THRESH_BINARY | cv.THRESH_OTSU)
contours, hierarchy = cv.findContours(
thresh, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_NONE)
print(contours, type(contours), len(contours))
输出结果如下,注意内容:
[array([[[0, 0]],
[[0, 1]],
[[0, 2]],
...,
[[3, 0]],
[[2, 0]],
[[1, 0]]], dtype=int32)] <class 'list'> 1
cv.findContours
边缘检测函数,返回只第一个是一个列表,其中每一项都是 numpy
中的数组,表示的就是边界值。
列表有 1 个,那得到的就只有一个边界。
接下来绘制出边缘,这里需要调整的内容有些多,具体代码如下。
import cv2 as cv
import numpy as np
img = cv.imread("./ddd.jpg")
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
med_img = cv.medianBlur(gray, 7)
ret, thresh = cv.threshold(med_img, 225, 255, cv.THRESH_BINARY_INV)
# cv.imshow("thresh", thresh)
contours, hierarchy = cv.findContours(
thresh, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_NONE)
print(contours, type(contours), len(contours), contours[0].shape)
print(hierarchy, type(hierarchy), len(hierarchy), hierarchy.shape)
dst = cv.drawContours(img, contours, -1, (0, 0, 255), 2)
cv.imshow("dst", dst)
cv.waitKey()
其中涉及的输出内容如下:
print(contours, type(contours), len(contours), contours[0].shape)
print(hierarchy, type(hierarchy), len(hierarchy), hierarchy.shape)
通过 len(contours)
得到的数字是几,就表示几个边界。
下面绘制轮廓的代码,你可以进行一下尝试。
dst = cv.drawContours(img, contours, 0, (0, 0, 255), 2)
contourIdx
是找到的轮廓索引,不可以超过轮廓总数,否则会出现如下 BUG。
error: (-215:Assertion failed) 0 <= contourIdx && contourIdx < (int)last in function 'cv::drawContours'
继续修改,将最后一个参数修改为 -1
,得到的结果如下。
将阈值分割修改为边缘检测
上文我们是通过 cv2.threshold
函数实现的最终效果,接下来在通过 Canny
边缘检测进行一下上述操作。
import cv2 as cv
import numpy as np
img = cv.imread("./ddd.jpg")
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
med_img = cv.medianBlur(gray, 5)
# 阈值分割
# ret, thresh = cv.threshold(med_img, 225, 255, cv.THRESH_BINARY_INV)
# cv.imshow("thresh", thresh)
edges = cv.Canny(med_img,200,255)
cv.imshow("edges",edges)
contours, hierarchy = cv.findContours(
edges, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_NONE)
# print(contours, type(contours), len(contours), contours[0].shape)
# print(hierarchy, type(hierarchy), len(hierarchy), hierarchy.shape)
dst = cv.drawContours(img, contours, -1, (0, 0, 255), -1)
cv.imshow("dst", dst)
cv.waitKey()
在使用上述代码,如果想实现填充效果,比较难实现,因为很多路径并不是闭合的。参数调整的不是很理想,见谅。
在调整参数的时候,还出现了下述情况,这个地方并未找到合理的解释。
对象测量
获得轮廓之后,可以对轮廓进行一些几何特征的测量,包括 原点距
、中心距
、图像的重心坐标
,这些数学概念留到后续学习,先掌握应用层。
对象测量用到的函数是 cv2.moments
,该函数原型如下:
retval = cv2.moments(array[, binaryImage])
用途就是输入轮廓,返回一个字典,测试代码如下:
# dst = cv.drawContours(img, contours, -1, (200, 100, 0), 3)
for contour in contours:
print(cv.moments(contour))
我随便选择一个作为说明,内容如下:
{'m00': 3.0, 'm10': 213.0, 'm01': 295.5, 'm20': 15125.5, 'm11': 20982.75, 'm02': 29109.0, 'm30': 1074265.5, 'm21': 1490181.25, 'm12': 2067182.25, 'm03': 2867679.75, 'mu20': 2.5, 'mu11': 2.25, 'mu02': 2.25, 'mu30': 0.0, 'mu21': 0.0, 'mu12': 0.0, 'mu03': 0.0, 'nu20': 0.2777777777777778, 'nu11': 0.25, 'nu02': 0.25, 'nu30': 0.0, 'nu21': 0.0, 'nu12': 0.0, 'nu03': 0.0}
接下来就可以针对上述内容做相应的处理了。
例如,求出轮廓的重心。
最常见的一个错误是,出现该错误增加一个非零的分支验证即可。
ZeroDivisionError: float division by zero
第二个错误是类型错误,这个我们在学习绘制圆形的时候了解过,具体如下。
TypeError: integer argument expected, got float
修改代码之后的逻辑为:
import cv2 as cv
import numpy as np
img = cv.imread("./t1.jpg")
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
med_img = cv.medianBlur(gray, 7)
# 阈值分割
# ret, thresh = cv.threshold(med_img, 150, 255, cv.THRESH_BINARY_INV)
# cv.imshow("thresh", thresh)
edges = cv.Canny(med_img, 200, 255)
cv.imshow("edges", edges)
contours, hierarchy = cv.findContours(
edges, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_NONE)
# print(contours, type(contours), len(contours), contours[0].shape)
# print(hierarchy, type(hierarchy), len(hierarchy), hierarchy.shape)
# dst = cv.drawContours(img, contours, -1, (200, 100, 0), 3)
for contour in contours:
m = cv.moments(contour)
if m['m00'] != 0:
x = int(m['m10']/m['m00'])
y = int(m['m01']/m['m00'])
cv.circle(img, (x, y), 2, (0, 0, 255), -1)
else:
pass
dst = cv.drawContours(img, contours, -1, (200, 100, 0), 2)
cv.imshow("img", img)
cv.waitKey()
你还可以计算轮廓面积,使用的函数为 cv2.contourArea
。
for contour in contours:
print(cv.contourArea(contour))
曲线闭合的时候得的面积是轮廓包围的面积,如果轮廓不闭合或者边界有交叉,获取到的面积不在准确。
相关阅读
技术专栏
今天是持续写作的第 78 / 100 天。
如果你有想要交流的想法、技术,欢迎在评论区留言。
博主 ID:梦想橡皮擦,希望大家点赞、评论、收藏。
- 点赞
- 收藏
- 关注作者
评论(0)