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

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



前言

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


一、Canny算子原理

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

二、C++实现Canny算子

1.步骤

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

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

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 文件

三、Canny算子运行结果

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

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

  • 2
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C++代码实现: ```cpp #include <iostream> #include <cmath> #include <algorithm> #include <opencv2/opencv.hpp> using namespace std; using namespace cv; // 高斯滤波 void gaussianBlur(Mat &image, int ksize, double sigma) { Mat kernel = getGaussianKernel(ksize, sigma, CV_64F); sepFilter2D(image, image, -1, kernel, kernel); } // sobel 算子计算水平方向和垂直方向梯度 void sobel(Mat &image, Mat &grad_x, Mat &grad_y) { Sobel(image, grad_x, CV_32F, 1, 0, 3); Sobel(image, grad_y, CV_32F, 0, 1, 3); } // 计算梯度幅值和方向 void gradient(Mat &grad_x, Mat &grad_y, Mat &grad_mag, Mat &grad_dir) { cartToPolar(grad_x, grad_y, grad_mag, grad_dir, true); } // 非最大抑制 void nonMaximumSuppression(Mat &grad_mag, Mat &grad_dir, Mat &grad_nms) { float pi = 3.14159265358979323846; grad_nms = Mat::zeros(grad_mag.size(), grad_mag.type()); for (int i = 1; i < grad_mag.rows - 1; i++) { for (int j = 1; j < grad_mag.cols - 1; j++) { float angle = grad_dir.at<float>(i, j); float m1, m2; // 比较梯度方向上的两个像素 if (angle < pi / 4 && angle >= -pi / 4) { m1 = grad_mag.at<float>(i, j - 1); m2 = grad_mag.at<float>(i, j + 1); } else if (angle < -pi / 4 && angle >= -3 * pi / 4) { m1 = grad_mag.at<float>(i - 1, j); m2 = grad_mag.at<float>(i + 1, j); } else if (angle < 3 * pi / 4 && angle >= pi / 4) { m1 = grad_mag.at<float>(i - 1, j - 1); m2 = grad_mag.at<float>(i + 1, j + 1); } else { m1 = grad_mag.at<float>(i - 1, j + 1); m2 = grad_mag.at<float>(i + 1, j - 1); } // 如果当前像素的梯度值不是最大值,就将其设为 0 if (grad_mag.at<float>(i, j) < m1 || grad_mag.at<float>(i, j) < m2) { grad_nms.at<float>(i, j) = 0; } else { grad_nms.at<float>(i, j) = grad_mag.at<float>(i, j); } } } } // 双阈值处理 void doubleThreshold(Mat &grad_nms, Mat &grad_thres, float low_threshold, float high_threshold) { grad_thres = Mat::zeros(grad_nms.size(), grad_nms.type()); for (int i = 0; i < grad_nms.rows; i++) { for (int j = 0; j < grad_nms.cols; j++) { float val = grad_nms.at<float>(i, j); if (val > high_threshold) { grad_thres.at<float>(i, j) = 255; } else if (val > low_threshold) { grad_thres.at<float>(i, j) = 127; } } } } // 连通域分析 void connectedComponents(Mat &grad_thres, Mat &edges, int min_size) { edges = Mat::zeros(grad_thres.size(), grad_thres.type()); vector<vector<Point>> contours; findContours(grad_thres, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE); for (int i = 0; i < contours.size(); i++) { if (contours[i].size() < min_size) { continue; } for (int j = 0; j < contours[i].size(); j++) { Point p = contours[i][j]; edges.at<float>(p) = 255; } } } int main() { Mat image = imread("test.jpg", IMREAD_GRAYSCALE); if (image.empty()) { cerr << "Failed to open image file!" << endl; return -1; } // 高斯滤波 Mat blurred; gaussianBlur(image, 5, 1.4); // sobel 算子计算梯度 Mat grad_x, grad_y; sobel(blurred, grad_x, grad_y); // 计算梯度幅值和方向 Mat grad_mag, grad_dir; gradient(grad_x, grad_y, grad_mag, grad_dir); // 非最大抑制 Mat grad_nms; nonMaximumSuppression(grad_mag, grad_dir, grad_nms); // 双阈值处理 float low_threshold = 35, high_threshold = 70; Mat grad_thres; doubleThreshold(grad_nms, grad_thres, low_threshold, high_threshold); // 连通域分析 int min_size = 10; Mat edges; connectedComponents(grad_thres, edges, min_size); // 显示结果 namedWindow("Original", WINDOW_AUTOSIZE); imshow("Original", image); namedWindow("Edges", WINDOW_AUTOSIZE); imshow("Edges", edges); waitKey(0); return 0; } ``` Matlab代码实现: ```matlab function edges = canny(image, low_threshold, high_threshold, min_size) % 高斯滤波 blurred = imgaussfilt(image, 1.4); % sobel 算子计算梯度 [grad_x, grad_y] = gradient(double(blurred)); % 计算梯度幅值和方向 grad_mag = hypot(grad_x, grad_y); grad_dir = atan2(grad_y, grad_x); % 非最大抑制 grad_nms = non_maximum_suppression(grad_mag, grad_dir); % 双阈值处理 grad_thres = double_threshold(grad_nms, low_threshold, high_threshold); % 连通域分析 edges = connected_components(grad_thres, min_size); end function grad_nms = non_maximum_suppression(grad_mag, grad_dir) [rows, cols] = size(grad_mag); grad_nms = zeros(rows, cols); pi = 3.14159265358979323846; for i = 2 : rows - 1 for j = 2 : cols - 1 angle = grad_dir(i, j); if angle < pi / 4 && angle >= -pi / 4 m1 = grad_mag(i, j - 1); m2 = grad_mag(i, j + 1); elseif angle < -pi / 4 && angle >= -3 * pi / 4 m1 = grad_mag(i - 1, j); m2 = grad_mag(i + 1, j); elseif angle < 3 * pi / 4 && angle >= pi / 4 m1 = grad_mag(i - 1, j - 1); m2 = grad_mag(i + 1, j + 1); else m1 = grad_mag(i - 1, j + 1); m2 = grad_mag(i + 1, j - 1); end if grad_mag(i, j) < m1 || grad_mag(i, j) < m2 grad_nms(i, j) = 0; else grad_nms(i, j) = grad_mag(i, j); end end end end function grad_thres = double_threshold(grad_nms, low_threshold, high_threshold) [rows, cols] = size(grad_nms); grad_thres = zeros(rows, cols); for i = 1 : rows for j = 1 : cols val = grad_nms(i, j); if val > high_threshold grad_thres(i, j) = 255; elseif val > low_threshold grad_thres(i, j) = 127; end end end end function edges = connected_components(grad_thres, min_size) % 取整数类型 grad_thres = uint8(grad_thres); % 连通域分析 cc = bwconncomp(grad_thres); num_pixels = cellfun(@numel, cc.PixelIdxList); % 标记连通域是否符合条件 labels = zeros(size(grad_thres)); for i = 1 : cc.NumObjects if num_pixels(i) < min_size continue end labels(cc.PixelIdxList{i}) = 1; end % 输出边缘图像 edges = uint8(labels) * 255; end ``` 以上代码均实现了高斯滤波、sobel算子计算梯度、计算梯度幅值和方向、非最大抑制、双阈值处理和连通域分析,可以实现 Canny 算子进行边缘检测

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值