写点什么

Python OpenCV Canny 边缘检测知识补充

发布于: 2 小时前

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

学在前面

Canny 边缘提取相关知识学习,图像处理第 32 篇博客 这篇博客中,我们已经对 Canny 边缘检测进行了基本的学习,今天这篇文章主要用于对其进行补充,当然知识难度不大,1 个小时就能学到。

Canny 边缘检测流程

参照互联网大家发布最多的流程


  1. 高斯模糊去噪;

  2. 计算图像梯度幅值和方向,这里一般用的是 Sobel 算子;

  3. 非极大值抑制,简单理解就是“瘦边”;

  4. 双阈值检测,也叫做滞后阈值。


按照上面的步骤进行翻译,就能写出一个 Canny 朴实的案例了


import cv2 as cvimport numpy as np
src = cv.imread("./t7.jpg", 0)# 高斯模糊gaussian = cv.GaussianBlur(src, (3, 3), 0)ret, thresh = cv.threshold(gaussian, 0, 255, cv.THRESH_BINARY | cv.THRESH_OTSU)# 计算图像梯度幅值和方向,这里一般用的是 Sobel 算子;sobel_x = cv.Sobel(thresh, cv.CV_16SC1, 1, 0)sobel_y = cv.Sobel(thresh, cv.CV_16SC1, 0, 1)sobel_x = cv.convertScaleAbs(sobel_x)sobel_y = cv.convertScaleAbs(sobel_y)sobel_xy = cv.addWeighted(sobel_x, 0.5, sobel_y, 0.5, 0)
canny1 = cv.Canny(sobel_xy, 50, 150)
image = np.hstack((thresh, canny1))cv.imshow('img', image)cv.waitKey(0)cv.destroyAllWindows()
复制代码



代码中一些要说明的地方,双阈值检测采用的是高低阈值检测,高阈值使用 maxVal、低阈值使用 minVal,有以下结论。


  1. 梯度值 > maxVal ,边界保留;

  2. minVal < 梯度值 < maxVal 时,如果像素梯度值在边界上,保留,否则,舍弃;

  3. 梯度值 < minVal,舍弃。


以上三点的大白话,非常容易记忆,如果你想要更多的边界,调小 minVal 值,否则调高它。


在实操的时候还尝试出一个问题,如果在进行边缘提取之前进行了二值化操作,那得到的图像受上面参数影响较小,例如下述代码,双阈值设置不同值,得到的结果基本无差异。


import cv2 as cvimport numpy as np
src = cv.imread("./t77.jpg", 0)# 高斯模糊gaussian = cv.GaussianBlur(src, (3, 3), 0)ret, thresh = cv.threshold(gaussian, 0, 255, cv.THRESH_BINARY | cv.THRESH_OTSU)# 计算图像梯度幅值和方向,这里一般用的是 Sobel 算子;sobel_x = cv.Sobel(thresh, cv.CV_16SC1, 1, 0)sobel_y = cv.Sobel(thresh, cv.CV_16SC1, 0, 1)sobel_x = cv.convertScaleAbs(sobel_x)sobel_y = cv.convertScaleAbs(sobel_y)sobel_xy = cv.addWeighted(sobel_x, 0.5, sobel_y, 0.5, 0)
canny1 = cv.Canny(sobel_xy, 10, 30)canny2 = cv.Canny(sobel_xy, 30, 90)canny3 = cv.Canny(sobel_xy, 50, 150)image = np.hstack((canny1, canny2,canny3))cv.imshow('img', image)cv.waitKey(0)cv.destroyAllWindows()
复制代码



如果去掉二值化,那得到的图像边缘有明显区别,现在也理解了为何刚才在说明 Canny 边缘提取步骤,并未有二值化这一步,看来是多此一举了。


# 高斯模糊gaussian = cv.GaussianBlur(src, (3, 3), 0)# ret, thresh = cv.threshold(gaussian, 0, 255, cv.THRESH_BINARY | cv.THRESH_OTSU)# 计算图像梯度幅值和方向,这里一般用的是 Sobel 算子;
复制代码



还有一个小问题,是在测试的时候发现,使用下面两段代码得到的边缘不一致,具体如下,你可以分开测试。差别在于方法 1 通过 Sobel 算子分别计算了 x 方向和 y 方向的梯度,在使用合并之后的图像进行 Canny 边缘检测,得到的边缘是双线,方法 2 效果更佳理想。


# 方法1import cv2 as cvimport numpy as np
src = cv.imread("./t77.jpg", 0)# 高斯模糊gaussian = cv.GaussianBlur(src, (3, 3), 0)
# 计算图像梯度幅值和方向,这里一般用的是 Sobel 算子;sobel_x = cv.Sobel(gaussian, cv.CV_16SC1, 1, 0)sobel_y = cv.Sobel(gaussian, cv.CV_16SC1, 0, 1)sobel_x = cv.convertScaleAbs(sobel_x)sobel_y = cv.convertScaleAbs(sobel_y)sobel_xy = cv.addWeighted(sobel_x, 0.5, sobel_y, 0.5, 0)

canny1 = cv.Canny(sobel_xy, 50, 150)# canny2 = cv.Canny(sobel_x,sobel_y, 50, 150)image = np.hstack((gaussian, canny1))cv.imshow('img', image)cv.waitKey(0)cv.destroyAllWindows()

### 方法2import cv2 as cvimport numpy as np
src = cv.imread("./t77.jpg", 0)# 高斯模糊gaussian = cv.GaussianBlur(src, (3, 3), 0)
# 计算图像梯度幅值和方向,这里一般用的是 Sobel 算子;sobel_x = cv.Sobel(gaussian, cv.CV_16SC1, 1, 0)sobel_y = cv.Sobel(gaussian, cv.CV_16SC1, 0, 1)
canny2 = cv.Canny(sobel_x, sobel_y, 50, 150)image = np.hstack((gaussian, canny2))cv.imshow('img', image)cv.waitKey(0)cv.destroyAllWindows()
复制代码



很遗憾是 非极大值抑制 相关知识的学习,还需要在延期一下,现在的知识储备量不足,学起来有些吃力,我们后面见。

橡皮擦的小节

希望今天的 1 个小时你有所收获,我们下篇博客见~


博主 ID:梦想橡皮擦,希望大家<font color="red">点赞</font>、<font color="red">评论</font>、<font color="red">收藏</font>。

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

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

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

评论

发布
暂无评论
Python OpenCV Canny 边缘检测知识补充