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

举报
梦想橡皮擦 发表于 2021/09/20 15:05:46 2021/09/20
【摘要】 Python OpenCV 365 天学习计划,与橡皮擦一起进入图像领域吧。本篇博客是这个系列的第 37 篇。 基础知识铺垫这篇文章需要配合上一篇一起观看,当然为了更好的学习效果,咱在一起复习一遍。上篇博客重点学习了两个函数的用法,第一个就是 findContours 函数,用来检测轮廓,该函数的原型如下:findContours(image, mode, method[, contours...

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

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

相关阅读


技术专栏

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

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


博主 ID:梦想橡皮擦,希望大家点赞评论收藏

【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。