利用OpenCV进行边缘检测

article/2025/9/15 12:31:26

简 介: 本文讨论了图像处理中重要的技术:边缘检测,重点介绍了两种方法(Sobel边缘检测和Canny边缘检测)。在展示OpenCV中的用法 同时也强调了为什么图像平滑是重要的预处理步骤。在Canny边缘检测中 也是使用的Sobel算子获得导数的数值解。通过应用非最大抑制以及滞回比较, Canny算法更加鲁棒和灵活。这就是为什么Canny算子最令人喜欢并被广泛使用在边缘检测中的原因。

关键词 边缘检测SobelCanny

前 言
目 录
Contents
如何进行边缘检测?
边缘检测
前期处理
Sobel边缘检测
Canny边缘检测
算法总结

本文是通过 Edge Detection Using OpenCV 中的内容进行整理而成,用于之后的学习和工作。

 

§00   言

  边缘检测是图像处理技术,用于确定图片中物体的边界(边缘)或者区域。边缘是图像中重要的特征。我们通过边缘来了解图像的结构信息。边缘检测算法广泛存在于实际应用中的计算机视觉算法处理流水线中。

0.1 如何进行边缘检测?

  边缘的特征在于像素亮度的突然变化,为了检测边缘,我们需要能够寻找出存在于相邻像素之间的这种变化。来吧,让我们探讨两种OpenCV中的重要边缘检测算法:Sobel边缘检测以及Canny边缘检测。我们不但演示如何使用OpenCV实现这两个算法,还讨论背后的理论。

  首先,看一下将要展示的边缘检测代码。后面会对代码每一行进行讲解帮助你充分理解他们。

  • Python
import cv2# Read the original image
img = cv2.imread('test.jpg') 
# Display original image
cv2.imshow('Original', img)
cv2.waitKey(0)# Convert to graycsale
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Blur the image for better edge detection
img_blur = cv2.GaussianBlur(img_gray, (3,3), 0) # Sobel Edge Detection
sobelx = cv2.Sobel(src=img_blur, ddepth=cv2.CV_64F, dx=1, dy=0, ksize=5) # Sobel Edge Detection on the X axis
sobely = cv2.Sobel(src=img_blur, ddepth=cv2.CV_64F, dx=0, dy=1, ksize=5) # Sobel Edge Detection on the Y axis
sobelxy = cv2.Sobel(src=img_blur, ddepth=cv2.CV_64F, dx=1, dy=1, ksize=5) # Combined X and Y Sobel Edge Detection
# Display Sobel Edge Detection Images
cv2.imshow('Sobel X', sobelx)
cv2.waitKey(0)
cv2.imshow('Sobel Y', sobely)
cv2.waitKey(0)
cv2.imshow('Sobel X Y using Sobel() function', sobelxy)
cv2.waitKey(0)# Canny Edge Detection
edges = cv2.Canny(image=img_blur, threshold1=100, threshold2=200) # Canny Edge Detection
# Display Canny Edge Detection Image
cv2.imshow('Canny Edge Detection', edges)
cv2.waitKey(0)cv2.destroyAllWindows()
  • C++
#include <opencv2/opencv.hpp>
#include <iostream>
// using namespaces to nullify use of cv::function(); syntax and std::function();
using namespace std;
using namespace cv;int main()
{// Reading imageMat img = imread("test.jpg");// Display original imageimshow("original Image", img);waitKey(0);// Convert to graycsaleMat img_gray;cvtColor(img, img_gray, COLOR_BGR2GRAY);// Blur the image for better edge detectionMat img_blur;GaussianBlur(img_gray, img_blur, Size(3,3), 0);// Sobel edge detectionMat sobelx, sobely, sobelxy;Sobel(img_blur, sobelx, CV_64F, 1, 0, 5);Sobel(img_blur, sobely, CV_64F, 0, 1, 5);Sobel(img_blur, sobelxy, CV_64F, 1, 1, 5);// Display Sobel edge detection imagesimshow("Sobel X", sobelx);waitKey(0);imshow("Sobel Y", sobely);waitKey(0);imshow("Sobel XY using Sobel() function", sobelxy);waitKey(0);// Canny edge detectionMat edges;Canny(img_blur, edges, 100, 200, 3, false);// Display canny edge detected imageimshow("Canny edge detection", edges);waitKey(0);destroyAllWindows();return 0;
}

▲ 图1.1  用于变换检测的图像

▲ 图1.1 用于变换检测的图像

  在讨论每个算法细节之前,我们需要完成一些变换检测基本步骤。将OpenCV软件库导入,就像下面代码所示:

  • Python
import cv2
  • C++
#include<opencv2/opencv.hpp>
#include<iostream>// Namespace nullifies the use of cv::function(); 
using namespace std;
using namespace cv;

 

§01 缘检测


1.1 前期处理

1.1.1 读入图像

  第一步使用OpenCV中的imread() 函数将图像读入内存。

  这里,我们按照灰度图格式将彩色图片读入内存,这是因为在检测边缘是你不需要彩色信息。如果你想了解读入图片更多参数信息,可以参考 之前的博文

1.1.2 图像平滑

  载入图像之后,我们也使用 GaussianBlur() 函数对其进行平滑。这是为了去除图片中的噪声。在边缘检测中,需要对像素亮度进行数值求导,结果会产生带有噪声的边缘。换而言之,图像中的相邻像素的亮度(特别是在边缘附近)波动比较大,者会被我们当成边界,但并不是我们寻找的主要边缘结构。

  对于边界附近的亮度通过模糊化进行平滑,可以比较容易识别出图像中的主要边缘结构。 你可以通过OpenCV相关文档了解GaussianBlur() 函数的更多的细节。 我们制定卷积核的尺寸(本例中,使用一个3×3的卷积核),决定了模糊化的程度。

  • Python
# Read the original image
img = cv2.imread('test.jpg',flags=0)  
# Blur the image for better edge detection
img_blur = cv2.GaussianBlur(img,(3,3), SigmaX=0, SigmaY=0) 
  • C++
// Reading image
Mat img = imread("test.jpg",0);
// Blur the image for better edge detection
Mat img_blur;
GaussianBlur(img, img_blur, Size(3,3), SigmaX=0, SigmaY=0);

1.2 Sobel边缘检测

  Sobel变换检测是一个广泛使用的边缘检测算法。 通过下图显示Sobel边缘检算子是寻找那些像素亮度突然变化的边缘。

1.2.1 基本原理

▲ 图1.2.1  像素随着变量t而发生变化

▲ 图1.2.1 像素随着变量t而发生变化

  当我们对亮度函数求其一阶导数的时候,会获得更加明显的峰值。

▲ 图1.2.2  求取亮度变化的一阶导数

▲ 图1.2.2 求取亮度变化的一阶导数

  上面绘制的图像展示了通过检测高于某个阈值的梯度值来检测边缘。再者,导数的突然改变也显示出像素亮度的突变。根据这一点,我们可以使用3×3的卷积核来获得近似导数。 我们使用一个卷积核检测在X方向(水平方向)的像素亮度突变, 使用另外一个卷积核检测Y方向(垂直方向)的亮度突变 。可以通过 这个链接 学习更多关于图像滤波和卷积核的内容。

  下面是两个用于Sobel边缘检测的卷积核:


  (1)是用于检测X方向边缘的卷积核。


  (2)是用于检测Y方向的卷积核。

  利用这些卷积核对源图像进行卷积,我们活得“Sobel边缘图像”。

  • 如果我们只使用垂直卷积核,卷积结果将会增加X方向边缘;
  • 如果使用水平卷积核所获得的的Sobel图像,在Y方向上的边缘被增强;

  使用 G x G_x Gx G y G_y Gy 分别代表x和y方向的梯度值,利用A,B表示X,Y方向的卷积核:

G x = A ∗ I , G y = B ∗ I G_x = A * I,\,\,\,G_y = B * I Gx=AI,Gy=BI

  其中 * 表示卷积算子, I I I 表示输入图像。

  最终的图像梯度幅值 G G G 可以通过下式计算: G = G x 2 + G y 2 G = \sqrt {G_x^2 + G_y^2 } G=Gx2+Gy2

  梯度方向由下式近似: θ = arctan ⁡ ( G y / G x ) \theta = \arctan \left( {G_y /G_x } \right) θ=arctan(Gy/Gx)

1.2.2 实现代码

  下面代码中,使用 Sobel() 函数计算:

  • 分别计算两个方向(x方向,y方向)的Sobel边缘图像;
  • 获得两个方向的组合梯度;

  下面是OpenCV中的Sobel边缘检测算子函数调用格式:

Sobel(src, ddepth, dx, dy)

  参数 ddepth 表示输出图像的精度, dx,dy表示两个方向求导的阶次,比如:

  • dx=1,dy=0,表示计算x方向上的一阶导数;

  如果 dx=1,dy=1,那么计算图像在两个方面上的一阶导数Sobel图像。

  • Python
# Sobel Edge Detection
sobelx = cv2.Sobel(src=img_blur, ddepth=cv2.CV_64F, dx=1, dy=0, ksize=5) # Sobel Edge Detection on the X axis
sobely = cv2.Sobel(src=img_blur, ddepth=cv2.CV_64F, dx=0, dy=1, ksize=5) # Sobel Edge Detection on the Y axis
sobelxy = cv2.Sobel(src=img_blur, ddepth=cv2.CV_64F, dx=1, dy=1, ksize=5) # Combined X and Y Sobel Edge Detection# Display Sobel Edge Detection Images
cv2.imshow('Sobel X', sobelx)
cv2.waitKey(0)cv2.imshow('Sobel Y', sobely)
cv2.waitKey(0)cv2.imshow('Sobel X Y using Sobel() function', sobelxy)
cv2.waitKey(0)
  • C++
// Sobel edge detection
Mat sobelx, sobely, sobelxy;
Sobel(img_blur, sobelx, CV_64F, 1, 0, 5);
Sobel(img_blur, sobely, CV_64F, 0, 1, 5);
Sobel(img_blur, sobelxy, CV_64F, 1, 1, 5);// Display Sobel edge detection images
imshow("Sobel X", sobelx);
waitKey(0);
imshow("Sobel Y", sobely);
waitKey(0);
imshow("Sobel XY using Sobel() function", sobelxy);
waitKey(0);

  下面给出了处理结果。可以注意到X方向的Sobel图像是如何确定出垂直边缘(也就是那些在x方向,也就是水平方向上梯度变化大的像素)。 同样,y方向上(也就是垂直方向)的Sobel图像确定出水平边缘。仔细分辨一下两个图像中老虎皮上垂直的条纹。可以看到在x方向的Sobel图像中条纹更清晰。

▲ 图1.2.3  X方向上的Sobel边缘算子图像

▲ 图1.2.3 X方向上的Sobel边缘算子图像

▲ 图1.2.4  Y方向Sobel算子图像
▲ 图1.2.4 Y方向Sobel算子图像

  下图是两个方向上的Sobel图像,抽取了原始图像中的边缘结构,所以保持图像结构没有受到改变。

▲ 图1.2.5  XY两个方向的Sobel边缘图像

▲ 图1.2.5 XY两个方向的Sobel边缘图像

1.3 Canny边缘检测

  Canny边缘检测是当今最流行的边缘检测算法,这是因为它具有可靠性和灵活性。算法通过三个步骤抽取原始图像的边缘信息。 除了使用图像模糊化,还有一些其他必要的去除噪声的预处理过程。加上这一步骤,Canny算法就具有四个处理阶段了:
  1. 消除噪声;
  2. 计算图像的亮度梯度值;
  3. 减除虚假边缘;
  4. 带有滞回的阈值检测;

1.3.1 去除噪声

  原始图像的像素通常会产生边缘噪声,所以Canny边缘检测在计算边缘之前进行噪声去除非常重要。使用高斯模糊化可以去除大部分,或者尽量减少不必要的图像细节产生不必要的边缘。对比下面两张图片。右边的图像使用了高斯平滑。他显得模糊了,但还是保留了能够计算出边缘的重要细节。可以通过 这个链接 进一步了解图像平滑。

▲ 图1.3.1 对比原始图像和高斯平滑后的图像

▲ 图1.3.1 对比原始图像和高斯平滑后的图像

1.3.2 计算图像梯度

  在图像平滑后,通过使用Sobel水平和垂直卷积核对图像进行滤波。利用滤波结果可以同时计算出亮度梯度的幅值( G G G )和方向( θ \theta θ ), 下面给出了计算方法:
G = G x 2 + G y 2 G = \sqrt {G_x^2 + G_y^2 } G=Gx2+Gy2 θ = tan ⁡ − 1 ( G y G x ) \theta = \tan ^{ - 1} \left( {{{G_y } \over {G_x }}} \right) θ=tan1(GxGy)

  梯度方向被量化的最接近的45°倍数的数值。下面右图显示了这个组合步骤结果。

▲ 图1.3.2 Sobel滤波结果

▲ 图1.3.2 Sobel滤波结果

1.3.3 去除虚假边缘

  完成了图像中的噪声去除和亮度梯度计算,算法的这个步骤通过使用 non-maximum supperssion 来剔除不需要的像素(这些像素不是组成边缘的部分)。 通过比较每个像素与周围像素在水平和垂直两个方向上的梯度值来实现剔除虚假边缘。如果一个像素对应的梯度在局部是最大的,也就是比他的上下左右像素梯度都大,它就保留下来。否则将就该像素置为0. 下面图像显示了处理结果。正如你所见,皮毛中的很多边缘都被除去了。

▲ 图1.3.3 NON-MAXIMUM Suppresion结果

▲ 图1.3.3 NON-MAXIMUM Suppresion结果

1.3.4 带有滞回的阈值处理

  最后一步,梯度值与两个阈值进行比较。其中一个小于另外一个。

  • 如果图像梯度值比较大的阈值还大,代表这个像素是一个很强的边缘,它被保留在最后的边缘图中;
  • 如果提督府之小于较小的阈值,则改像素被抑制,从最终边缘图中去除;
  • 对于那些梯度值落在两个给定阈值范围之内的像素他们被标记为弱边缘(也就是作为最终边缘图的候选者);
  • 如果弱边缘与强边缘相连,他们最终会保留在边缘图中。

1.3.5 编程实现

  下面是调用OpenCV中Canny边缘检测函数语法:

Canny(image, threshold1, threshold2)

  下面代码中,Canny() 函数实现了上面描述的方法。我们只需给出两个用于边缘检测的阈值, OpenCV软件将算法其它细节都实现了。 不要忘了在使用Canny() 算法之前需要对图像进行平滑。强烈建议不要省略这一步骤。 通过OpenCV文档可以了解更多的可选参数。

  • Python
# Canny Edge Detection
edges = cv2.Canny(image=img_blur, threshold1=100, threshold2=200) # Display Canny Edge Detection Image
cv2.imshow('Canny Edge Detection', edges)
cv2.waitKey(0)
  • C++
// Canny edge detection
Mat edges;
Canny(img_blur, edges, 100, 200, 3, false);
// Display canny edge detected image
imshow("Canny edge detection", edges);
waitKey(0);

  下面就是最终的结果:其中低阈值为100,高阈值为200.你可以看到算法吧图像中的主要变换都确定出来,处理过程吧那些对整体结构不重要的部分都去除了。然后,对于不同的图像,尝试不同的平滑、阈值大小,获得Canny算法的经验。你可以看到输出结果很容易被算法中的参数所改变。

▲ 图1.3.4  Canny边缘检测结果

▲ 图1.3.4 Canny边缘检测结果

  由于同时使用了Sobel算子, Non-Maximum Suppression以及带有滞回阈值处理,所以Canny变换检测算法的性能是最好的。在算法最后一步的阈值参数也给算法有更强的灵活性。

 

法总结 ※


  文讨论了图像处理中重要的技术:边缘检测,重点介绍了两种方法(Sobel边缘检测和Canny边缘检测)。在展示OpenCV中的用法 同时也强调了为什么图像平滑是重要的预处理步骤。

  在Canny边缘检测中 也是使用的Sobel算子获得导数的数值解。通过应用非最大抑制以及滞回比较, Canny算法更加鲁棒和灵活。这就是为什么Canny算子最令人喜欢并被广泛使用在边缘检测中的原因。


■ 相关文献链接:

  • 之前的博文
  • 这个链接

● 相关图表链接:

  • 图1.1 用于变换检测的图像
  • 图1.2.1 像素随着变量t而发生变化
  • 图1.2.2 求取亮度变化的一阶导数
  • 图1.2.3 X方向上的Sobel边缘算子图像
  • 图1.2.4 Y方向Sobel算子图像
  • 图1.2.5 XY两个方向的Sobel边缘图像
  • 图1.3.1 对比原始图像和高斯平滑后的图像
  • 图1.3.2 Sobel滤波结果
  • 图1.3.3 NON-MAXIMUM Suppresion结果
  • 图1.3.4 Canny边缘检测结果

http://chatgpt.dhexx.cn/article/Uw4GhjkZ.shtml

相关文章

微信小程序实现轮播图根据图片大小自适应高度

上效果图 话不多说——————上代码 首先wxml <swiper class"con_img" style"height:{{swiperheight}}px" indicator-dots"true" autoplay"true" indicator-active-color"#f8e112" bindchange"changeheight&quo…

微信小程序-图片自适应

image组件 使用使用mode&#xff1a;widthFix&#xff0c;宽度固定&#xff0c;高度自适应 <image class"nav_img" mode"widthFix" src"{{img}}"></image> 注意&#xff0c;在初次进入页面时会有高度拉伸情况&#xff0c;使用样式…

小程序中图片宽度实现100%,高度自适应

在做轮播图时放了几个图片&#xff0c;但是显示一直怪怪的&#xff0c;宽度没有占满&#xff0c;右边总是留一大块空白&#xff0c;怪难看的&#xff0c;开始试了所有的mode属性都不行&#xff0c;至于mode属性起什么作用可以查一下小程序组件的API说明&#xff0c;主要用于控制…

微信小程序之swiper组件高度自适应

要求&#xff1a; &#xff08;顶部广告栏 &#xff09; 改变swiper组件的固定高度&#xff0c;使之随内部每张图片的高度做自适应 原理&#xff1a; 图片加载完之后&#xff0c;获取图片的原始宽高&#xff0c;根据宽高比&#xff0c;计算出适应后的宽高&#xff0c;如果是适…

微信小程序图片固定宽度,高度自适应处理方法及相关API介绍

微信小程序图片固定宽度&#xff0c;高度自适应处理方法及相关API介绍 1、背景 在我们写js代码时&#xff0c;控制图片固定宽度固定&#xff0c;使得高度自适应缩小&#xff0c;是非常简单的&#xff0c;但在微信小程序中不了解微信小程中组件image相关的属性是有些麻烦的&am…

html微信图片自适应,微信小程序实现图片高度自适应

1. swiper轮播海报通过wx.getSystemInfo接口获取屏幕高度&#xff0c;高度依据图片宽高等比缩放 运行于app.js&#xff0c;全局保存 // 设备信息 wx.getSystemInfo({ success: function(res) { that.screenWidth res.windowWidth; } }); page前端调用 bannerHeight: Math.ceil…

微信小程序的swiper轮播图中的图片设置自适应高度的一种方法

微信小程序的swiper轮播图中的图片设置自适应高度的一种方法 小程序中的轮播图很简单&#xff0c;但是唯一的缺陷就是 swiper 是固定的150px 高度&#xff08;320px 宽度&#xff09;&#xff0c;这样如果传入的图片大于这个高度就会被隐藏。那么如何让图片自适应不同分辨率是一…

【小程序中image自适应以及默认高度问题】

原因&#xff1a;小程序不知道background-image&#xff0c;本人开发小程序有个头图&#xff0c;根据天气展示不同的头图&#xff0c;要适配各种宽窄不同的屏幕以及Ipad&#xff0c;不能拉伸图片&#xff0c;UI不同意使用渐变&#xff0c;其实使用宽100%完全可以解决&#xff0…

小程序图片高度自适应等问题

小程序图片高度自适应 这里踩了很多遍的坑&#xff0c;花了很多时间&#xff0c;以为像以前以前给他上级的盒子不给高度就行了&#xff0c;oh,no&#xff0c;这是一个坑&#xff0c;关键在于image标签的一个属性&#xff1a;mode"widthFix"&#xff0c;简单搞定 &l…

微信小程序图片高度按照图片真实宽高比自适应

原理&#xff1a;image组件bindload属性 前端在按照设计图定的宽度做好图片的宽高之后&#xff0c;如果图片的原始宽高比对应不上&#xff0c;那么图片就会出现拉伸变形的情况&#xff0c;虽然微信小程序有mode属性可以对图片进行适应&#xff0c;但是会有部分显示不完全或者会…

微信小程序——整个页面的自动适应布局、图片自适应、高度自动占满剩余部分

1、按比例适应布局 大家有没有过一个烦恼&#xff0c;就是让某个view的高度或者宽度扩大点&#xff0c;而且是要按比例适应不同的手机&#xff0c;遇到这类问题应该怎么办&#xff1f; 下面就为大家讲解怎么做一个能够自动适应不同手机的布局 1、像素单位 rpx 首先&#xff…

用arcgis裁剪面时出错,ERROR 999999: 执行函数时出错

转载地址&#xff1a;http://blog.sina.com.cn/s/blog_712f5b3b0101frfj.html 方法&#xff1a;ArcToolBox tool Data Management Tools -> Features -> Repair Geometry . 注&#xff1a;要裁剪的图层是要编辑&#xff08;editor&#xff09;状态。

ArcGIS裁剪时警告 warning001003:Datum conflict between input and output

问题描述 ArcGIS进行clip操作的时候警告&#xff1a;warning001003:Datum conflict between input and output。 原因 问题在于&#xff0c;输入和裁剪范围的坐标系不同。即Input raster和output extent两个图层的坐标系不同。 解决方案 统一坐标系统&#xff0c;具体方法很…

ArcGIS裁剪影像如何保持裁剪完全一致

ArcGIS裁剪影像如何保持裁剪范围完全一致 在长时间序列的数据分析中&#xff0c;经常会遇到要求所有的栅格数据范围一致&#xff0c;栅格数一致&#xff0c;所以在使用ArcGIS时&#xff0c;需要设置一些参数&#xff0c;才能得到正确的结果。 使用mask工具&#xff08;掩膜提取…

gis 数据框裁剪_ArcGIS中裁剪工具的使用

应约讲下裁剪。数据裁剪是经常使用的一个工具&#xff0c;工具说明也直接明了&#xff0c;隐藏功能他是不是说的&#xff0c;今天具体讲讲怎么用。 1.分析工具-提取分析-裁剪&#xff0c;启动裁剪工具。 2.将数据选中后&#xff0c;进行裁剪。输入要素此时不做任何选中。 看结果…

ArcGIS:矢量、栅格文件裁剪(批量处理)

一、栅格文件批量裁剪 1.加载矢量边界&#xff08;xzq&#xff09;&#xff0c;在Catalog对应文件夹中可查看原始数据为6个tif文件 2.在工具栏中打开ModelBuilder: &#xff08;1&#xff09;将矢量边界拖入Model&#xff1b;通过Insert->Iterators->Rasters添加栅格数据…

ArcMap(ArcGIS)批量裁剪图片【超详细】

1、将shp文件拖入ArcMap 2、打开工具&#xff08;ArcToolbox&#xff09; 3、右键按掩膜提取&#xff0c;选择批处理 4、增加列表的行数&#xff0c;使其与需要进行批量处理的图像数量保持一致 5、新建excel&#xff0c;填写相关信息 6、选中这17行 将excel数据复制至这1…

ArcGIS栅格裁剪

自己进行了ArcGIS栅格的裁剪&#xff0c;总结了一些小经验&#xff1a; 首先需要准备好待裁剪的栅格影像和裁剪范围&#xff0c;这里是博主自己画的面要素 然后ArcToolbox——数据管理工具——栅格——栅格处理——裁剪 如果选择了使用输入要素裁剪几何&#xff0c;裁剪出的栅…

用arcgis批量裁剪栅格(tiff)数据的矩形区域

首先&#xff0c;打开arcgis软件arcmap&#xff0c;然后加入数据 然后&#xff0c;在工作目录新建一个工具箱并命名​ 新建模型并命名进一步搭建模型 如图&#xff1a; 进一步搭建模型 插入迭代器&#xff0c;选择栅格 ​ 单击迭代栅格数据&#xff0c;选择参数&#xff0c;…

ArcGis批量裁剪栅格图层

1、简述 本文主要讲述&#xff1a; 还是老样子&#xff0c;先给初始的样子&#xff1a; 这些都是一个县的栅格图层&#xff0c;现在要全部裁剪为指定村的栅格图层&#xff0c;这要是用按掩膜提取工具去一个个裁剪&#xff0c;这得弄多久呀&#xff01;&#xff01; 于是利用A…