写点什么

Python OpenCV 对象检测,图像处理取经之旅第 37 篇

发布于: 1 小时前

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

基础知识铺垫

这篇文章需要配合上一篇一起观看,当然为了更好的学习效果,咱在一起复习一遍。


上篇博客重点学习了两个函数的用法,第一个就是 findContours 函数,用来检测轮廓,该函数的原型如下:


findContours(image, mode, method[, contours[, hierarchy[, offset]]]) -> contours, hierarchy
复制代码


简单写一段代码测试一下:


import cv2 as cvimport 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 cvimport 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 cvimport 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 cvimport 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: passdst = 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))
复制代码


曲线闭合的时候得的面积是轮廓包围的面积,如果轮廓不闭合或者边界有交叉,获取到的面积不在准确。


发布于: 1 小时前阅读数: 2
用户头像

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

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

评论

发布
暂无评论
Python OpenCV 对象检测,图像处理取经之旅第 37 篇