图像处理之sobel算子

71 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第24天

1 sobel算子的基本概念

Sobel 算子是一个主要用于边缘检测的离散微分算子(discrete differentiation operator)。它结合了高斯平滑和微分求导,用来计算图像灰度函数的近似梯度。在图像的任何一点使用此算子,都将会产生对应的梯度矢量或是其法矢量。

2 sobel算子的计算过程

我们假设被作用图像为I然后进行如下操作。

(1)分别在x和y两个方向求导。

①水平变化:将I与一个奇数大小的内核G,进行卷积。比如,当内核大小为3时,G2的计算结果为:

2.png

②垂直变化:将:I与一个奇数大小的内核进行卷积。比如,当内核大小为3时,计算结果为:

3.png

(2)在图像的每一点,结合以上两个结果求出近似梯度:

4.png

另外有时,也可用下面更简单的公式代替:

5.png

3 sobel函数

Sobel 函数使用扩展的Sobel算子,来计算一阶、二阶、三阶或混合图像差分。

C++:

void Sobel(
InputArray src, 
OutputArray dst, 
int ddepth, 
int dx,
int dy, 
int ksize=3,
double scale=1,
double delta=0,
int borderType=BORDER_DEFAULT);

(1)第一个参数,InputArray类型的src,为输入图像,填Mat类型即可。

(2)第二个参数,OutputArray 类型的dst,即目标图像,函数的输出参数,需要和源图片有一样的尺寸和类型。

(3)第三个参数,int类型的ddepth,输出图像的深度,支持如下 src.depth()和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

(4)第四个参数,int类型dx,x方向上的差分阶数。

(5)第五个参数,int类型dy,y方向上的差分阶数。

(6)第六个参数,int类型ksize,有默认值3,表示Sobel核的大小;必须取1、3、5或7。

(7)第七个参数,double类型的scale,计算导数值时可选的缩放因子,默认值是1,表示默认情况下是没有应用缩放的。可以在文档中查阅 getDerivKernels的相关介绍,来得到这个参数的更多信息。

(8)第八个参数,double类型的delta,表示在结果存入目标图(第二个参数dst)之前可选的delta值,有默认值0。

(9)第九个参数,int 类型的 borderType,边界模式,默认值为BORDER DEFAULT。这个参数可以在官方文档中borderInterpolate 处得到更详细的信息。

一般情况下,都是用ksizexksize内核来计算导数的。然而,有一种特殊情况—当ksize为1时,往往会使用3x1或者1x3的内核。且这种情况下,并没有进行高斯平滑操作。

一些补充说明如下。

①当内核大小为3时,Sobel 内核可能产生比较明显的误差(毕竟,Sobel算子只是求取了导数的近似值而已)。为解决这一问题,OpenCV 提供了 Scharr函数,但该函数仅作用于大小为3的内核。该函数的运算与Sobel函数一样快,但结果却更加精确,其内核是这样的:

6.png

②因为Sobel算子结合了高斯平滑和分化(differentiation),因此结果会具有更多的抗噪性。大多数情况下,我们使用sobel函数时,取【xorder=1,yorder=0,ksize=3】来计算图像X方向的导数,【xorder=0,yorder=1,ksize=3】来 计算图像y方向的导数。

计算图像X方向的导数,取【xorder=1,yorder=0,ksize=3】情况对应的 内核:

7.png

而计算图像Y方向的导数,取【xorder=0,yorder=1,ksize=3】对应的内 核:

8.png

4 示例

代码:

//-----------------------------------【头文件包含部分】---------------------------------------
//            描述:包含程序所依赖的头文件
//------------------------------------------------------------------------------------------
#include <opencv2/opencv.hpp>
#include<opencv2/highgui/highgui.hpp>
#include<opencv2/imgproc/imgproc.hpp>

//-----------------------------------【命名空间声明部分】---------------------------------------
//            描述:包含程序所使用的命名空间
//--------------------------------------------------------------------------------------------
using namespace cv;
//-----------------------------------【main( )函数】-------------------------------------------
//            描述:控制台应用程序的入口函数,我们的程序从这里开始
//--------------------------------------------------------------------------------------------
int main( )
{
	//【0】创建 grad_x 和 grad_y 矩阵
	Mat grad_x, grad_y;
	Mat abs_grad_x, abs_grad_y,dst;

	//【1】载入原始图  
	Mat src = imread("1.jpg");  //工程目录下应该有一张名为1.jpg的素材图

	//【2】显示原始图 
	imshow("【原始图】sobel边缘检测", src); 

	//【3】求 X方向梯度
	Sobel( src, grad_x, CV_16S, 1, 0, 3, 1, 1, BORDER_DEFAULT );
	convertScaleAbs( grad_x, abs_grad_x );
	imshow("【效果图】 X方向Sobel", abs_grad_x); 

	//【4】求Y方向梯度
	Sobel( src, grad_y, CV_16S, 0, 1, 3, 1, 1, BORDER_DEFAULT );
	convertScaleAbs( grad_y, abs_grad_y );
	imshow("【效果图】Y方向Sobel", abs_grad_y); 

	//【5】合并梯度(近似)
	addWeighted( abs_grad_x, 0.5, abs_grad_y, 0.5, 0, dst );
	imshow("【效果图】整体方向Sobel", dst); 

	waitKey(0); 
	return 0; 
}

原图

1-1.png

效果图1

1-2.png

效果图2

1-3.png

效果图3

1-4.png