概念讲解:

边缘检测算法是基于图像强度的一阶和二阶微分操作,但是操作时的导数对噪声比较敏感,所以边缘检测算法需要对源数据进行对应的处理,通常采用滤波来消除噪声。我们可以先进行高斯模板卷积,再使用高斯平滑滤波器降低噪声。

代码展示:

#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;

int main(int argc, char** argv) {
    Mat src;
    //载入原图像
    src = imread("C:/Users/fh/Pictures/001.jpg");
    if (!src.data) {
        printf("could not load image...\n");
        return -1;
    }
    //创建目标图像
    Mat gray_img, edge;
    //转换为灰度图像
    cvtColor(src, gray_img, COLOR_BGR2GRAY);
	//进行模糊处理(可以降噪)
    blur(gray_img, edge, Size(3, 3));
	//运行边缘算法处理
    Canny(edge, edge, 3, 9, 3);
    imshow("edge", edge);
    //Canny(gray_img, gray_img, 3, 9, 3);
    //imshow("gray_img", gray_img);
    waitKey(0);
    return 0;
}

主要使用的是Canny函数进行处理。

//函数模型
void Canny(InputArray image,OutputArray edges, double threshold1, double threshold2, int apertureSize=3,bool L2gradient=false )
函数说明:
  1. InputArray类型的image,输入图像,即源图像,填Mat类的对象即可,且需为单通道8位图像。
  2. OutputArray类型的edges,输出的边缘图,需要和源图片有一样的尺寸和类型。
  3. double类型的threshold1,第一个滞后性阈值【低阈值】。值越大,找到的边缘越少
  4. double类型的threshold2,第二个滞后性阈值【高阈值】。
  5. int类型的apertureSize,表示应用Sobel算子的孔径大小,其有默认值3。
  6. bool类型的L2gradient,一个计算图像梯度幅值的标识,有默认值false。

低于阈值1的像素点会被认为不是边缘;高于阈值2的像素点会被认为是边缘;在阈值1和阈值2之间的像素点,若与第2步得到的边缘像素点相邻,则被认为是边缘,否则被认为不是边缘。

算法详解
1.高斯平滑滤波

为了尽可能减少噪声对边缘检测结果的影响,所以必须滤除噪声以防止由噪声引起的错误检测。为了平滑图像,使用高斯滤波器与图像进行卷积,该步骤将平滑图像,以减少边缘检测器上明显的噪声影响。大小为(2k+1)x(2k+1)的高斯滤波器核的生成方程式由下式给出:

python opencv实现边缘分割 opencv图像边缘检测_算法


下面是一个sigma = 1.4,尺寸为3x3的高斯卷积核的例子(需要注意归一化):

python opencv实现边缘分割 opencv图像边缘检测_像素点_02


若图像中一个3x3的窗口为A,要滤波的像素点为e,则经过高斯滤波之后,像素点e的亮度值为:

python opencv实现边缘分割 opencv图像边缘检测_像素点_03


其中*为卷积符号,sum表示矩阵中所有元素相加求和。

重要的是需要理解,高斯卷积核大小的选择将影响Canny检测器的性能。尺寸越大,检测器对噪声的敏感度越低,但是边缘检测的定位误差也将略有增加。一般5x5是一个比较不错的trade off。

2.计算梯度强度和方向

图像中的边缘可以指向各个方向,因此Canny算法使用四个算子来检测图像中的水平、垂直和对角边缘。边缘检测的算子(如Roberts,Prewitt,Sobel等)返回水平Gx和垂直Gy方向的一阶导数值,由此便可以确定像素点的梯度G和方向theta 。

python opencv实现边缘分割 opencv图像边缘检测_计算机视觉_04


其中G为梯度强度, theta表示梯度方向,arctan为反正切函数。下面以Sobel算子为例讲述如何计算梯度强度和方向。x和y方向的Sobel算子分别为:

python opencv实现边缘分割 opencv图像边缘检测_计算机视觉_05


其中Sx表示x方向的Sobel算子,用于检测y方向的边缘; Sy表示y方向的Sobel算子,用于检测x方向的边缘(边缘方向和梯度方向垂直)。在直角坐标系中,Sobel算子的方向如下图所示。

python opencv实现边缘分割 opencv图像边缘检测_python opencv实现边缘分割_06


图3-1 Sobel算子的方向若图像中一个3x3的窗口为A,要计算梯度的像素点为e,则和Sobel算子进行卷积之后,像素点e在x和y方向的梯度值分别为:

python opencv实现边缘分割 opencv图像边缘检测_opencv_07


其中*为卷积符号,sum表示矩阵中所有元素相加求和。根据公式(3-2)便可以计算出像素点e的梯度和方向。

3. 非极大值抑制

非极大值抑制是一种边缘稀疏技术,非极大值抑制的作用在于“瘦”边。对图像进行梯度计算后,仅仅基于梯度值提取的边缘仍然很模糊。对于标准3,对边缘有且应当只有一个准确的响应。而非极大值抑制则可以帮助将局部最大值之外的所有梯度值抑制为0,对梯度图像中每个像素进行非极大值抑制的算法是:

  1. 将当前像素的梯度强度与沿正负梯度方向上的两个像素进行比较
  2. 如果当前像素的梯度强度与另外两个像素相比最大,则该像素点保留为边缘点,否则该像素点将被抑制

通常为了更加精确的计算,在跨越梯度方向的两个相邻像素之间使用线性插值来得到要比较的像素梯度,现举例如下:

python opencv实现边缘分割 opencv图像边缘检测_opencv_08


如图3-2所示,将梯度分为8个方向,分别为E、NE、N、NW、W、SW、S、SE,其中0代表0045o,1代表45090o,2代表-900-45o,3代表-4500o。像素点P的梯度方向为theta,则像素点P1和P2的梯度线性插值为:

python opencv实现边缘分割 opencv图像边缘检测_opencv_09


因此非极大值抑制的伪代码描写如下:

python opencv实现边缘分割 opencv图像边缘检测_python opencv实现边缘分割_10


需要注意的是,如何标志方向并不重要,重要的是梯度方向的计算要和梯度算子的选取保持一致。

4. 双阈值检测

在施加非极大值抑制之后,剩余的像素可以更准确地表示图像中的实际边缘。然而,仍然存在由于噪声和颜色变化引起的一些边缘像素。为了解决这些杂散响应,必须用弱梯度值过滤边缘像素,并保留具有高梯度值的边缘像素,可以通过选择高低阈值来实现。如果边缘像素的梯度值高于高阈值,则将其标记为强边缘像素;如果边缘像素的梯度值小于高阈值并且大于低阈值,则将其标记为弱边缘像素;如果边缘像素的梯度值小于低阈值,则会被抑制。阈值的选择取决于给定输入图像的内容。

双阈值检测的伪代码描写如下:

python opencv实现边缘分割 opencv图像边缘检测_算法_11


3.5 抑制孤立低阈值点

到目前为止,被划分为强边缘的像素点已经被确定为边缘,因为它们是从图像中的真实边缘中提取出来的。然而,对于弱边缘像素,将会有一些争论,因为这些像素可以从真实边缘提取也可以是因噪声或颜色变化引起的。为了获得准确的结果,应该抑制由后者引起的弱边缘。通常,由真实边缘引起的弱边缘像素将连接到强边缘像素,而噪声响应未连接。为了跟踪边缘连接,通过查看弱边缘像素及其8个邻域像素,只要其中一个为强边缘像素,则该弱边缘点就可以保留为真实的边缘。

抑制孤立边缘点的伪代码描述如下:

python opencv实现边缘分割 opencv图像边缘检测_算法_12