Python OpenCV Canny 边缘检测知识补充
Python OpenCV 365 天学习计划,与橡皮擦一起进入图像领域吧。本篇博客是这个系列的第 47 篇。
学在前面
在 Canny 边缘提取相关知识学习,图像处理第 32 篇博客 这篇博客中,我们已经对 Canny
边缘检测进行了基本的学习,今天这篇文章主要用于对其进行补充,当然知识难度不大,1 个小时就能学到。
Canny 边缘检测流程
参照互联网大家发布最多的流程
- 高斯模糊去噪;
- 计算图像梯度幅值和方向,这里一般用的是 Sobel 算子;
- 非极大值抑制,简单理解就是“瘦边”;
- 双阈值检测,也叫做滞后阈值。
按照上面的步骤进行翻译,就能写出一个 Canny 朴实的案例了
import cv2 as cv
import 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
,有以下结论。
- 梯度值 > maxVal ,边界保留;
- minVal < 梯度值 < maxVal 时,如果像素梯度值在边界上,保留,否则,舍弃;
- 梯度值 < minVal,舍弃。
以上三点的大白话,非常容易记忆,如果你想要更多的边界,调小 minVal
值,否则调高它。
在实操的时候还尝试出一个问题,如果在进行边缘提取之前进行了二值化操作,那得到的图像受上面参数影响较小,例如下述代码,双阈值设置不同值,得到的结果基本无差异。
import cv2 as cv
import 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 效果更佳理想。
# 方法1
import cv2 as cv
import 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()
### 方法2
import cv2 as cv
import 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 个小时你有所收获,我们下篇博客见~
相关阅读
技术专栏
今天是持续写作的第 89 / 100 天。
如果你想跟博主建立亲密关系,可以关注同名公众号 梦想橡皮擦,近距离接触一个逗趣的互联网高级网虫。
博主 ID:梦想橡皮擦,希望大家点赞、评论、收藏。
- 点赞
- 收藏
- 关注作者
评论(0)