文档结构  
翻译进度:已翻译     翻译赏金:0 元 (?)    ¥ 我要打赏

目标

在这篇教程中您将会学习如何:

  • 利用OpenCV函数Sobel计算一张图片中的导数.
  • 利用OpenCV函数Scharr,用一个 3 \cdot 3大小的核更加精确的计算导数

理论

注意

以下解释属于Bradski and Kaehler的书《Learning OpenCV》.

  1. 在过去的两个教程我们学习了卷积的应用实例.其中一个最重要的部分是在图像导数的计算 (或导数的近似).

  2. 为何计算一个图像的导数显得是很重要? 举个例子,如何检测下方图片中的边缘部分?

    How intensity changes in an edge

    你很容易能找出这个边缘,像素强度在这里发生了一个剧烈变化. 利用导数将能很好的表示这种变化. 梯度的巨大变化代表图像中也出现了巨大变化.

  3. 更图形化一些, 假设有一张一维图像. 下边的折线图展示了一个边缘在像素在强度上 “阶跃“ 的变化:

    Intensity Plot for an edge
  4. 如果我们计算了导数这个“阶跃”边缘会看的更加清晰 (事实上, 这里出现了最大值)

    First derivative of Intensity - Plot for an edge
  5. 所以利用上述解释, 可以推断出寻找图像中像素梯度大于相邻点的部分是一种边缘检测的方法  (或者说大于某一个阈值).

  6. 更详细的解释,请查询Bradski and Kaehler的书《Learning OpenCV》.

第 1 段(可获 2 积分)

Sobel算子

  1. Sobel算子是一种离散的微分算子。它用于计算图像强度在梯度上的近似值。
  2. Sobel算子结合了高斯平滑和微分操作。

公式

Formulation

假设被操作的图像是 I:

  1. 首先计算两个中间变量:

    1. 水平的变化: 水平的变化是由I 和奇数尺寸的核 G_{x}卷积得到.在这个例子中核的尺寸是3*3, G_{x}计算方法如下:

      G_{x} = \begin{bmatrix} -1 & 0 & +1 \\ -2 & 0 & +2 \\ -1 & 0 & +1 \end{bmatrix} * I

    2. 垂直的变化: 垂直的变化是由I 和奇数尺寸的核G_{y}卷积得到.在这个例子中核的尺寸是3*3, G_{y} 计算方法如下:

      G_{y} = \begin{bmatrix} -1 & -2 & -1 \\ 0 & 0 & 0 \\ +1 & +2 & +1 \end{bmatrix} * I

  2. 图像的每一点计算梯度的近似点应结合以上两个结果:

    G = \sqrt{ G_{x}^{2} + G_{y}^{2} }

    有时使用以下简单的公式:

    G = |G_{x}| + |G_{y}|

第 2 段(可获 2 积分)

注意

如果核的尺寸是3, 上述介绍的Sobel核可能会产生明显的不准确的情况 (毕竟,Sobel算子只是一个近似的导数计算). OpenCV中利用Scharr函数解决尺寸为3产生的错误.这是比标准Sobel函数快但更准确. 它通过以下内核实现:

G_{x} = \begin{bmatrix} -3 & 0 & +3 \\ -10 & 0 & +10 \\ -3 & 0 & +3 \end{bmatrix} G_{y} = \begin{bmatrix} -3 & -10 & -3 \\ 0 & 0 & 0 \\ +3 & +10 & +3 \end{bmatrix}

在OpenCV指南中你可以查询关于此函数的更多信息 (Scharr). 同样的, 在下方的代码中, 在下边的代码中我们同时给出了Sobel函数的代码和Scharr函数的注释.注释它将帮助您更好的理解函数是如何实现的(尤其是关于Sobel的解释).

第 3 段(可获 2 积分)

代码

  1. 这段程序有什么功能?
    • 运用Sobel算子在暗背景下检测图像边缘并输出.
  2. 教程代码如下.你也可以在下载
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <stdlib.h>
#include <stdio.h>

using namespace cv;

/** @主函数*/
int main( int argc, char** argv )
{

  Mat src, src_gray;
  Mat grad;
  char* window_name = "Sobel Demo - Simple Edge Detector";
  int scale = 1;
  int delta = 0;
  int ddepth = CV_16S;

  int c;

  /// 加载一张图像
  src = imread( argv[1] );

  if( !src.data )
  { return -1; }

  GaussianBlur( src, src, Size(3,3), 0, 0, BORDER_DEFAULT );

  /// 转换为灰度图
  cvtColor( src, src_gray, CV_BGR2GRAY );

  /// 创建窗口
  namedWindow( window_name, CV_WINDOW_AUTOSIZE );

  /// 计算grad_x和grad_y变量
  Mat grad_x, grad_y;
  Mat abs_grad_x, abs_grad_y;

  /// X轴梯度
  //Scharr( src_gray, grad_x, ddepth, 1, 0, scale, delta, BORDER_DEFAULT );
  Sobel( src_gray, grad_x, ddepth, 1, 0, 3, scale, delta, BORDER_DEFAULT );
  convertScaleAbs( grad_x, abs_grad_x );

  /// Y轴梯度
  //Scharr( src_gray, grad_y, ddepth, 0, 1, scale, delta, BORDER_DEFAULT );
  Sobel( src_gray, grad_y, ddepth, 0, 1, 3, scale, delta, BORDER_DEFAULT );
  convertScaleAbs( grad_y, abs_grad_y );

  /// 总梯度(近似)
  addWeighted( abs_grad_x, 0.5, abs_grad_y, 0.5, 0, grad );

  imshow( window_name, grad );

  waitKey(0);

  return 0;
  }
第 4 段(可获 2 积分)

解释

  1. 首先声明了将用到的变量:

    Mat src, src_gray;
    Mat grad;
    char* window_name = "Sobel Demo - Simple Edge Detector";
    int scale = 1;
    int delta = 0;
    int ddepth = CV_16S;
    
  2. 接着加载了源图像src:

    src = imread( argv[1] );
    
    if( !src.data )
    { return -1; }
    
  3. 第一步,使用了GaussianBlur滤波器以减少图像中的噪声(核的尺寸为3)

    GaussianBlur( src, src, Size(3,3), 0, 0, BORDER_DEFAULT );
    
  4. 将过滤后的图像转化为灰度:

    cvtColor( src, src_gray, CV_BGR2GRAY );
    
  5. 第二部,计算出x和y方向上的“梯度”.为此,我们使用了如下所示的Sobel函数:

    Mat grad_x, grad_y;
    Mat abs_grad_x, abs_grad_y;
    
    /// Gradient X
    Sobel( src_gray, grad_x, ddepth, 1, 0, 3, scale, delta, BORDER_DEFAULT );
    /// Gradient Y
    Sobel( src_gray, grad_y, ddepth, 0, 1, 3, scale, delta, BORDER_DEFAULT );
    

    该函数包含有以下参数:

    注意到为了计算x方向的梯度,我们设置了: x_{order}= 1y_{order} = 0. 计算y方向时也利用了同样的方法.

    • src_gray: 本例中为输入图像. 它是CV_8U格式的
    • grad_x/grad_y: 输出图像.
    • ddepth: 图像的深度. 设置为CV_16S避免溢出 .
    • x_order: x方向上的梯度.
    • y_order: y方向上的梯度.
    • scale, delta and BORDER_DEFAULT: 使用了默认值.
  6. 将部分结果转化为CV_8U:

    convertScaleAbs( grad_x, abs_grad_x );
    convertScaleAbs( grad_y, abs_grad_y );
    
  7. 最终,通过结合两个方向上的梯度以近似得出最终的梯度(注意:这不是一个准确的计算,但是它有利于计算).

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

最终显示了结果:

imshow( window_name, grad );

Results

  1. 这是我们对lena.jpg进行基本边缘检测的结果:

    Result of applying Sobel operator to lena.jpg

  

    第 5 段(可获 2 积分)

    文章评论