图像处理:变换——Sobel 边缘检测算子 OpenCV v4.8.0

上一个 教程为图像添加边框

下一个教程拉普拉斯算子

原作者Ana Huamán
兼容性OpenCV >= 3.0

目标

在本教程中,您将学习如何

  • 使用 OpenCV 函数 Sobel() 计算图像的导数。
  • 使用 OpenCV 函数 Scharr() 为大小为 3⋅3 的核计算更精确的导数。

理论

注释
以下解释来自 Bradski 和 Kaehler 所著的《Learning OpenCV》一书。

  1. 在前两篇教程中,我们看到了卷积的应用实例。最重要的卷积之一是计算图像中的导数(或其近似值)。
  2. 为什么图像中的导数计算很重要?假设我们想要检测图像中的边缘。例如

在这里插入图片描述

您可以很容易地注意到,在边缘处,像素的强度会发生明显的变化。使用导数是表达变化的好方法。梯度变化大,表示图像发生了重大变化。

  1. 为了更加形象,我们假设有一幅一维图像。下图中强度的 "跳跃 "表示边缘:

在这里插入图片描述

  1. 如果我们求一阶导数(实际上,这里显示的是最大值),边缘 "跳跃 "就更容易看到了

在这里插入图片描述

  1. 因此,从上面的解释中,我们可以推断出,检测图像边缘的方法可以通过定位梯度高于其邻近像素的像素位置(或者概括地说,高于阈值)来实现。
  2. 更多详细解释,请参阅 Bradski 和 Kaehler 合著的《学习 OpenCV》一书。

Sobel算子

  1. 索贝尔算子是一种离散微分算子。它计算图像强度函数梯度的近似值。
  2. 索贝尔算子结合了高斯平滑和微分。

计算公式
假设要运算的图像为 I:

  1. 我们计算两个导数:

    水平变化: 通过将 I 与奇数大小的核 Gx 相卷积来计算。例如,内核大小为 3 时,Gx 的计算公式为
    G x = [ − 1 0 + 1 − 2 0 + 2 − 1 0 + 1 ] ∗ I G_x=\left[ \begin{matrix} -1& 0& +1\\ -2& 0& +2\\ -1& 0& +1\\ \end{matrix} \right] *I Gx= 121000+1+2+1 I

    垂直变化: 计算方法是将 I 与奇数大小的内核 Gy 相卷积。例如,内核大小为 3 时,Gy 的计算公式为
    G y = [ − 1 − 2 − 1 0 0 0 + 1 + 2 + 1 ] ∗ I G_y=\left[ \begin{matrix} -1& -2& -1\\ 0& 0& 0\\ +1& +2& +1\\ \end{matrix} \right] *I Gy= 10+120+210+1 I

  2. 在图像的每一点上,我们结合上述两个结果计算出该点的梯度近似值:

G = G x 2 + G x 2 G=\sqrt{G_x^2+G_x^2} G=Gx2+Gx2

尽管有时会使用以下更简单的等式:
G = ∣ G x ∣ + ∣ G y ∣ G=|G_x|+|G_y| G=Gx+Gy

注释
当核的大小为 3 时,上图所示的 Sobel 核可能会产生明显的误差(毕竟 Sobel 只是导数的近似值)。OpenCV 使用
Scharr() 函数解决了核大小为 3 时的不准确性问题。该函数与标准 Sobel 函数相比,速度更快,精度更高。它实现了以下核:
G x = [ − 3 0 + 3 − 10 0 + 10 − 3 0 + 3 ] G_x=\left[ \begin{matrix} -3& 0& +3\\ -10& 0& +10\\ -3& 0& +3\\ \end{matrix} \right] Gx= 3103000+3+10+3
G y = [ − 3 − 10 − 3 0 0 0 + 3 + 10 + 3 ] G_y=\left[ \begin{matrix} -3& -10& -3\\ 0& 0& 0\\ +3& +10& +3\\ \end{matrix} \right] Gy= 30+3100+1030+3
有关此函数的更多信息,请参阅
OpenCV 参考资料 - Scharr() 。此外,在下面的示例代码中,您会发现在 Sobel() 函数的代码上方也注释了
Scharr() 函数的代码。取消注释(显然也包括 Sobel 的注释)应该能让你了解这个函数是如何工作的。

代码

  1. 该程序有何作用?
    • 应用 Sobel 运算器并生成图像输出,图像中检测到的边缘在较暗的背景上显得明亮。
  2. 教程代码如下所示。

C++
您也可以从这里下载


#include "opencv2/imgproc.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
#include <iostream>
using namespace cv;
using namespace std;
int main( int argc, char** argv )
{
 cv::CommandLineParser parser(argc, argv,
 "{@input |lena.jpg|input image}"
 "{ksize k|1|ksize (hit 'K' to increase its value at run time)}"
 "{scale s|1|scale (hit 'S' to increase its value at run time)}"
 "{delta d|0|delta (hit 'D' to increase its value at run time)}"
 "{help h|false|show help message}");
 cout << "The sample uses Sobel or Scharr OpenCV functions for edge detection\n\n";
 parser.printMessage();
 cout << "\nPress 'ESC' to exit program.\nPress 'R' to reset values ( ksize will be -1 equal to Scharr function )";
 // 首先我们声明要使用的变量
 Mat image,src, src_gray;
 Mat grad;
 const String window_name = "Sobel Demo - Simple Edge Detector"int ksize = parser.get<int>("ksize")int scale = parser.get<int>("scale")int delta = parser.get<int>("delta")int ddepth = CV_16S;
 String imageName = parser.get<String>("@input")// 像往常一样,我们加载源图像 (src)
 image = imread( samples::findFile( imageName ), IMREAD_COLOR ); // 加载图像
 // 检查图像是否加载成功
 if( image.empty() )
 {
 printf("Error opening image: %s\n", imageName.c_str());
 return EXIT_FAILURE;
 }
 for (;;)
 {
 // 使用高斯滤波器模糊去除噪声(内核大小 = 3)
 GaussianBlur(image, src, Size(3, 3), 0, 0, BORDER_DEFAULT)// 将图像转换为灰度图像
 cvtColor(src, src_gray, COLOR_BGR2GRAY);
 Mat grad_x, grad_y;
 Mat abs_grad_x, abs_grad_y;
 Sobel(src_gray, grad_x, ddepth, 1, 0, ksize, scale, delta, BORDER_DEFAULT)Sobel(src_gray, grad_y, ddepth, 0, 1, ksize, scale, delta, BORDER_DEFAULT)// 转换回 CV_8U
 convertScaleAbs(grad_x, abs_grad_x);
 convertScaleAbs(grad_y, abs_grad_y);
 addWeighted(abs_grad_x, 0.5, abs_grad_y, 0.5, 0, grad);
 imshow(window_name, grad);
 char key = (char)waitKey(0);
 if(key == 27)
 {
 return EXIT_SUCCESS;
 }
 if (key == 'k' || key == 'K')
 {
 ksize = ksize < 30 ? ksize+2 : -1;
 }
 if (key == 's' || key == 'S')
 {
 scale++;
 }
 if (key == 'd' || key == 'D')
 {
 delta++;
 }
 if (key == 'r' || key == 'R')
 {
 scale = 1;
 ksize = -1;
 delta = 0;
 }
 }
 return EXIT_SUCCESS;
}

Java
您也可以从这里下载

import org.opencv.core.*;
import org.opencv.highgui.HighGui;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
class SobelDemoRun {
 public void run(String[] args) {
 // 首先,我们声明将要使用的变量
 Mat src, src_gray = new Mat()Mat grad = new Mat()String window_name = "Sobel Demo - Simple Edge Detector"int scale = 1int delta = 0int ddepth = CvType.CV_16S// 像往常一样加载源图像 (src)
 // 检查参数数量
 if (args.length == 0){
 System.out.println("Not enough parameters!")System.out.println("Program Arguments: [image_path]")System.exit(-1)}
 // 加载图像
 src = Imgcodecs.imread(args[0])// 检查图像加载是否正常
 if( src.empty() ) {
 System.out.println("Error opening image: " + args[0])System.exit(-1)}
 // 使用高斯滤波器模糊去除噪音 ( 内核大小 = 3 )
 Imgproc.GaussianBlur( src, src, new Size(3, 3), 0, 0, Core.BORDER_DEFAULT )// 将图像转换为灰度图像
 Imgproc.cvtColor( src, src_gray, Imgproc.COLOR_RGB2GRAY )Mat grad_x = new Mat(), grad_y = new Mat()Mat abs_grad_x = new Mat(), abs_grad_y = new Mat()//Imgproc.Scharr( src_gray, grad_x, ddepth, 1, 0, scale, delta, Core.BORDER_DEFAULT );
 Imgproc.Sobel( src_gray, grad_x, ddepth, 1, 0, 3, scale, delta, Core.BORDER_DEFAULT )//Imgproc.Scharr( src_gray、grad_y、ddepth、0、1、scale、delta、Core.BORDER_DEFAULT );
 Imgproc.Sobel( src_gray, grad_y, ddepth, 0, 1, 3, scale, delta, Core.BORDER_DEFAULT )// 转换回 CV_8U

通过www.DeepL.com/Translator(免费版)翻译
 Core.convertScaleAbs( grad_x, abs_grad_x );
 Core.convertScaleAbs( grad_y, abs_grad_y );
 Core.addWeighted( abs_grad_x, 0.5, abs_grad_y, 0.5, 0, grad );
 HighGui.imshow( window_name, grad );
 HighGui.waitKey(0);
 System.exit(0);
 }
}
public class SobelDemo {
 public static void main(String[] args) {
 // Load the native library.
 System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
 new SobelDemoRun().run(args);
 }
}

Python
您也可以从这里下载

"""
@file sobel_demo.py
@brief Sample code using Sobel and/or Scharr OpenCV functions to make a simple Edge Detector
"""
import sys
import cv2 as cv
def main(argv):
 
 window_name = ('Sobel Demo - Simple Edge Detector')
 scale = 1
 delta = 0
 ddepth = cv.CV_16S
 
 
 if len(argv) < 1:
 print ('Not enough parameters')
 print ('Usage:\nmorph_lines_detection.py < path_to_image >')
 return -1
 # 加载图像
 src = cv.imread(argv[0], cv.IMREAD_COLOR)
 # 检查图片加载是否正常
 if src is None:
 print ('Error opening image: ' + argv[0])
 return -1
 
 
 src = cv.GaussianBlur(src, (3, 3), 0)
 
 
 gray = cv.cvtColor(src, cv.COLOR_BGR2GRAY)
 
 
 grad_x = cv.Sobel(gray, ddepth, 1, 0, ksize=3, scale=scale, delta=delta, borderType=cv.BORDER_DEFAULT)
 # 梯度-Y
 # grad_y = cv.Scharr(gray,ddepth,0,1)
 grad_y = cv.Sobel(gray, ddepth, 0, 1, ksize=3, scale=scale, delta=delta, borderType=cv.BORDER_DEFAULT)
 
 
 abs_grad_x = cv.convertScaleAbs(grad_x)
 abs_grad_y = cv.convertScaleAbs(grad_y)
 
 
 grad = cv.addWeighted(abs_grad_x, 0.5, abs_grad_y, 0.5, 0)
 
 
 cv.imshow(window_name, grad)
 cv.waitKey(0)
 
 return 0
if __name__ == "__main__":
 main(sys.argv[1:])

说明

声明变量

C++

 // First we declare the variables we are going to use
 Mat image,src, src_gray;
 Mat grad;
 const String window_name = "Sobel Demo - Simple Edge Detector";
 int ksize = parser.get<int>("ksize");
 int scale = parser.get<int>("scale");
 int delta = parser.get<int>("delta");
 int ddepth = CV_16S;

加载源图像

C++

 String imageName = parser.get<String>("@input");
 // As usual we load our source image (src)
 image = imread( samples::findFile( imageName ), IMREAD_COLOR ); // Load an image
 // Check if image is loaded fine
 if( image.empty() )
 {
 printf("Error opening image: %s\n", imageName.c_str());
 return EXIT_FAILURE;
 }

减少噪声

C++

 // Remove noise by blurring with a Gaussian filter ( kernel size = 3 )
 GaussianBlur(image, src, Size(3, 3), 0, 0, BORDER_DEFAULT);

灰度

C++

 // Convert the image to grayscale
 cvtColor(src, src_gray, COLOR_BGR2GRAY);

Sobel运算符

C++

 Mat grad_x, grad_y;
 Mat abs_grad_x, abs_grad_y;
 Sobel(src_gray, grad_x, ddepth, 1, 0, ksize, scale, delta, BORDER_DEFAULT);
 Sobel(src_gray, grad_y, ddepth, 0, 1, ksize, scale, delta, BORDER_DEFAULT);
  • 我们计算 x 和 y 方向的 “导数”。为此,我们使用函数 Sobel(),如下所示: 该函数接收以下参数

    • src_gray: 在我们的示例中,输入图像。这里是 CV_8U
    • grad_x / grad_y : 输出图像。
    • ddepth:输出图像的深度: 输出图像的深度。我们将其设置为 CV_16S,以避免溢出。
    • x_order: X 方向导数的阶数。
    • y_order:导数的阶数: 在 Y 方向上的导数顺序。
    • scale、delta 和 BORDER_DEFAULT:使用默认值。

请注意,计算 x 方向的梯度时,我们使用:xorder=1,yorder=0。

将输出转换为 CV_8U 图像

C++

 // converting back to CV_8U
 convertScaleAbs(grad_x, abs_grad_x);
 convertScaleAbs(grad_y, abs_grad_y);

梯度

C++

 addWeighted(abs_grad_x, 0.5, abs_grad_y, 0.5, 0, grad);

我们尝试通过将两个方向的梯度相加来近似计算梯度(注意,这根本不是精确计算!但对于我们的目的来说,这是很好的)。

显示结果

C++

 imshow(window_name, grad);
 char key = (char)waitKey(0);

结果

  1. 以下是对 lena.jpg 应用基本检测器后的输出结果:

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值