canny边缘检测实现(C++、opencv)

1.作用:

图像边缘信息主要集中在高频段,通常说图像锐化或检测边缘,实质就是高频滤波。我们知道微分运算是求信号的变化率,具有加强高频分量的作用。在空域运算中来说,对图像的锐化就是计算微分。对于数字图像的离散信号,微分运算就变成计算差分或梯度。图像处理中有多种边缘检测(梯度)算子,常用的包括普通一阶差分,Robert算子(交叉差分),Sobel算子等等,是基于寻找梯度强度。拉普拉斯算子(二阶差分)是基于过零点检测。通过计算梯度,设置阀值,得到边缘图像。

Canny边缘检测算子是一种多级检测算法。1986年由John F. Canny提出,同时提出了边缘检测的三大准则:

  1. 低错误率的边缘检测:检测算法应该精确地找到图像中的尽可能多的边缘,尽可能的减少漏检和误检。
  2. 最优定位:检测的边缘点应该精确地定位于边缘的中心。
  3. 图像中的任意边缘应该只被标记一次,同时图像噪声不应产生伪边缘。

Canny的工作本质是,从数学上表达前面的三个准则。因此Canny的步骤如下:

  1. 对输入图像进行高斯平滑,降低错误率。
  2. 计算梯度幅度和方向来估计每一点处的边缘强度与方向。
  3. 根据梯度方向,对梯度幅值进行非极大值抑制。本质上是对Sobel、Prewitt等算子结果的进一步细化。
  4. 用双阈值处理和连接边缘。

降噪

任何边缘检测算法都不可能在未经处理的原始数据上很好地处理,所以第一步是对原始数据与高斯平滑模板作卷积,得到的图像与原始图像相比有些轻微的模糊(blurred)。这样,单独的一个像素噪声在经过高斯平滑的图像上变得几乎没有影响。

寻找图像中的亮度梯度

图像中的边缘可能会指向不同的方向,所以Canny算法使用4个mask检测水平、垂直以及对角线方向的边缘。原始图像与每个mask所作的卷积都存储起来。对于每个点我们都标识在这个点上的最大值以及生成的边缘的方向。这样我们就从原始图像生成了图像中每个点亮度梯度图以及亮度梯度的方向。

在图像中跟踪边缘

较高的亮度梯度比较有可能是边缘,但是没有一个确切的值来限定多大的亮度梯度是边缘多大又不是,所以Canny使用了滞后阈值。

滞后阈值需要两个阈值——高阈值与低阈值。假设图像中的重要边缘都是连续的曲线,这样我们就可以跟踪给定曲线中模糊的部分,并且避免将没有组成曲线的噪声像素当成边缘。所以我们从一个较大的阈值开始,这将标识出我们比较确信的真实边缘,使用前面导出的方向信息,我们从这些真正的边缘开始在图像中跟踪整个的边缘。在跟踪的时候,我们使用一个较小的阈值,这样就可以跟踪曲线的模糊部分直到我们回到起点。

一旦这个过程完成,我们就得到了一个二值图像,每点表示是否是一个边缘点。

一个获得亚像素精度边缘的改进实现是在梯度方向检测二阶方向导数的过零点

它在梯度方向的三阶方向导数满足符号条件

其中表示用高斯核平滑原始图像得到的尺度空间表示L计算得到的偏导数。用这种方法得到的边缘片断是连续曲线,这样就不需要另外的边缘跟踪改进。滞后阈值也可以用于亚像素边缘检测。

2.实现:

#include <iostream>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
sobel算子/
//阶乘
int factorial(int n){int fac = 1;//0的阶乘if (n == 0)return fac;for (int i = 1; i <= n; ++i){fac *= i;}return fac;
}//获得Sobel平滑算子
cv::Mat getSobelSmoooth(int wsize){int n = wsize - 1;cv::Mat SobelSmooothoper = cv::Mat::zeros(cv::Size(wsize, 1), CV_32FC1);for (int k = 0; k <= n; k++){float *pt = SobelSmooothoper.ptr<float>(0);pt[k] = factorial(n) / (factorial(k)*factorial(n - k));}return SobelSmooothoper;
}//获得Sobel差分算子
cv::Mat getSobeldiff(int wsize){cv::Mat Sobeldiffoper = cv::Mat::zeros(cv::Size(wsize, 1), CV_32FC1);cv::Mat SobelSmoooth = getSobelSmoooth(wsize - 1);for (int k = 0; k < wsize; k++){if (k == 0)Sobeldiffoper.at<float>(0, k) = 1;else if (k == wsize - 1)Sobeldiffoper.at<float>(0, k) = -1;elseSobeldiffoper.at<float>(0, k) = SobelSmoooth.at<float>(0, k) - SobelSmoooth.at<float>(0, k - 1);}return Sobeldiffoper;
}//卷积实现
void conv2D(cv::Mat& src, cv::Mat& dst, cv::Mat kernel, int ddepth, cv::Point anchor = cv::Point(-1, -1), int delta = 0, int borderType = cv::BORDER_DEFAULT){cv::Mat  kernelFlip;cv::flip(kernel, kernelFlip, -1);cv::filter2D(src, dst, ddepth, kernelFlip, anchor, delta, borderType);
}//可分离卷积———先垂直方向卷积,后水平方向卷积
void sepConv2D_Y_X(cv::Mat& src, cv::Mat& dst, cv::Mat kernel_Y, cv::Mat kernel_X, int ddepth, cv::Point anchor = cv::Point(-1, -1), int delta = 0, int borderType = cv::BORDER_DEFAULT){cv::Mat dst_kernel_Y;conv2D(src, dst_kernel_Y, kernel_Y, ddepth, anchor, delta, borderType); //垂直方向卷积conv2D(dst_kernel_Y, dst, kernel_X, ddepth, anchor, delta, borderType); //水平方向卷积
}//可分离卷积———先水平方向卷积,后垂直方向卷积
void sepConv2D_X_Y(cv::Mat& src, cv::Mat& dst, cv::Mat kernel_X, cv::Mat kernel_Y, int ddepth, cv::Point anchor = cv::Point(-1, -1), int delta = 0, int borderType = cv::BORDER_DEFAULT){cv::Mat dst_kernel_X;conv2D(src, dst_kernel_X, kernel_X, ddepth, anchor, delta, borderType); //水平方向卷积conv2D(dst_kernel_X, dst, kernel_Y, ddepth, anchor, delta, borderType); //垂直方向卷积
}//Sobel算子边缘检测
//dst_X 垂直方向
//dst_Y 水平方向
void Sobel(cv::Mat& src, cv::Mat& dst_X, cv::Mat& dst_Y, cv::Mat& dst, int wsize, int ddepth, cv::Point anchor = cv::Point(-1, -1), int delta = 0, int borderType = cv::BORDER_DEFAULT){cv::Mat SobelSmooothoper = getSobelSmoooth(wsize); //平滑系数cv::Mat Sobeldiffoper = getSobeldiff(wsize); //差分系数//可分离卷积———先垂直方向平滑,后水平方向差分——得到垂直边缘sepConv2D_Y_X(src, dst_X, SobelSmooothoper.t(), Sobeldiffoper, ddepth);//可分离卷积———先水平方向平滑,后垂直方向差分——得到水平边缘sepConv2D_X_Y(src, dst_Y, SobelSmooothoper, Sobeldiffoper.t(), ddepth);//边缘强度(近似)dst = abs(dst_X) + abs(dst_Y);cv::convertScaleAbs(dst, dst); //求绝对值并转为无符号8位图
}//确定一个点的坐标是否在图像内
bool checkInRang(int r,int c, int rows, int cols){if (r >= 0 && r < rows && c >= 0 && c < cols)return true;elsereturn false;
}//从确定边缘点出发,延长边缘
void trace(cv::Mat &edgeMag_noMaxsup, cv::Mat &edge, float TL,int r,int c,int rows,int cols){if (edge.at<uchar>(r, c) == 0){edge.at<uchar>(r, c) = 255;for (int i = -1; i <= 1; ++i){for (int j = -1; j <= 1; ++j){float mag = edgeMag_noMaxsup.at<float>(r + i, c + j);if (checkInRang(r + i, c + j, rows, cols) && mag >= TL)trace(edgeMag_noMaxsup, edge, TL, r + i, c + j, rows, cols);}}}
}//Canny边缘检测
void Edge_Canny(cv::Mat &src, cv::Mat &edge, float TL, float TH, int wsize=3, bool L2graydient = false){int rows = src.rows;int cols = src.cols;//高斯滤波cv::GaussianBlur(src,src,cv::Size(5,5),0.8);//sobel算子cv::Mat dx, dy, sobel_dst;Sobel(src, dx, dy, sobel_dst, wsize, CV_32FC1);//计算梯度幅值cv::Mat edgeMag;if (L2graydient)   cv::magnitude(dx, dy, edgeMag); //开平方else  edgeMag = abs(dx) + abs(dy); //绝对值之和近似//计算梯度方向 以及 非极大值抑制cv::Mat edgeMag_noMaxsup = cv::Mat::zeros(rows, cols, CV_32FC1);for (int r = 1; r < rows - 1; ++r){for (int c = 1; c < cols - 1; ++c){float x = dx.at<float>(r, c);float y = dy.at<float>(r, c);float angle = std::atan2f(y, x) / CV_PI * 180; //当前位置梯度方向float mag = edgeMag.at<float>(r, c);  //当前位置梯度幅值//非极大值抑制//垂直边缘--梯度方向为水平方向-3*3邻域内左右方向比较if (abs(angle)<22.5 || abs(angle)>157.5){float left = edgeMag.at<float>(r, c - 1);float right = edgeMag.at<float>(r, c + 1);if (mag >= left && mag >= right)edgeMag_noMaxsup.at<float>(r, c) = mag;}//水平边缘--梯度方向为垂直方向-3*3邻域内上下方向比较if ((angle>=67.5 && angle<=112.5 ) || (angle>=-112.5 && angle<=-67.5)){float top = edgeMag.at<float>(r-1, c);float down = edgeMag.at<float>(r+1, c);if (mag >= top && mag >= down)edgeMag_noMaxsup.at<float>(r, c) = mag;}//+45°边缘--梯度方向为其正交方向-3*3邻域内右上左下方向比较if ((angle>112.5 && angle<=157.5) || (angle>-67.5 && angle<=-22.5)){float right_top = edgeMag.at<float>(r - 1, c+1);float left_down = edgeMag.at<float>(r + 1, c-1);if (mag >= right_top && mag >= left_down)edgeMag_noMaxsup.at<float>(r, c) = mag;}//+135°边缘--梯度方向为其正交方向-3*3邻域内右下左上方向比较if ((angle >=22.5 && angle < 67.5) || (angle >= -157.5 && angle < -112.5)){float left_top = edgeMag.at<float>(r - 1, c - 1);float right_down = edgeMag.at<float>(r + 1, c + 1);if (mag >= left_top && mag >= right_down)edgeMag_noMaxsup.at<float>(r, c) = mag;}}}//双阈值处理及边缘连接edge = cv::Mat::zeros(rows, cols, CV_8UC1);for (int r = 1; r < rows - 1; ++r){for (int c = 1; c < cols - 1; ++c){float mag = edgeMag_noMaxsup.at<float>(r, c);//大于高阈值,为确定边缘点if (mag >= TH)trace(edgeMag_noMaxsup, edge, TL, r, c, rows, cols);else if (mag < TL)edge.at<uchar>(r, c) = 0;}}
}int main(){cv::Mat src = cv::imread("I:\\Learning-and-Practice\\2019Change\\Image process algorithm\\Img\\lena.jpg");if (src.empty()){return -1;}if (src.channels() > 1) cv::cvtColor(src, src, CV_RGB2GRAY);cv::Mat edge,dst;//CannyEdge_Canny(src, edge, 20,60);//opencv自带Cannycv::Canny(src, dst, 20, 80);cv::namedWindow("src", CV_WINDOW_NORMAL);imshow("src", src);cv::namedWindow("My_canny", CV_WINDOW_NORMAL);imshow("My_canny", edge);cv::namedWindow("Opencv_canny", CV_WINDOW_NORMAL);imshow("Opencv_canny", dst);cv::waitKey(0);return 0;
}
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/video/background_segm.hpp>
#include "opencv2/calib3d/calib3d.hpp"
#include <opencv2/opencv.hpp>using namespace std;
using namespace cv;int main(int argc, char** argv) {cv::Mat temp_thin = cv::imread("../example/2.jpg", CV_LOAD_IMAGE_UNCHANGED);//cv::imshow("temp_thin",temp_thin);cv::Mat gray_image, dst, temp_thin_image, binary_image, edge_image;cvtColor(temp_thin, gray_image, CV_RGB2GRAY);GaussianBlur(gray_image, edge_image, Size(3, 3), 0, 0);//blur(gray, edge, Size(3, 3));Canny(edge_image, edge_image, 10, 150, 3, false);cv::imshow("edge_image", edge_image);std:; string anme_pic = "..\\example\\edge_image.bmp";cv::imwrite(anme_pic, edge_image);cv::waitKey(0);return 0;
}

3.效果

4.函数原型

void Canny(InputArray image, OutputArray edges, double threshold1, 
double threshold2, int apertureSize=3, bool L2gradient=false )

5.原理

  • 须满足条件:抑制噪声;精确定位边缘。
  • 从数学上表达了三个准则[信噪比准则(低错误率)、定位精度准则、单边缘响应准则],并寻找表达式的最佳解。
  • 属于先平滑后求导的方法。

1、高斯平滑滤波(略)

2、计算图像梯度的幅值和方向

可选用的模板:soble算子、Prewitt算子、一阶差分卷积模板等等;

在此选用Prewitt算子为例:

3、对幅值图像进行非极大值抑制

首先将角度划分成四个方向范围 :水平(0°)、−45°、垂直(90°)、+45°。如下图:

扇形区标号d1~d4,对应3*3领域的4种可能的组合,1-x-5 , 7-x-3 , 2-x-6 , 8-x-4。

在每一点上,领域中心 x 与沿着其对应的梯度方向的两个像素相比,若中心像素为最大值,则保留,否则中心置0,这样可以抑制非极大值,保留局部梯度最大的点,以得到细化的边缘。

4、用双阈值算法检测和连接边缘

选取系数TH和TL,比率为2:1或3:1。(一般取TH=0.3或0.2,TL=0.1);
取出非极大值抑制后的图像中的最大梯度幅值,定义高低阈值。即:TH×Max,TL×Max (当然可以自己给定) ;
将小于低阈值的点抛弃,赋0;将大于高阈值的点立即标记(这些点就是边缘点),赋1;
将小于高阈值,大于低阈值的点使用8连通区域确定(即:只有与TH像素连接时才会被接受,成为边缘点,赋  1)。

6.参考

【1】 https://blog.csdn.net/weixin_40647819/article/details/91411424

【2】https://blog.csdn.net/likezhaobin/article/details/6892176

【3】https://docs.opencv.org/master/dd/d1a/group__imgproc__feature.html

【4】https://blog.csdn.net/weixin_40647819/article/details/80377343

【5】https://blog.csdn.net/liuzhuomei0911/article/details/51345591

OpenCV 【七】————边缘提取算子(图像边缘提取)——canny算法的原理及实现相关推荐

  1. OpenCV中的图像处理 —— 图像梯度+Canny边缘检测+图像金字塔

    OpenCV中的图像处理 -- 图像梯度+Canny边缘检测+图像金字塔 目录 OpenCV中的图像处理 -- 图像梯度+Canny边缘检测+图像金字塔 1. 图像梯度 1.1 Sobel和Schar ...

  2. 【OpenCV 4开发详解】Canny算法

    本文首发于"小白学视觉"微信公众号,欢迎关注公众号 本文作者为小白,版权归人民邮电出版社发行所有,禁止转载,侵权必究! 经过几个月的努力,小白终于完成了市面上第一本OpenCV 4 ...

  3. java边缘检测算子代码_图像边缘检测(Canny 算法)的Java实现

    快速排序 一. 算法描述 快速排序是对冒泡排序的一种改进.在冒泡排序中,记录每次都是与相邻位置上的数据作比较,因此每次只能移动一个位置.而在快速排序中,记录的比较和移动都是从两端向中间进行的. 其主要 ...

  4. 《Single Image Haze Removal Using Dark Channel Prior》一文中图像去雾算法的原理、实现、效果及其他。...

    在图像去雾这个领域,几乎没有人不知道<Single Image Haze Removal Using Dark Channel Prior>这篇文章,该文是2009年CVPR最佳论文.作者 ...

  5. 《Single Image Haze Removal Using Dark Channel Prior》一文中图像去雾算法的原理、实现、效果(速度可实时)...

    最新的效果见 :http://video.sina.com.cn/v/b/124538950-1254492273.html 可处理视频的示例:视频去雾效果 在图像去雾这个领域,几乎没有人不知道< ...

  6. 《Single Image Haze Removal Using Dark Channel Prior》一文中图像去雾算法的原理、实现、效果及其他。 ...

    在图像去雾这个领域,几乎没有人不知道<Single Image Haze Removal Using Dark Channel Prior>这篇文章,该文是2009年CVPR最佳论文.作者 ...

  7. 图像去雾算法的原理、实现、效果

    图像的去雾算法原理及实现: 本文主要是实现的是基于暗通道处理的去雾.有部分是看论文直接翻译而来,如有错误,欢迎评论区指出,当然您也可以直接阅读原文. 一.原理 暗通道先验解释: 说明:何凯明博士的论文 ...

  8. 《OpenCv视觉之眼》Python图像处理十四 :Opencv图像轮廓提取之Scharr算法和Canny算法

    本专栏主要介绍如果通过OpenCv-Python进行图像处理,通过原理理解OpenCv-Python的函数处理原型,在具体情况中,针对不同的图像进行不同等级的.不同方法的处理,以达到对图像进行去噪.锐 ...

  9. 【OpenCV 4开发详解】分割图像——Mean-Shift分割算法

    本文首发于"小白学视觉"微信公众号,欢迎关注公众号 本文作者为小白,版权归人民邮电出版社发行所有,禁止转载,侵权必究! 经过几个月的努力,小白终于完成了市面上第一本OpenCV 4 ...

最新文章

  1. 问号和星号 php,星号和问号两个通配符
  2. Silverlight中的ControlTemplate(2)
  3. 【Python】Conda的安装
  4. hdu 2049 考新郎
  5. java calendar_Java Calendar getDisplayNames()方法与示例
  6. python网站设计开题报告_网站设计开题报告范文精选5篇
  7. 周末舞会(信息学奥赛一本通-T1332)
  8. TopicDeletionManager分析
  9. elasticsearch使用Filter过滤查询操作(使用marvel插件)
  10. [转载] python classmethod存在的意义_@classmethod和@staticmethod对初学者的意义?
  11. 循环数组的动态规划问题
  12. python dtype o_python – 为什么dtype = str的空数据框填充“n”?
  13. Servlet过滤器和监听器知识总结
  14. 微星X79主板修改BIOS支持NVMe
  15. 使用daemontools监控zookeeper,storm等进程
  16. 线性系统理论2 系统状态和状态空间
  17. 胡嘉伟 :实时计算在提升播放体验的应用实践
  18. Jetson TK1
  19. 华为、魅族手机不显示Log的原因
  20. Silent Install Builder制作静默安装包实现软件自动部署

热门文章

  1. php增删改查前后端分离,前后端分离之前端增删改查
  2. python数据分析要学什么_python数据分析学什么?python数据分析入门
  3. java 判断页面刷新_如何判断一个网页是刷新还是关闭的方法
  4. 如何获取元素在父级div里的位置_前端面试题--元素的BFC特性和实例
  5. debian php mysql 包_Linux+Varnish+Apache+MySQL+PHP一键包For Ubuntu/Debian
  6. 使用css实现瀑布流的效果
  7. 三维点云课程第一章:应用
  8. PCL:超详细的基于法向量和曲率的区域生长算法原理以及源码解读
  9. GitHub开源:一键生成前后端代码神器
  10. PCA(Principal Component Analysis)的原理、算法步骤和实现。