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

本文介绍了使用Python的OpenCV库进行边缘检测和对象测量的方法。首先,通过findContours函数检测图像轮廓,然后使用Canny边缘检测替代阈值分割。接着,展示了如何计算轮廓的几何特性,如重心,并处理了可能出现的错误。最后,讨论了对象测量,包括使用moments函数计算轮廓的几何属性。
摘要由CSDN通过智能技术生成

Python OpenCV 365 天学习计划,与橡皮擦一起进入图像领域吧。本篇博客是这个系列的第 37 篇。
该系列文章导航参考:https://blog.csdn.net/hihell/category_10688961.html

基础知识铺垫

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

上篇博客重点学习了两个函数的用法,第一个就是 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) 得到的数字是几,就表示几个边界。
Python OpenCV 对象检测,图像处理取经之旅第 37 篇
下面绘制轮廓的代码,你可以进行一下尝试。

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,得到的结果如下。
Python OpenCV 对象检测,图像处理取经之旅第 37 篇

将阈值分割修改为边缘检测

上文我们是通过 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()

在使用上述代码,如果想实现填充效果,比较难实现,因为很多路径并不是闭合的。参数调整的不是很理想,见谅。
Python OpenCV 对象检测,图像处理取经之旅第 37 篇
在调整参数的时候,还出现了下述情况,这个地方并未找到合理的解释。
Python OpenCV 对象检测,图像处理取经之旅第 37 篇

对象测量

获得轮廓之后,可以对轮廓进行一些几何特征的测量,包括 原点距中心距图像的重心坐标,这些数学概念留到后续学习,先掌握应用层。

对象测量用到的函数是 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()

Python OpenCV 对象检测,图像处理取经之旅第 37 篇
你还可以计算轮廓面积,使用的函数为 cv2.contourArea

for contour in contours:
    print(cv.contourArea(contour))

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

相关阅读


技术专栏

  1. Python 爬虫 100 例教程,超棒的爬虫教程,立即订阅吧
  2. Python 爬虫小课,精彩 9 讲

今天是持续写作的第 78 / 100 天。
如果你有想要交流的想法、技术,欢迎在评论区留言。


如果你想跟博主建立亲密关系,可以关注同名公众号 梦想橡皮擦,近距离接触一个逗趣的互联网高级网虫。
博主 ID:梦想橡皮擦,希望大家点赞评论收藏

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

梦想橡皮擦

如有帮助,来瓶可乐

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值