简 介: 本文讨论了图像处理中重要的技术:边缘检测,重点介绍了两种方法(Sobel边缘检测和Canny边缘检测)。在展示OpenCV中的用法 同时也强调了为什么图像平滑是重要的预处理步骤。在Canny边缘检测中 也是使用的Sobel算子获得导数的数值解。通过应用非最大抑制以及滞回比较, Canny算法更加鲁棒和灵活。这就是为什么Canny算子最令人喜欢并被广泛使用在边缘检测中的原因。

关键词 边缘检测SobelCanny

前 言
目 录
Contents
如何进行边缘检测?
边缘检测
前期处理
Sobel边缘检测
Canny边缘检测
算法总结

本文是通过 Edge Detection Using OpenCV 中的内容进行整理而成,用于之后的学习和工作。

 

§00   言

  边缘检测是图像处理技术,用于确定图片中物体的边界(边缘)或者区域。边缘是图像中重要的特征。我们通过边缘来了解图像的结构信息。边缘检测算法广泛存在于实际应用中的计算机视觉算法处理流水线中。

0.1 如何进行边缘检测?

  边缘的特征在于像素亮度的突然变化,为了检测边缘,我们需要能够寻找出存在于相邻像素之间的这种变化。来吧,让我们探讨两种OpenCV中的重要边缘检测算法:Sobel边缘检测以及Canny边缘检测。我们不但演示如何使用OpenCV实现这两个算法,还讨论背后的理论。

  首先,看一下将要展示的边缘检测代码。后面会对代码每一行进行讲解帮助你充分理解他们。

  • Python
import cv2

# Read the original image
img = cv2.imread('test.jpg') 
# Display original image
cv2.imshow('Original', img)
cv2.waitKey(0)

# Convert to graycsale
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Blur the image for better edge detection
img_blur = cv2.GaussianBlur(img_gray, (3,3), 0) 

# Sobel Edge Detection
sobelx = cv2.Sobel(src=img_blur, ddepth=cv2.CV_64F, dx=1, dy=0, ksize=5) # Sobel Edge Detection on the X axis
sobely = cv2.Sobel(src=img_blur, ddepth=cv2.CV_64F, dx=0, dy=1, ksize=5) # Sobel Edge Detection on the Y axis
sobelxy = cv2.Sobel(src=img_blur, ddepth=cv2.CV_64F, dx=1, dy=1, ksize=5) # Combined X and Y Sobel Edge Detection
# Display Sobel Edge Detection Images
cv2.imshow('Sobel X', sobelx)
cv2.waitKey(0)
cv2.imshow('Sobel Y', sobely)
cv2.waitKey(0)
cv2.imshow('Sobel X Y using Sobel() function', sobelxy)
cv2.waitKey(0)

# Canny Edge Detection
edges = cv2.Canny(image=img_blur, threshold1=100, threshold2=200) # Canny Edge Detection
# Display Canny Edge Detection Image
cv2.imshow('Canny Edge Detection', edges)
cv2.waitKey(0)

cv2.destroyAllWindows()
  • C++
#include <opencv2/opencv.hpp>
#include <iostream>
// using namespaces to nullify use of cv::function(); syntax and std::function();
using namespace std;
using namespace cv;

int main()
{
    // Reading image
    Mat img = imread("test.jpg");
    // Display original image
    imshow("original Image", img);
    waitKey(0);

    // Convert to graycsale
    Mat img_gray;
    cvtColor(img, img_gray, COLOR_BGR2GRAY);
    // Blur the image for better edge detection
    Mat img_blur;
    GaussianBlur(img_gray, img_blur, Size(3,3), 0);
    
    // Sobel edge detection
    Mat sobelx, sobely, sobelxy;
    Sobel(img_blur, sobelx, CV_64F, 1, 0, 5);
    Sobel(img_blur, sobely, CV_64F, 0, 1, 5);
    Sobel(img_blur, sobelxy, CV_64F, 1, 1, 5);
    // Display Sobel edge detection images
    imshow("Sobel X", sobelx);
    waitKey(0);
    imshow("Sobel Y", sobely);
    waitKey(0);
    imshow("Sobel XY using Sobel() function", sobelxy);
    waitKey(0);

    // Canny edge detection
    Mat edges;
    Canny(img_blur, edges, 100, 200, 3, false);
    // Display canny edge detected image
    imshow("Canny edge detection", edges);
    waitKey(0);
    
    destroyAllWindows();
    return 0;
}

▲ 图1.1  用于变换检测的图像

▲ 图1.1 用于变换检测的图像

  在讨论每个算法细节之前,我们需要完成一些变换检测基本步骤。将OpenCV软件库导入,就像下面代码所示:

  • Python
import cv2
  • C++
#include<opencv2/opencv.hpp>
#include<iostream>

// Namespace nullifies the use of cv::function(); 
using namespace std;
using namespace cv;

 

§01 缘检测


1.1 前期处理

1.1.1 读入图像

  第一步使用OpenCV中的imread() 函数将图像读入内存。

  这里,我们按照灰度图格式将彩色图片读入内存,这是因为在检测边缘是你不需要彩色信息。如果你想了解读入图片更多参数信息,可以参考 之前的博文

1.1.2 图像平滑

  载入图像之后,我们也使用 GaussianBlur() 函数对其进行平滑。这是为了去除图片中的噪声。在边缘检测中,需要对像素亮度进行数值求导,结果会产生带有噪声的边缘。换而言之,图像中的相邻像素的亮度(特别是在边缘附近)波动比较大,者会被我们当成边界,但并不是我们寻找的主要边缘结构。

  对于边界附近的亮度通过模糊化进行平滑,可以比较容易识别出图像中的主要边缘结构。 你可以通过OpenCV相关文档了解GaussianBlur() 函数的更多的细节。 我们制定卷积核的尺寸(本例中,使用一个3×3的卷积核),决定了模糊化的程度。

  • Python
# Read the original image
img = cv2.imread('test.jpg',flags=0)  
# Blur the image for better edge detection
img_blur = cv2.GaussianBlur(img,(3,3), SigmaX=0, SigmaY=0) 
  • C++
// Reading image
Mat img = imread("test.jpg",0);
// Blur the image for better edge detection
Mat img_blur;
GaussianBlur(img, img_blur, Size(3,3), SigmaX=0, SigmaY=0);

1.2 Sobel边缘检测

  Sobel变换检测是一个广泛使用的边缘检测算法。 通过下图显示Sobel边缘检算子是寻找那些像素亮度突然变化的边缘。

1.2.1 基本原理

▲ 图1.2.1  像素随着变量t而发生变化

▲ 图1.2.1 像素随着变量t而发生变化

  当我们对亮度函数求其一阶导数的时候,会获得更加明显的峰值。

▲ 图1.2.2  求取亮度变化的一阶导数

▲ 图1.2.2 求取亮度变化的一阶导数

  上面绘制的图像展示了通过检测高于某个阈值的梯度值来检测边缘。再者,导数的突然改变也显示出像素亮度的突变。根据这一点,我们可以使用3×3的卷积核来获得近似导数。 我们使用一个卷积核检测在X方向(水平方向)的像素亮度突变, 使用另外一个卷积核检测Y方向(垂直方向)的亮度突变 。可以通过 这个链接 学习更多关于图像滤波和卷积核的内容。

  下面是两个用于Sobel边缘检测的卷积核:


  (1)是用于检测X方向边缘的卷积核。


  (2)是用于检测Y方向的卷积核。

  利用这些卷积核对源图像进行卷积,我们活得“Sobel边缘图像”。

  • 如果我们只使用垂直卷积核,卷积结果将会增加X方向边缘;
  • 如果使用水平卷积核所获得的的Sobel图像,在Y方向上的边缘被增强;

  使用 G x G_x Gx G y G_y Gy 分别代表x和y方向的梯度值,利用A,B表示X,Y方向的卷积核:

G x = A ∗ I ,     G y = B ∗ I G_x = A * I,\,\,\,G_y = B * I Gx=AI,Gy=BI

  其中 * 表示卷积算子, I I I 表示输入图像。

  最终的图像梯度幅值 G G G 可以通过下式计算: G = G x 2 + G y 2 G = \sqrt {G_x^2 + G_y^2 } G=Gx2+Gy2

  梯度方向由下式近似: θ = arctan ⁡ ( G y / G x ) \theta = \arctan \left( {G_y /G_x } \right) θ=arctan(Gy/Gx)

1.2.2 实现代码

  下面代码中,使用 Sobel() 函数计算:

  • 分别计算两个方向(x方向,y方向)的Sobel边缘图像;
  • 获得两个方向的组合梯度;

  下面是OpenCV中的Sobel边缘检测算子函数调用格式:

Sobel(src, ddepth, dx, dy)

  参数 ddepth 表示输出图像的精度, dx,dy表示两个方向求导的阶次,比如:

  • dx=1,dy=0,表示计算x方向上的一阶导数;

  如果 dx=1,dy=1,那么计算图像在两个方面上的一阶导数Sobel图像。

  • Python
# Sobel Edge Detection
sobelx = cv2.Sobel(src=img_blur, ddepth=cv2.CV_64F, dx=1, dy=0, ksize=5) # Sobel Edge Detection on the X axis
sobely = cv2.Sobel(src=img_blur, ddepth=cv2.CV_64F, dx=0, dy=1, ksize=5) # Sobel Edge Detection on the Y axis
sobelxy = cv2.Sobel(src=img_blur, ddepth=cv2.CV_64F, dx=1, dy=1, ksize=5) # Combined X and Y Sobel Edge Detection

# Display Sobel Edge Detection Images
cv2.imshow('Sobel X', sobelx)
cv2.waitKey(0)

cv2.imshow('Sobel Y', sobely)
cv2.waitKey(0)

cv2.imshow('Sobel X Y using Sobel() function', sobelxy)
cv2.waitKey(0)
  • C++
// Sobel edge detection
Mat sobelx, sobely, sobelxy;
Sobel(img_blur, sobelx, CV_64F, 1, 0, 5);
Sobel(img_blur, sobely, CV_64F, 0, 1, 5);
Sobel(img_blur, sobelxy, CV_64F, 1, 1, 5);

// Display Sobel edge detection images
imshow("Sobel X", sobelx);
waitKey(0);
imshow("Sobel Y", sobely);
waitKey(0);
imshow("Sobel XY using Sobel() function", sobelxy);
waitKey(0);

  下面给出了处理结果。可以注意到X方向的Sobel图像是如何确定出垂直边缘(也就是那些在x方向,也就是水平方向上梯度变化大的像素)。 同样,y方向上(也就是垂直方向)的Sobel图像确定出水平边缘。仔细分辨一下两个图像中老虎皮上垂直的条纹。可以看到在x方向的Sobel图像中条纹更清晰。

▲ 图1.2.3  X方向上的Sobel边缘算子图像

▲ 图1.2.3 X方向上的Sobel边缘算子图像

▲ 图1.2.4  Y方向Sobel算子图像
▲ 图1.2.4 Y方向Sobel算子图像

  下图是两个方向上的Sobel图像,抽取了原始图像中的边缘结构,所以保持图像结构没有受到改变。

▲ 图1.2.5  XY两个方向的Sobel边缘图像

▲ 图1.2.5 XY两个方向的Sobel边缘图像

1.3 Canny边缘检测

  Canny边缘检测是当今最流行的边缘检测算法,这是因为它具有可靠性和灵活性。算法通过三个步骤抽取原始图像的边缘信息。 除了使用图像模糊化,还有一些其他必要的去除噪声的预处理过程。加上这一步骤,Canny算法就具有四个处理阶段了:
  1. 消除噪声;
  2. 计算图像的亮度梯度值;
  3. 减除虚假边缘;
  4. 带有滞回的阈值检测;

1.3.1 去除噪声

  原始图像的像素通常会产生边缘噪声,所以Canny边缘检测在计算边缘之前进行噪声去除非常重要。使用高斯模糊化可以去除大部分,或者尽量减少不必要的图像细节产生不必要的边缘。对比下面两张图片。右边的图像使用了高斯平滑。他显得模糊了,但还是保留了能够计算出边缘的重要细节。可以通过 这个链接 进一步了解图像平滑。

▲ 图1.3.1 对比原始图像和高斯平滑后的图像

▲ 图1.3.1 对比原始图像和高斯平滑后的图像

1.3.2 计算图像梯度

  在图像平滑后,通过使用Sobel水平和垂直卷积核对图像进行滤波。利用滤波结果可以同时计算出亮度梯度的幅值( G G G )和方向( θ \theta θ ), 下面给出了计算方法:
G = G x 2 + G y 2 G = \sqrt {G_x^2 + G_y^2 } G=Gx2+Gy2 θ = tan ⁡ − 1 ( G y G x ) \theta = \tan ^{ - 1} \left( {{{G_y } \over {G_x }}} \right) θ=tan1(GxGy)

  梯度方向被量化的最接近的45°倍数的数值。下面右图显示了这个组合步骤结果。

▲ 图1.3.2 Sobel滤波结果

▲ 图1.3.2 Sobel滤波结果

1.3.3 去除虚假边缘

  完成了图像中的噪声去除和亮度梯度计算,算法的这个步骤通过使用 non-maximum supperssion 来剔除不需要的像素(这些像素不是组成边缘的部分)。 通过比较每个像素与周围像素在水平和垂直两个方向上的梯度值来实现剔除虚假边缘。如果一个像素对应的梯度在局部是最大的,也就是比他的上下左右像素梯度都大,它就保留下来。否则将就该像素置为0. 下面图像显示了处理结果。正如你所见,皮毛中的很多边缘都被除去了。

▲ 图1.3.3 NON-MAXIMUM Suppresion结果

▲ 图1.3.3 NON-MAXIMUM Suppresion结果

1.3.4 带有滞回的阈值处理

  最后一步,梯度值与两个阈值进行比较。其中一个小于另外一个。

  • 如果图像梯度值比较大的阈值还大,代表这个像素是一个很强的边缘,它被保留在最后的边缘图中;
  • 如果提督府之小于较小的阈值,则改像素被抑制,从最终边缘图中去除;
  • 对于那些梯度值落在两个给定阈值范围之内的像素他们被标记为弱边缘(也就是作为最终边缘图的候选者);
  • 如果弱边缘与强边缘相连,他们最终会保留在边缘图中。

1.3.5 编程实现

  下面是调用OpenCV中Canny边缘检测函数语法:

Canny(image, threshold1, threshold2)

  下面代码中,Canny() 函数实现了上面描述的方法。我们只需给出两个用于边缘检测的阈值, OpenCV软件将算法其它细节都实现了。 不要忘了在使用Canny() 算法之前需要对图像进行平滑。强烈建议不要省略这一步骤。 通过OpenCV文档可以了解更多的可选参数。

  • Python
# Canny Edge Detection
edges = cv2.Canny(image=img_blur, threshold1=100, threshold2=200) 

# Display Canny Edge Detection Image
cv2.imshow('Canny Edge Detection', edges)
cv2.waitKey(0)
  • C++
// Canny edge detection
Mat edges;
Canny(img_blur, edges, 100, 200, 3, false);
// Display canny edge detected image
imshow("Canny edge detection", edges);
waitKey(0);

  下面就是最终的结果:其中低阈值为100,高阈值为200.你可以看到算法吧图像中的主要变换都确定出来,处理过程吧那些对整体结构不重要的部分都去除了。然后,对于不同的图像,尝试不同的平滑、阈值大小,获得Canny算法的经验。你可以看到输出结果很容易被算法中的参数所改变。

▲ 图1.3.4  Canny边缘检测结果

▲ 图1.3.4 Canny边缘检测结果

  由于同时使用了Sobel算子, Non-Maximum Suppression以及带有滞回阈值处理,所以Canny变换检测算法的性能是最好的。在算法最后一步的阈值参数也给算法有更强的灵活性。

 

法总结 ※


  文讨论了图像处理中重要的技术:边缘检测,重点介绍了两种方法(Sobel边缘检测和Canny边缘检测)。在展示OpenCV中的用法 同时也强调了为什么图像平滑是重要的预处理步骤。

  在Canny边缘检测中 也是使用的Sobel算子获得导数的数值解。通过应用非最大抑制以及滞回比较, Canny算法更加鲁棒和灵活。这就是为什么Canny算子最令人喜欢并被广泛使用在边缘检测中的原因。


■ 相关文献链接:

● 相关图表链接:

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐