opencv变换:Sobel 导数算子

160 阅读4分钟

1. 目标

在本文中,将学习如何:

  • 使用 OpenCV 函数Sobel() 计算图像的导数。
  • 使用 OpenCV 函数Scharr()333*3的核计算更精确的导数

2. 理论

  • 笔记

    下面的解释来自Bradski 和 Kaehler的《 Learning OpenCV 》一书。

  1. 在前2篇文章中,演示了卷积的应用示例。最重要的卷积应用之一是计算图像中的导数(或它们的近似值)。

  2. 为什么图像中导数的计算很重要?假设想要检测图像中存在的边缘。例如:

    Sobel_Derivatives_Tutorial_Theory_0.jpg

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

  3. 在下图中,边缘由强度的“跳跃”表示:

    Sobel_Derivatives_Tutorial_Theory_Intensity_Function.jpg

  4. 如果取一阶导数,可以更容易地看到边缘“跳跃”(实际上,这里显示为最大值)

    Sobel_Derivatives_Tutorial_Theory_dIntensity_Function.jpg

  5. 因此,从上面的解释中,可以推断出一种检测图像边缘的方法可以通过定位梯度高于其邻居(或概括地说,高于阈值)的像素位置来执行。

  6. 更详细的解释请参考Bradski 和 Kaehler的Learning OpenCV

Sobel算子

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

公式

假设要操作的图像是II

  1. 计算两个导数:

    a. 水平方向:通过IGxI*G_x 计算卷积。例如,对于 内核大小3 ,GxG_x将计算为:

    G_x=\begin{bmatrix}

-1& 0& +1 \ -2& 0& +2 \ -1& 0& +1 \ \end{bmatrix}*I $$

b.  **垂直方向**:通过$I*G_x$ 计算卷积。例如,内核大小3$G_x$将计算为:
Gy=[121000+1+2+1]I G_y=\begin{bmatrix} -1& -2& -1 \\ 0& 0& 0 \\ +1& +2& +1 \\ \end{bmatrix}*I
  1. 在图像的每个点,通过结合上述两个结果来计算该点的梯度近似值:

    G=Gx2+Gy2G=\sqrt{G_x^2+G_y^2}

    有时会使用以下更简单的等式:

    G=Gx+GyG=|G_x|+|G_y|
  • 笔记

    • 当内核大小为 3时,上面显示的 Sobel 内核可能会产生明显的不准确性(毕竟,Sobel 只是导数的近似值)。OpenCV 通过使用Scharr() 函数解决了大小为 3 的内核的这种不准确性。这与标准 Sobel 函数一样快但更准确。它实现了以下内核:
Gx=[30+3100+1030+3]I G_x=\begin{bmatrix} -3& 0& +3 \\ -10& 0& +10 \\ -3& 0& +3 \\ \end{bmatrix}*I

Gy=[3103000+3+10+3]I G_y=\begin{bmatrix} -3& -10& -3 \\ 0& 0& 0 \\ +3& +10& +3 \\ \end{bmatrix}*I
  • 可以在 OpenCV 参考资料 - Scharr()  中查看有关此函数的更多信息。此外,在下面的示例代码中,会注意到在Sobel()** 函数的代码上方还注释了Scharr()函数的代码。取消注释它(并且显然注释 Sobel 的东西)应该让你了解这个函数是如何工作的。

3. 代码

  1. 这个程序有什么作用?

    • 在较暗背景上应用Sobel 算子并把检测到的明亮边缘作为输出图像。
  2. 教程代码如下所示。

也可以从这里下载

#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 )";
  // 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;
  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;
  }
  for (;;)
  {
    // Remove noise by blurring with a Gaussian filter ( kernel size = 3 )
    GaussianBlur(image, src, Size(3, 3), 0, 0, BORDER_DEFAULT);
    // Convert the image to grayscale
    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);
    // converting back to 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;
}

4. 代码解释

声明变量

// 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;

加载源图像

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;
  }

减少噪音

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

灰度

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

Sobel算子

 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);
  • 计算xy方向的“导数” 。为此,使用如下所示的函数Sobel() : 该函数采用以下参数:

    • src_gray:输入图像。这是CV_8U
    • grad_x / grad_y:输出图像。
    • ddepth:输出图像的深度。我们将其设置为CV_16S以避免溢出。
    • x_order : x方向的导数的阶数。
    • y_order : y方向导数的阶数。
    • scaledeltaBORDER_DEFAULT:我们使用默认值。

    请注意,要计算x方向的梯度,我们使用:xorder=1x_{order}=1yorder=0y_{order}=0. 对y方向进行类似的操作。

将输出转换为 CV_8U 图像

 // converting back to 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);

5. 结果

  1. 这是基本检测器应用于lena.jpg的输出:

    Sobel_Derivatives_Tutorial_Result.jpg

原文地址