本片文章记录学习边缘检测的运用。
- 了解坎尼边缘检测的概念
- 学习函数:cv2.Canny()。
Canny 边缘检测
- 使用高斯滤波器,以平滑图像,滤除噪声。
由于边缘检测很容易受到噪声影响,所以第一步是使用 5x5 的高斯滤波器去除噪声。
- 计算图像中每个像素点的梯度强度和方向。
对平滑后的图像使用 Sobel 算子计算水平方向和竖直方向的一阶导数(图像梯度)( Gx 和 Gy)。根据得到的这两幅梯度图( Gx 和 Gy)找到边界的梯度和方向,公式如下:
梯度的方向一般总是与边界垂直。梯度方向被归为四类:垂直,水平,和两个对角线。
- 应用非极大值抑制,以消除边缘检测带来的杂散影响。
在获得梯度的方向和大小之后,应该对整幅图像做一个扫描,去除那些非边界上的点。对每一个像素进行检查,看这个点的梯度是不是周围具有相同梯度方向的点中最大的。如下图所示:
点A在边缘(垂直方向)上。渐变方向垂直于边缘。点B和C在梯度方向上。因此,将A点与B点和C点进行检查,看是否形成局部最大值。如果是这样,则考虑将其用于下一阶段,否则将其抑制(置为零)。即得到的是一个包含“窄边界”的二值图像。
- 应用双阈值检测来确定真实和潜在的边缘。
现在要确定那些边界才是真正的边界。这时我们需要设置两个阈值:minVal 和 maxVal。当图像的灰度梯度高于 maxVal 时被认为是真的边界,那些低于 minVal 的边界会被抛弃。如果介于两者之间的话,就要看这个点是否与某个被确定为真正的边界点相连,如果是就认为它也是边界点,如果不是就抛弃。如下图:
A 高于阈值 maxVal 所以是真正的边界点, C 虽然低于 maxVal 但高于minVal 并且与 A 相连,所以也被认为是真正的边界点。而 B 就会被抛弃,因为他不仅低于 maxVal 而且不与真正的边界点相连。所以选择合适的 maxVal和 minVal 对于能否得到好的结果非常重要。在这一步一些小的噪声点也会被除去,因为我们假设边界都是一些长的线段。
- 通过抑制孤立的弱边缘最终完成边缘检测。
接下来我们看一下效果:
# -*- coding: utf-8 -*-
# @Time : 2019/10/19 17:11
# @Author : MMagicLoren
# @Email : 993983320@qq.com
# @File : 边缘检测.py
# @Software: PyCharm
import cv2 as cv
import numpy as np
def canny_demo(image):
edges1 = cv.Canny(image, 80, 150)
edges2 = cv.Canny(image, 30, 100)
res = np.hstack((edges1, edges2))
cv.imshow('res', res)
if __name__ == '__main__':
src = cv.imread("F:/Pycharm/opencv_exercises-master/images/CrystalLiu1.jpg") # 读入图片放进src中
# cv.namedWindow("input image", cv.WINDOW_AUTOSIZE) # 创建窗口, 窗口尺寸自动调整
# cv.imshow("input image", src)
canny_demo(src)
cv.waitKey(0) # 等有键输入或者1000ms后自动将窗口消除,0表示只用键输入结束窗口
cv.destroyAllWindows()
可见,设置的阈值越小,检测出来的边缘越多。
我们知道了Canny边缘检测的原理之后,我们可以用之前学过的知识,自己写个demo看看效果。
# -*- coding: utf-8 -*-
# @Time : 2019/10/19 17:11
# @Author : MMagicLoren
# @Email : 993983320@qq.com
# @File : 边缘检测.py
# @Software: PyCharm
import cv2 as cv
import numpy as np
def edges_demo(image):
blurred = cv.GaussianBlur(image, (3, 3), 0)
gray = cv.cvtColor(blurred, cv.COLOR_BGR2GRAY)
xgray = cv.Sobel(gray, cv.CV_16SC1, 1, 0)
ygray = cv.Sobel(gray, cv.CV_16SC1, 0, 1)
edges_output = cv.Canny(xgray, ygray, 50, 150) # edges_output = cv.Canny(gray, 50, 150)
cv.imshow("Canny Edges", edges_output)
dst = cv.bitwise_and(image, image, mask=edges_output) # 彩色边缘输出
cv.imshow("Color Edges", dst)
if __name__ == '__main__':
src = cv.imread("F:/Pycharm/opencv_exercises-master/images/CrystalLiu1.jpg") # 读入图片放进src中
# cv.namedWindow("input image", cv.WINDOW_AUTOSIZE) # 创建窗口, 窗口尺寸自动调整
# cv.imshow("input image", src)
canny_demo(src)
# edges_demo(src)
cv.waitKey(0) # 等有键输入或者1000ms后自动将窗口消除,0表示只用键输入结束窗口
cv.destroyAllWindows()
完整工程代码:
# -*- coding: utf-8 -*-
# @Time : 2019/10/19 17:11
# @Author : MMagicLoren
# @Email : 993983320@qq.com
# @File : 边缘检测.py
# @Software: PyCharm
import cv2 as cv
import numpy as np
def canny_demo(image):
edges1 = cv.Canny(image, 80, 150)
edges2 = cv.Canny(image, 30, 100)
res = np.hstack((edges1, edges2))
cv.imshow('res', res)
def edges_demo(image):
blurred = cv.GaussianBlur(image, (3, 3), 0)
gray = cv.cvtColor(blurred, cv.COLOR_BGR2GRAY)
xgray = cv.Sobel(gray, cv.CV_16SC1, 1, 0)
ygray = cv.Sobel(gray, cv.CV_16SC1, 0, 1)
edges_output = cv.Canny(xgray, ygray, 50, 150) # edges_output = cv.Canny(gray, 50, 150)
cv.imshow("Canny Edges", edges_output)
dst = cv.bitwise_and(image, image, mask=edges_output) # 彩色边缘输出
cv.imshow("Color Edges", dst)
if __name__ == '__main__':
src = cv.imread("F:/Pycharm/opencv_exercises-master/images/CrystalLiu1.jpg") # 读入图片放进src中
# cv.namedWindow("input image", cv.WINDOW_AUTOSIZE) # 创建窗口, 窗口尺寸自动调整
# cv.imshow("input image", src)
# canny_demo(src)
edges_demo(src)
cv.waitKey(0) # 等有键输入或者1000ms后自动将窗口消除,0表示只用键输入结束窗口
cv.destroyAllWindows()