当前位置:   article > 正文

C++ Canny算子进行边缘检测

C++ Canny算子进行边缘检测

C++实现Canny算子 进行边缘检测



前言

使用C++和opencv实现Canny算子进行边缘检测


一、Canny算子原理

先利用高斯函数对图像进行低通滤波;然后对图像中的每个像素进行处理,寻找边缘的位置及在该位置的边缘法向,并采用一种称之为“非极值抑制”的技术在边缘法向寻找局部最大值;最后对边缘图像做滞后阈值化处理,消除虚假响应。

二、C++实现Canny算子

1.步骤

Canny边缘检测算法可以分为以下5个步骤:

  1.    使用高斯滤波器,以平滑图像,滤除噪声。
    
    • 1
  2.    计算图像中每个像素点的梯度强度和方向。
         Gx = [ f(x+1,y-1)+2*f(x+1,y)+f(x+1,y+1)] - [f(x-1,y-1)+2*f(x-1,y)+f(x-1,y+1) ]
         Gy = [ f(x-1,y-1) + 2f(x,y-1) + f(x+1,y-1)] - [f(x-1, y+1) + 2*f(x,y+1)+f(x+1,y+1)
    
    • 1
    • 2
    • 3

梯度方向示意图:
在这里插入图片描述

  1.    应用非极大值(Non-Maximum Suppression)抑制,以消除边缘检测带来的杂散响应。
    
    • 1
  2.    应用双阈值(Double-Threshold)检测来确定真实的和潜在的边缘。
    
    • 1
  3.    通过抑制孤立的弱边缘最终完成边缘检测。
    
    • 1

2.C++代码如下

// Canny.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <iostream>
#include<string>
#include<cmath>
#include<vector>
#include<gdal.h>
#include<opencv2/highgui/highgui.hpp>
#include<opencv2/opencv.hpp>
#include "cpl_conv.h"
#include<algorithm>

#include<cstring>

using namespace std;
using namespace cv;

#define PI 3.1415;

void Canny(Mat img,Mat img2,string str2)
{
    int r, c;
    int rows = img.rows - 1;
    int cols = img.cols - 1;
    int add, des;
    int IX, IY,gx,gy;//xy方向梯度
    float m;//模,
    double angle_atanValue;
    double arctan;//arctan
    double angle;//方向

    Mat Gauss(rows+1,cols+1,CV_32FC1,Scalar::all(0));
   // 
    Mat Gray(rows + 1, cols + 1, CV_8UC1, Scalar::all(0)); //= 0.299R + 0.587G + 0.114B
    
    //step1:高斯滤波
    //Canny算子通常处理的图像是灰度图像,若不是灰度图像,先对彩色图像灰度化,再高斯滤波处理
    //cvtColor(img, Gray, COLOR_BGR2GRAY);//灰度化
    GaussianBlur(img, Gauss, Size(5, 5), 0,0);
    //imshow("Gauss", Gauss);
    //cv::waitKey(0);

   //step2:计算梯度和方向
    Mat GX(rows + 1, cols + 1, CV_32SC1, Scalar::all(0));//x梯度矩阵
    Mat GY(rows + 1, cols + 1, CV_32SC1, Scalar::all(0));//y梯度矩阵
    Mat M(rows+1,cols+1,CV_32FC1,Scalar::all(0));//模矩阵

   // Mat Angle(rows + 1, cols + 1, CV_32FC1, Scalar::all(0));//角度矩阵
    Mat Direction(rows + 1, cols + 1, CV_32SC1, Scalar::all(0));//方向矩阵:0、1、2、3
    for ( r = 0; r < rows+1; r++)
    {
        for ( c = 0; c < cols+1; c++)
        {
            add = 1, des = 1;
            if (r == 0 || c == 0) des = 0;
            if (r == rows || c == cols) add = 0;
           // Gx = [f(x + 1, y - 1) + 2 * f(x + 1, y) + f(x + 1, y + 1)] - [f(x - 1, y - 1) + 2 * f(x - 1, y) + f(x - 1, y + 1)]
           //Gy = [f(x - 1, y - 1) + 2f(x, y - 1) + f(x + 1, y - 1)] - [f(x - 1, y + 1) + 2 * f(x, y + 1) + f(x + 1, y + 1)
            gx = (Gauss.at<uchar>(r+add,c-des)+2* Gauss.at<uchar>(r+add,c)+ Gauss.at<uchar>(r+add,c+add))-(Gauss.at<uchar>(r -des, c - des) + 2 * Gauss.at<uchar>(r-des, c) + Gauss.at<uchar>(r-des, c + add));
            gy = (Gauss.at<uchar>(r -des, c - des) + 2 * Gauss.at<uchar>(r , c-des) + Gauss.at<uchar>(r + add, c -des)) - (Gauss.at<uchar>(r -des, c +add) + 2 * Gauss.at<uchar>(r , c+add) + Gauss.at<uchar>(r + add, c + add));
      
            GX.at<int>(r, c) = gx;//计算xy方向梯度
            GY.at<int>(r, c) = gy;
            m = sqrt(pow(gx, 2) + pow(gy, 2));//计算模
            M.at<float>(r, c) = m;
            //cout << "gx:" << gx<< "gy:" << gy <<"m:"<<m<< endl;

            //计算梯度方向:arctan(gx/gy):范围:-180~180°
            //注:0°、180°为0;45°、-135°为1;90°、-90°为2;135°、-45°为3;
            if (gy == 0 )//除数不能等于0
            {
                angle = 0;//当gy=0时,角度是0度

            }
            else
            {
                angle_atanValue = gx / gy;
                arctan =atan(angle_atanValue);//计算arctan值
                //可以看出:atan函数输出的是弧度! 如果想进行atan运算得到角度,需要乘以(180/PI)把弧度转为角度"
                angle = arctan * 180 / PI;
            }
           // Angle.at<float>(r, c) = angle;//将角度存入矩阵
           //cout << "angle:" << angle << endl;
            //角度近似,分成四个方向
            if ((angle> -22.5 && angle<=22.5)||(angle >=-180 && angle < -157.5)||(angle>157.5 && angle<=180))
            {
                Direction.at<int>(r, c) = 0;//-22.5°~22.5°和-157.5°~-180°、157.5~180方向设置为0
            }
            else if ((angle > 22.5 && angle <= 67.5) || (angle >= -67.5 && angle < -22.5))
            {
                Direction.at<int>(r, c) = 1;//22.5°~67.5°和-22.5°~-67.5°方向设置为1
            }
            else if ((angle >67.5 && angle <= 112.5) || (angle >= -112.5 && angle < -67.5))
            {
                Direction.at<int>(r, c) = 2;//67.5°~112.5°和247.5°~292.5°方向设置为2
            }
            else {
                Direction.at<int>(r, c) = 3;//112.5°~157.5°和-112.5°~-157.5°方向设置为3
            }
            //cout << "Direction.at<int>(r, c):"<< Direction.at<int>(r, c) <<endl;

        }

    }
    
    //step3:梯度方向局部非最大抑制
    //至此我们得到了整幅图像上所有像素的方向


    //int winR, winC;//窗口内的位置
    //int size;//窗口大小
    //Mat DX(rows + 1, cols + 1, CV_32SC1, Scalar::all(0));//用来存放梯度方向差值
    for  (r = 0; r < rows+1; r++)
    {
        for ( c = 0; c < cols+1; c++)//遍历整幅图像
        {
            int d,d1,d2;//用来暂存梯度值
            /*for ( winR = 0; winR < size; winR++)//窗口内比较梯度
            {
                for ( winC = 0; winC < size; winC++)
                {
                    */
                    //cout << "lalala" << endl;
                    if (r == 0 || c == 0) des = 0;
                    if (r == rows || c == cols) add = 0;

                    if (Direction.at<int>(r, c) ==0)//如果是0方向上,对f(x,y),f(x,y+1),f(x,y-1)的灰度值进行比大小,看f(x,y)是不是最大值
                    {
                        //cout << "Direction.at<int>(r, c)" << Direction.at<int>(r, c) << endl;
                        d = Gauss.at<uchar>(r , c );
                        d1 = Gauss.at<uchar>(r , c  - des);
                        d2 = Gauss.at<uchar>(r , c + add);
                        if (d<d1 || d<d2)
                        {
                            Gauss.at<uchar>(r , c ) = 0;//如果不是最大,将像素设为0
                           // cout << "r:"<<r<<"c:"<<c<<"Gauss.at<float>(r , c ):"<< Gauss.at<uchar>(r, c) << endl;
                        }
                    }
                    else if (Direction.at<int>(r , c ) == 1)//如果是1方向上,对f(x,y),f(x+1,y-1),f(x-1,y+1)的灰度值进行比大小,看f(x,y)是不是最大值
                    {
                        d = Gauss.at<uchar>(r , c );
                        d1 = Gauss.at<uchar>(r  +add, c  - des);
                        d2 = Gauss.at<uchar>(r -des, c + add);
                        if (d < d1 || d < d2)
                        {
                            Gauss.at<uchar>(r, c) = 0;//如果不是最大,将像素设为0
                          //  cout << "r:" << r << "c:" << c << "Gauss.at<float>(r , c ):" << Gauss.at<uchar>(r, c) << endl;
                        }
                    }
                    else if (Direction.at<int>(r , c ) == 2)//如果是2方向上,对f(x,y),f(x+1,y),f(x-1,y)的灰度值进行比大小,看f(x,y)是不是最大值
                    {
                        d = Gauss.at<uchar>(r , c );
                        d1 = Gauss.at<uchar>(r + add, c );
                        d2 = Gauss.at<uchar>(r  - des, c  );
                        if (d < d1 || d < d2)
                        {
                            Gauss.at<uchar>(r, c) = 0;//如果不是最大,将像素设为0
                            //cout << "r:" << r << "c:" << c << "Gauss.at<float>(r , c ):" << Gauss.at<uchar>(r, c) << endl;
                        }
                    }
                    else//如果是3方向上,对f(x,y),f(x+1,y+1),f(x-1,y-1)的灰度值进行比大小,看f(x,y)是不是最大值
                    {
                        d = Gauss.at<uchar>(r, c );
                        d1 = Gauss.at<uchar>(r + add, c  +add);
                        d2 = Gauss.at<uchar>(r  - des, c  -des);
                        if (d < d1 || d < d2)
                        {
                            Gauss.at<uchar>(r, c) = 0;//如果不是最大,将像素设为0
                           // cout << "r:" << r << "c:" << c << "img.at<float>(r , c ):" << Gauss.at<uchar>(r, c) << endl;
                        }
                    }
                   // cout << "Gauss.at<float>(r, c):" << Gauss.at<uchar>(r, c) << endl;
             /*   }
            }*/
        }

    }
    //至此我们重新构造了图像,其中可疑边缘处的像素仍保留像素原值,非边缘位置像素为0
    //step4:双阈值和链接边缘
    cout << "step4" << endl;
    float high_value=30.0;//高阈值
    float low_value=20.0;//低阈值
    Mat Location(rows + 1, cols + 1, CV_32SC1, Scalar::all(0));//可疑边缘点的位置
    //int add=1, des = 1;
    for ( r = 0; r < rows+1; r++)
    {
        for ( c = 0; c < cols+1; c++)
        {
            add = 1,des = 1;
            if (r == 0 || c == 0) des = 0;
            if (r == rows || c == cols) add = 0;
            float m1,m2,m3,m4,m5,m6,m7,m8,m9,maxm;//暂存梯度值
            if (Gauss.at<uchar>(r, c) != 0)//是可疑点时
            {
                //cout << "判断可疑点" << endl;
                m5 = M.at<float>(r, c);
                if (m5 >= high_value)
                {
                     //强边缘点
                    Location.at<int>(r,c) = 1;
                }
                else if (m5 <= low_value)
                {
                    //不是边缘点
                    Location.at<int>(r, c) =0;
                    Gauss.at<uchar>(r, c) = 0;
                }
                else
                {
                    //如果low_value < m <high_value
                    m1= M.at<float>(r - des, c-des);
                    m2= M.at<float>(r , c - des);
                    m3 = M.at<float>(r + add, c - des);
                    m4 = M.at<float>(r - des, c );
                    m6 = M.at<float>(r + add, c );
                    m7 = M.at<float>(r - des, c +add);
                    m8 = M.at<float>(r , c + add);
                    m9 = M.at<float>(r +add, c + add);
                    maxm = MAX(m1,m2,m3,m4,m5,m6,m7,m8,m9);
                    if (maxm>high_value)
                    {
                        Gauss.at<uchar>(r, c) = 1;
                    }
                    else
                    {
                        Gauss.at<uchar>(r, c) = 0;
                    }

                }
            }
        }

    }
    namedWindow("Canny",WINDOW_NORMAL);
    imshow("Canny", Gauss);
    imwrite(str2, Gauss);
    cv::waitKey(0);


}


   


int main()
{
    string str = "D://学习课件//数字摄影测量//实验指导//实验指导//实验数据//实验2//DJI_0011.JPG";
    string str2= "D://学习课件//数字摄影测量//实验指导//实验指导//Canny4.JPG";
    Mat img, img2;
    img = imread(str,IMREAD_GRAYSCALE);
    img2 = imread(str,IMREAD_COLOR);
    if (img.empty() || img2.empty())
    {
        cout << "无法打开图像" << endl;
    }
    else
    {
        cout << "OKKKKKKKKK" << endl;
    }
    Canny(img,img2,str2);
    std::cout << "Hello World!\n";
}

// 运行程序: Ctrl + F5 或调试 >“开始执行(不调试)”菜单
// 调试程序: F5 或调试 >“开始调试”菜单

// 入门使用技巧: 
//   1. 使用解决方案资源管理器窗口添加/管理文件
//   2. 使用团队资源管理器窗口连接到源代码管理
//   3. 使用输出窗口查看生成输出和其他消息
//   4. 使用错误列表窗口查看错误
//   5. 转到“项目”>“添加新项”以创建新的代码文件,或转到“项目”>“添加现有项”以将现有代码文件添加到项目
//   6. 将来,若要再次打开此项目,请转到“文件”>“打开”>“项目”并选择 .sln 文件

  • 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

三、Canny算子运行结果

原图:
在这里插入图片描述

结果:
在这里插入图片描述

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/代码探险家/article/detail/846118
推荐阅读
相关标签
  

闽ICP备14008679号