当前位置:   article > 正文

C++实现Canny边缘检测(原理+底层代码)

C++实现Canny边缘检测(原理+底层代码)

一、算法原理

canny边缘检测算法步骤:
1、使用高斯滤波器对图像进行平滑处理
2、利用一阶偏导算子找到灰度图像沿着水平方向Gx和垂直方向Gy的偏导数,并计算梯度的幅值和方向
求幅值公式:

3、对梯度幅值进行NMS非极大值抑制,获取局部梯度的最大值。在3X3窗口中,将给定像素P与沿着梯度线方向的两个像素进行比较,若P的梯度幅值小于该两个像素的梯度幅值,则令P=0;否则保留原幅值。
备注:将梯度方向分为4种来比较梯度幅值的强度:水平方向、垂直方向、正方向、-45°方向。==
4、用双阈值算法检测和边缘连接。分三种情况:
(1)若像素值大于高阈值,则该像素一定是边缘像素(强边缘点),置为255;
(2)若小于低阈值,则一定不是,置为0;
(3)若像素值大于低阈值但小于高阈值,则观察该像素的(3X3)8邻域像素中是否有大于高阈值的像素点,若有则该像素是边缘像素,并将该点置为255,用以连接强边缘点;否则不是,则该点置为0。Canny边缘检测算法原理

二、环境配置

详细请看博主这篇文章:【深度学习环境配置】Anaconda +Pycharm + CUDA +cuDNN + Pytorch + Opencv(资源已上传)

三、算法详解

3.1、数据结构 Mat

Opencv C++ 基本数据结构 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中
*/
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

3.2、高斯滤波器的C++实现

【图像处理】高斯模糊、高斯函数、高斯核、高斯卷积操作
将二维图像先按水平方向进行一维高斯卷积(行卷积),再按垂直方向进行一维高斯卷积(列卷积)。同理:将二维高斯卷积核拆分为一维高斯卷积核。

  • (1)对图像使用一维高斯卷积核,在一个方向上进行滤波(例如水平方向);
  • (2)将图像进行转置,并使用相同一维高斯卷积核,在相同方向上进行滤波;(由于转置,其实是对垂直方向进行滤波)
  • (3)再次转置,将图像还原为原来位置,最终得到二维高斯滤波后的图像。

3.3、用一阶偏导有限差分计算梯度幅值和方向

在这里插入图片描述

三、项目实战:C++实现Canny边缘检测

在这里插入图片描述
Canny边缘检测算法(C++实现)

#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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop】
推荐阅读
相关标签
  

闽ICP备14008679号