https://docs.opencv.org/2.4/doc/tutorials/imgproc/imgtrans/sobel_derivatives/sobel_derivatives.html

Sobel算子

Sobel 算子是一个离散微分算子 (discrete differentiation operator)。 它结合了高斯平滑和微分求导,用来计算图像灰度函数的近似梯度。

一.基础知识介绍

[1]图像的边缘

图像的边缘从数学上是如何表示的呢?

图像的边缘上,邻近的像素值应当显著地改变了。而在数学上,导数是表示改变快慢的一种方法,一个函数在某一点的导数描述了这个函数在这一点附近的变化率。

梯度可谓是多元函数的偏导,表示某一函数在该点处的方向导数沿着该方向取得最大值,即函数在该点处沿着该方向(此梯度的方向)变化最快,变化率最大(为该梯度的模)。。

用更加形象的图像来解释,假设我们有一张一维图形。下图中灰度值的“跃升”表示边缘的存在:

    Sobel Derivatives
    




sobel 使用说明

使用一阶微分求导我们可以更加清晰的看到边缘“跃升”的存在(这里显示为高峰值):

  Sobel Derivatives
    




sobel 使用说明

由此我们可以得出:边缘可以通过定位梯度值大于邻域的相素的方法找到。

[2]卷积

卷积可以近似地表示求导运算。

那么卷积是什么呢?

卷积是在每一个图像块与某个算子(核)之间进行的运算。

核呢?

核就是一个固定大小的数值数组。该数组带有一个锚点 ,一般位于数组中央。

 可是这怎么运算啊?

假如你想得到图像的某个特定位置的卷积值,可用下列方法计算:

  1. 将核的锚点放在该特定位置的像素上,同时,核内的其他值与该像素邻域的各像素重合;
  2. 将核内各值与相应像素值相乘,并将乘积相加;
  3. 将所得结果放到与锚点对应的像素上;
  4. 对图像所有像素重复上述过程。

用公式表示上述过程如下:

    

在图像边缘的卷积怎么办呢?

计算卷积前,OpenCV通过复制源图像的边界创建虚拟像素,这样边缘的地方也有足够像素计算卷积了。

二.Sobel的卷积实现

Sobel是采用卷积的计算方法实现的。假设被作用的图像为Sobel Derivatives
    




sobel 使用说明 ,在两个方向上求导:

水平变化求导:将 Sobel Derivatives
    




sobel 使用说明 与一个奇数大小的内核 Sobel Derivatives
    




sobel 使用说明 进行卷积。比如,当内核大小为3时, Sobel Derivatives
    




sobel 使用说明 的计算结果为图1

垂直变化求导:将 I 与一个奇数大小的内核 Sobel Derivatives
    




sobel 使用说明 进行卷积。比如,当内核大小为3时, Sobel Derivatives
    




sobel 使用说明 的计算结果为图2

在图像的每一点,结合以上两个结果求出近似梯度大小 ,如图3

计算梯度方向,如图4   (如果以上的角度Θ等于零,即代表图像该处拥有纵向边缘,左方较右方暗)

 

Sobel Derivatives
    




sobel 使用说明Sobel Derivatives
    




sobel 使用说明Sobel Derivatives
    




sobel 使用说明Sobel Derivatives
    




sobel 使用说明

              图1                                  图2                                    图3                         图4

三.Code

先来看一下C++下 Sobel 的定义

cv:Sobel(  InputArray src ,  OutputArray dst,  int ddepth,  int dx,  int dy,  int ksize=3,   

             double scale=1,double delta=0,intborderType=BORDER_DEFAULT )

各参数的意义如下:

src – 输入图像。
dst – 输出图像,与输入图像同样大小,拥有同样个数的通道。 ddepth –输出图片深度;下面是输入图像支持深度和输出图像支持深度的关系: src.depth()
= CV_8U, ddepth = -1/CV_16S/CV_32F/CV_64F src.depth() = CV_16U/CV_16S, ddepth = -1/CV_32F/CV_64F src.depth() = CV_32F, ddepth = -1/CV_32F/CV_64F src.depth() = CV_64F, ddepth = -1/CV_64F 当 ddepth为-1时, 输出图像将和输入图像有相同的深度。输入8位图像则会截取顶端的导数。 xorder – x方向导数运算参数。
yorder – y方向导数运算参数。 ksize – Sobel内核的大小,可以是:
1357。 注意:只可以是小于7 的奇数 scale – 可选的缩放导数的比例常数。
delta – 可选的增量常数被叠加到导数中。
borderType – 用于判断图像边界的模式。

 

 

#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <stdlib.h>
#include <stdio.h>

using namespace cv;

/** @function main */
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;

  /// Load an image
  src = imread( argv[1] );

  if( !src.data )
  { return -1; }
 
///高斯模糊---apply a GaussianBlur to our image to reduce the noise ( kernel size = 3 ) GaussianBlur( src, src, Size(
3,3), 0, 0, BORDER_DEFAULT ); /// Convert it to gray cvtColor( src, src_gray, CV_BGR2GRAY ); /// Create window namedWindow( window_name, CV_WINDOW_AUTOSIZE );
///calculate the “derivatives” in x and y directions
/// Generate grad_x and grad_y Mat grad_x, grad_y; Mat abs_grad_x, abs_grad_y; /// Gradient 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 ); //convert our partial results back to CV_8U /// Gradient 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 ); /// Total Gradient (approximate近似)---we try to approximate the gradient by adding both directional gradients
(note that this is not an exact calculation at all! but it is good for our purposes).
addWeighted( abs_grad_x, 0.5, abs_grad_y, 0.5, 0, grad ); imshow( window_name, grad ); waitKey(0); return 0; }

 

相关文章: