cv2.Canny() - 边缘检测 -- OpenCV





边缘定义及类型

边缘类型:简单分为 4 种类型,阶跃型、屋脊型、斜坡型、脉冲型,其中阶跃型和斜坡型是类似的,只是变化的快慢不同。



边缘检测算子类别

边缘检测算子:

  • 一阶导数: Roberts、Sobel、Prewitt
  • 二阶导数: Laplacian、Log/Marr、(Kirsch、Nevitia)
  • 非微分边缘检测算子: Canny

关于算子详情请查看:https://blog.csdn.net/wsp_1138886114/article/details/81368890



OpenCV-Python 中 Canny() 参数

步骤:

  • 彩色图像转换为灰度图像(以灰度图或者单通道图读入)
  • 对图像进行高斯模糊(去噪)
  • 计算图像梯度,根据梯度计算图像边缘幅值与角度
  • 沿梯度方向进行非极大值抑制(边缘细化)
  • 双阈值边缘连接处理
  • 二值化图像输出结果

"""
cv2.Canny(image,            # 输入原图(必须为单通道图)
          threshold1, 
          threshold2,       # 较大的阈值2用于检测图像中明显的边缘
          [, edges[, 
          apertureSize[,    # apertureSize:Sobel算子的大小
          L2gradient ]]])   # 参数(布尔值):
                              true: 使用更精确的L2范数进行计算(即两个方向的倒数的平方和再开放),
                              false:使用L1范数(直接将两个方向导数的绝对值相加)。
"""

import cv2
import numpy as np  

original_img = cv2.imread("qingwen.png", 0)

# canny(): 边缘检测
img1 = cv2.GaussianBlur(original_img,(3,3),0)
canny = cv2.Canny(img1, 50, 150)

# 形态学:边缘检测
_,Thr_img = cv2.threshold(original_img,210,255,cv2.THRESH_BINARY)#设定红色通道阈值210(阈值影响梯度运算效果)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(5,5))         #定义矩形结构元素
gradient = cv2.morphologyEx(Thr_img, cv2.MORPH_GRADIENT, kernel) #梯度

cv2.imshow("original_img", original_img) 
cv2.imshow("gradient", gradient) 
cv2.imshow('Canny', canny)

cv2.waitKey(0)
cv2.destroyAllWindows()


可调整阈值大小的程序

import cv2
import numpy as np

def CannyThreshold(lowThreshold):
    detected_edges = cv2.GaussianBlur(gray,(3,3),0)
    detected_edges = cv2.Canny(detected_edges,
                               lowThreshold,
                               lowThreshold*ratio,
                               apertureSize = kernel_size)
    dst = cv2.bitwise_and(img,img,mask = detected_edges)  # just add some colours to edges from original image.
    cv2.imshow('canny demo',dst)

lowThreshold = 0
max_lowThreshold = 100
ratio = 3
kernel_size = 3

img = cv2.imread('qingwen.png')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

cv2.namedWindow('canny demo')

cv2.createTrackbar('Min threshold','canny demo',lowThreshold, max_lowThreshold, CannyThreshold)

CannyThreshold(0)  # initialization
if cv2.waitKey(0) == 27:
    cv2.destroyAllWindows()



教程


Canny 边缘检测方法常被誉为边缘检测的最优方法,废话不多说,先看个例子:

import cv2
import matplotlib.pyplot as plt
import numpy as np

p = '../11-Edge-Detection/handwriting.jpg'

img = cv2.imread(p, 0)
edges = cv2.Canny(img, 30, 70)

plt.imshow(np.hstack((img, edges)), 'gray')
plt.show()

cv2.Canny() 进行边缘检测,参数 2、3 表示最低、高阈值,下面来解释下具体原理。

经验之谈:之前我们用低通滤波的方式模糊了图片,那反过来,想得到物体的边缘,就需要用到高通滤波。推荐先阅读:番外篇:图像梯度。


Canny 边缘检测


Canny 边缘提取的具体步骤如下:


1,使用 5×5 高斯滤波消除噪声:

边缘检测本身属于锐化操作,对噪点比较敏感,所以需要进行平滑处理。

K= 1/256 [ 1  4​  6  4 1
           4 16 24 16 4
           6 24 36 24 6
           4 16 24 16 ​4
           1  4  6  4 1]


2,计算图像梯度的方向:

首先使用 Sobel 算子计算两个方向上的梯度 G_x 和 G_y,然后算出梯度的方向:


保留这四个方向的梯度:0°/45°/90°/135°,有什么用呢?我们接着看。


3,取局部极大值:

梯度其实已经表示了轮廓,但为了进一步筛选,可以在上面的四个角度方向上再取局部极大值:


比如,A 点在 45° 方向上大于 B / C 点,那就保留它,把 B / C 设置为 0。



4,滞后阈值:

经过前面三步,就只剩下 0 和可能的边缘梯度值了,为了最终确定下来,需要设定高低阈值:


  • 像素点的值大于最高阈值,那肯定是边缘(上图A)
  • 同理像素值小于最低阈值,那肯定不是边缘
  • 像素值介于两者之间,如果与高于最高阈值的点连接,也算边缘,所以上图中C算,B不算

Canny 推荐的高低阈值比在 2:1 到 3:1 之间。


先阈值分割后检测

其实很多情况下,阈值分割后再检测边缘,效果会更好:

import cv2
import matplotlib.pyplot as plt
import numpy as np

p = '../11-Edge-Detection/handwriting.jpg'

img = cv2.imread(p, 0)

_, thresh = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
edges = cv2.Canny(thresh, 30, 70)

plt.imshow(np.hstack((img, edges)), 'gray')
plt.show()








reference

https://blog.csdn.net/wsp_1138886114/article/details/82935839

http://codec.wang/#/opencv/basic/11-edge-detection