C++ implements Canny edge detection (principle + underlying code)

1. Algorithm principle

Canny edge detection algorithm steps:
1. Use a Gaussian filter to smooth the image
2. Using the first-order partial derivative operatorFind the partial derivatives of the grayscale image along the horizontal direction Gx and the vertical direction Gy, andCalculate the magnitude and direction of the gradient
Find the amplitude formula:

3. Perform NMS non-maximum suppression on the gradient amplitude, to obtain the maximum value of the local gradient. In a 3X3 window, compare a given pixel P with two pixels along the direction of the gradient line, if the gradient magnitude of P is smaller than the gradient magnitude of the two pixels, set P=0; otherwise keep the original magnitude .
Remarks: Divide the gradient direction into 4 types to compare the strength of the gradient amplitude: horizontal direction, vertical direction, positive direction, and -45° direction. ==
4. Detection and edge connection with double threshold algorithm. There are three cases:
(1) If the pixel value is greater than the high threshold, then the pixel must be an edge pixel (strong edge point), set to 255; (2)
If it is less than the low threshold, it must not be, set to 0;
(3 ) If the pixel value is greater than the low threshold but less than the high threshold, then observe whether there is a pixel point greater than the high threshold among the (3X3) 8 neighboring pixels of the pixel, if so, the pixel is an edge pixel, and set the point to 255 , used to connect strong edge points; otherwise, the point is set to 0. Canny edge detection algorithm principle

2. Environment configuration

For details, please refer to this article of the blogger: [Deep learning environment configuration] Anaconda + Pycharm + CUDA +cuDNN + Pytorch + Opencv (resources have been uploaded)

3. Detailed Algorithm

3.1, data structure Mat

Opencv C++ basic data structure Mat

/*
函数说明:Mat(int rows, int cols, int type)
函数输入:		rows		代表行数
				cols		代表列数
				type		可以设置为:CV_8UC(n)、CV_8SC(n)、CV_16SC(n)、CV_16UC(n)、CV_32FC(n)、CV_32SC(n)、CV_64FC(n)
							其中:8U、8S、16S、16U、32S、32F、64F中的数字,代表Mat中每一个数值的位数
							U代表uchar类型、S代表int类型、F代表浮点型(32F为float、64F为double)其他类似。
备注:Mat代表矩阵,该类声明在头文件opencv2/core/core.hpp中
*/

3.2. C++ implementation of Gaussian filter

[Image processing] Gaussian blur, Gaussian function, Gaussian kernel, and Gaussian convolution operations.
First perform one-dimensional Gaussian convolution (row convolution) on a two-dimensional image in the horizontal direction, and then perform one-dimensional Gaussian convolution in the vertical direction (column convolution) product). In the same way: split the two-dimensional Gaussian convolution kernel into a one-dimensional Gaussian convolution kernel.

  • (1) Use a one-dimensional Gaussian convolution kernel on the image to filter in one direction (such as the horizontal direction);
  • (2) Transpose the image and use the same one-dimensional Gaussian convolution kernel to filter in the same direction; (due to transposition, it is actually filtering in the vertical direction)
  • (3) Transpose again to restore the image to its original position, and finally obtain a two-dimensional Gaussian filtered image.

3.3. Calculate the gradient magnitude and direction with the first-order partial derivative finite difference

insert image description here

3. Project actual combat: C++ implements Canny edge detection

insert image description here
Canny edge detection algorithm (C++ implementation)

#include <opencv2/opencv.hpp>
#include <math.h>
#include <corecrt_math_defines.h>
#define _USE_MATH_DEFINES

using namespace cv;		// 使用命名空间cv。例如:cv::Mat可以省略写为 Mat


/*
函数说明:将两个图像拼接,以便在同一个窗口显示
函数输入:	dst 		输出的拼接后的图像
            src1 		拼接的第一幅图
            src2 		拼接的第二幅图
*/
void mergeImg(Mat& dst, Mat& src1, Mat& src2)
{
    
    
    int rows = src1.rows;
    int cols = src1.cols + 5 + src2.cols;
    CV_Assert(src1.type() == src2.type());
    dst.create(rows, cols, src1.type());
    src1.copyTo(dst(Rect(0, 0, src1.cols, src1.rows)));
    src2.copyTo(dst(Rect(src1.cols + 5, 0, src2.cols, src2.rows)));
}


/*
函数说明:一维高斯卷积,对每行进行高斯卷积
函数输入:	img 		输入原图像
            dst 		一维高斯卷积后的输出图像
*/
void gaussianConvolution(Mat& img, Mat& dst)
{
    
    
    int nr = img.rows;
    int nc = img.cols;
    int templates[3] = {
    
     1, 2, 1 };

    // 按行遍历除每行边缘点的所有点
    for (int j = 0; j < nr; j++)
    {
    
    
        uchar* data = img.ptr<uchar>(j); 			//提取该行地址
        for (int i = 1; i < nc - 1; i++)
        {
    
    
            int sum = 0;
            for (int n = 0; n < 3; n++)
            {
    
    
                sum += data[i - 1 + n] * templates[n]; 	//相乘累加
            }
            sum /= 4;
            dst.ptr<uchar>(j)[i] = sum;
        }
    }
}


/*
函数说明:高斯滤波器,利用3*3的高斯模版进行高斯卷积
函数输入:	img 		输入原图像
            dst  		高斯滤波后的输出图像
*/
void gaussianFilter(Mat& img, Mat& dst)
{
    
    
    // 对水平方向进行滤波
    Mat dst1 = img.clone();
    gaussianConvolution(img, dst1);
    // 图像矩阵转置
    Mat dst2;
    transpose(dst1, dst2);

    // 对垂直方向进行滤波
    Mat dst3 = dst2.clone();
    gaussianConvolution(dst2, dst3);
    // 再次转置
    transpose(dst3, dst);
}


/*
函数说明:用一阶偏导有限差分计算梯度幅值和方向
函数输入:	img 		输入原图像
            gradXY 		输出的梯度幅值
            theta 		输出的梯度方向
*/
void getGrandient(Mat& img, Mat& gradXY, Mat& theta)
{
    
    
    gradXY = Mat::zeros(img.size(), CV_8U);
    theta = Mat::zeros(img.size(), CV_8U);

    for (int j = 1; j < img.rows - 1; j++)
    {
    
    
        for (int i = 1; i < img.cols - 1; i++)
        {
    
    
            double gradY = double(img.ptr<uchar>(j - 1)[i - 1] + 2 * img.ptr<uchar>(j - 1)[i] + img.ptr<uchar>(j - 1)[i + 1] - img.ptr<uchar>(j + 1)[i - 1] - 2 * img.ptr<uchar>(j + 1)[i] - img.ptr<uchar>(j + 1)[i + 1]);
            double gradX = double(img.ptr<uchar>(j - 1)[i + 1] + 2 * img.ptr<uchar>(j)[i + 1] + img.ptr<uchar>(j + 1)[i + 1] - img.ptr<uchar>(j - 1)[i - 1] - 2 * img.ptr<uchar>(j)[i - 1] - img.ptr<uchar>(j + 1)[i - 1]);

            gradXY.ptr<uchar>(j)[i] = sqrt(gradX * gradX + gradY * gradY); 		//计算梯度
            theta.ptr<uchar>(j)[i] = atan(gradY / gradX); 					//计算梯度方向
        }
    }
}


/*
函数说明:NMS非极大值抑制
函数输入:	gradXY 		输入的梯度幅值
            theta 		输入的梯度方向
            dst 		输出的经局部非极大值抑制后的图像
*/
void nonLocalMaxValue(Mat& gradXY, Mat& theta, Mat& dst)
{
    
    
    dst = gradXY.clone();
    for (int j = 1; j < gradXY.rows - 1; j++)
    {
    
    
        for (int i = 1; i < gradXY.cols - 1; i++)
        {
    
    
            double t = double(theta.ptr<uchar>(j)[i]);
            double g = double(dst.ptr<uchar>(j)[i]);
            if (g == 0.0)
            {
    
    
                continue;
            }
            double g0, g1;
            if ((t >= -(3 * M_PI / 8)) && (t < -(M_PI / 8)))
            {
    
    
                g0 = double(dst.ptr<uchar>(j - 1)[i - 1]);
                g1 = double(dst.ptr<uchar>(j + 1)[i + 1]);
            }
            else if ((t >= -(M_PI / 8)) && (t < M_PI / 8))
            {
    
    
                g0 = double(dst.ptr<uchar>(j)[i - 1]);
                g1 = double(dst.ptr<uchar>(j)[i + 1]);
            }
            else if ((t >= M_PI / 8) && (t < 3 * M_PI / 8))
            {
    
    
                g0 = double(dst.ptr<uchar>(j - 1)[i + 1]);
                g1 = double(dst.ptr<uchar>(j + 1)[i - 1]);
            }
            else
            {
    
    
                g0 = double(dst.ptr<uchar>(j - 1)[i]);
                g1 = double(dst.ptr<uchar>(j + 1)[i]);
            }

            if (g <= g0 || g <= g1)
            {
    
    
                dst.ptr<uchar>(j)[i] = 0.0;
            }
        }
    }
}


/*
函数说明:弱边缘点补充连接强边缘点
函数输入:img 弱边缘点补充连接强边缘点的输入和输出图像
 */
void doubleThresholdLink(Mat& img)
{
    
    
    // 循环找到强边缘点,把其领域内的弱边缘点变为强边缘点
    for (int j = 1; j < img.rows - 2; j++)
    {
    
    
        for (int i = 1; i < img.cols - 2; i++)
        {
    
    
            // 如果该点是强边缘点
            if (img.ptr<uchar>(j)[i] == 255)
            {
    
    
                // 遍历该强边缘点领域
                for (int m = -1; m < 1; m++)
                {
    
    
                    for (int n = -1; n < 1; n++)
                    {
    
    
                        // 该点为弱边缘点(不是强边缘点,也不是被抑制的0点)
                        if (img.ptr<uchar>(j + m)[i + n] != 0 && img.ptr<uchar>(j + m)[i + n] != 255)
                        {
    
    
                            img.ptr<uchar>(j + m)[i + n] = 255; //该弱边缘点补充为强边缘点
                        }
                    }
                }
            }
        }
    }

    for (int j = 0; j < img.rows - 1; j++)
    {
    
    
        for (int i = 0; i < img.cols - 1; i++)
        {
    
    
            // 如果该点依旧是弱边缘点,及此点是孤立边缘点
            if (img.ptr<uchar>(j)[i] != 255 && img.ptr<uchar>(j)[i] != 255)
            {
    
    
                img.ptr<uchar>(j)[i] = 0; //该孤立弱边缘点抑制
            }
        }
    }
}


/*
函数说明:用双阈值算法检测和连接边缘
函数输入:	low 		输入的低阈值
            high 		输入的高阈值
            img 		输入的原图像
            dst 		输出的用双阈值算法检测和连接边缘后的图像
 */
void doubleThreshold(double low, double high, Mat& img, Mat& dst)
{
    
    
    dst = img.clone();

    // 区分出弱边缘点和强边缘点
    for (int j = 0; j < img.rows - 1; j++)
    {
    
    
        for (int i = 0; i < img.cols - 1; i++)
        {
    
    
            double x = double(dst.ptr<uchar>(j)[i]);
            // 像素点为强边缘点,置255
            if (x > high)
            {
    
    
                dst.ptr<uchar>(j)[i] = 255;
            }
            // 像素点置0,被抑制掉
            else if (x < low)
            {
    
    
                dst.ptr<uchar>(j)[i] = 0;
            }
        }
    }

    // 弱边缘点补充连接强边缘点
    doubleThresholdLink(dst);
}


int main()
{
    
    
    // (1)读取图片
    Mat img = imread("test.jpg");
    if (img.empty())
    {
    
    
        printf("读取图像失败!");
        system("pause");
        return 0;
    }
    // 转换为灰度图
    Mat img_gray;
    if (img.channels() == 3)
        cvtColor(img, img_gray, COLOR_RGB2GRAY);
    else
        img_gray = img.clone();

    // (2)高斯滤波
    Mat gauss_img;
    gaussianFilter(img_gray, gauss_img);

    // (3)用一阶偏导有限差分计算梯度幅值和方向
    Mat gradXY, theta;
    getGrandient(gauss_img, gradXY, theta);

    // (4)NMS非极大值抑制
    Mat local_img;
    nonLocalMaxValue(gradXY, theta, local_img);

    // (5)用双阈值算法检测和连接边缘
    Mat dst;
    doubleThreshold(40, 80, local_img, dst);

    // (6)图像显示
    Mat outImg;
    //namedWindow("原始图", 0);
    //imshow("原始图", img);
    //namedWindow("灰度图", 0);
    //imshow("灰度图", img_gray);

    mergeImg(outImg, img_gray, dst); 		//图像拼接(维度需相同)
    namedWindow("img_gray");				//图窗名称
    imshow("img_gray", outImg);				//图像显示
    imwrite("canny.jpg", outImg);			//图像保存
    waitKey(); 								//等待键值输入
    return 0;
}

Guess you like

Origin blog.csdn.net/shinuone/article/details/129672919
Recommended