Sobel算子

1、卷积应用-图像边缘提取;
2、相关API;
3、代码演示;
4、掌握使用Sobel算子计算图像梯度,提取图像轮廓边缘;

卷积应用-图像边缘提取

1、边缘:像素值发生跃迁的地方,是图像的显著特征之一,在图像特征提取,对象检测,模式识别等方面有重要的作用;
2、捕捉提取边缘方法:对图像求取一阶导数,图像上前一个像素点像素值与后一个像素值的差值即为一阶导数:delta = f(x) - f(x-1); delta越大,说明像素在x方向变化越大,边缘信号越强;
3、通过Sobel算子,卷积操作即可求取灰度图像的在X方向和Y方向的梯度图像;
4、Sobel算子是离散微分算子,用来计算图像灰度的近似梯度,Sobel算子功能集合高斯平滑,微分求导,又被称为一阶微分算子,求导算子,在水平和垂直两个方向上求导,得到图像X方向和Y方向的梯度图像; Laplace算子为二阶微分算子;

Sobel算子

1、Sobel算子值:分为水平梯度Sobel算子,垂直梯度Sobel算子两个;合成后(近似合成可以直接用加法直接将水平梯度和垂直梯度相加,速度更快,减少计算量,如下图)得到最终图像梯度;

2、Sobel算子在kernel(卷积核)= 3时求导不是很准确,OpenCV使用改进的Sobel算子,算子值如下:(算子值变大了)

相关API

1、Sobel();
参数如下:
注意:输出图像一般比灰度图像大,输出图像深度depth通常取值CV_16S或CV_32F,dx,dy取1,代表一阶导数,ksize取3(常见的Sobel算子为3阶),scale表示放大倍数,一般取1,delta常量值一般取0,borderType表示边缘处理方式;


注:Output depth栏下:-1表示Input depth栏对应的位图深度值;

convertScaleAbs():

2、Scharr();参数基本同上;

使用注意

  1. Sobel算子对噪声比较敏感,容易受到噪声影响,使用时应先进行高斯模糊降噪;
  2. 使用步骤:
    ①高斯平滑处理;
    ②转灰度图像;
    ③求X和Y方向;
    ④对求得的结果取绝对值处理(梯度有较大的负值,这些值也是边缘信息,需要保留)(convertScaleAbs()函数)
    ⑤使用addWeighted()函数进行混合,得到综合图像,称为振幅图像;或者直接操作X梯度图像像素点与Y梯度图像像素点相加;
  3. 正确处理Sobel算子运算得到的数据(取正)才能保证图像的正确显示;
  4. Scharr()函数相比于Sobel()函数对干扰噪声有更好的抵抗效果;

Code

#include <opencv2/opencv.hpp>
#include <iostream>


using namespace std;
using namespace cv;

int main(int argc, char** argv)
{
   
	Mat src, dst;
	src = imread("C:\\Users\\hello\\Desktop\\17.jpg");
	if (!src.data)
	{
   
		cout << "could not load the image" << endl;
		return -1;
	}
	char INPUT_WIN[] = "input image";
	char OUTPUT_WIN[] = "Sobel image";
	namedWindow(INPUT_WIN, CV_WINDOW_AUTOSIZE);
	//namedWindow(OUTPUT_WIN, CV_WINDOW_AUTOSIZE);
	imshow(INPUT_WIN, src);

	//Sobel算子运算
	Mat g1,gray_src,sx,sy;
	GaussianBlur(src, g1, Size(3, 3), 0, 0); //一般给 3 * 3 小模糊
	cvtColor(g1, gray_src, CV_BGR2GRAY);
	imshow("gray image", gray_src);
	//Sobel算子不需要自己再定义了
	//Mat kernel_x = (Mat_<int>(3, 3) << -1, 0, 1, -2, 0, 2, -1, 0, 1); //水平方向Sobel算子
	//Mat kernel_y = (Mat_<int>(3, 3) << -1, -2, -1, 0, 0, 0, 1, 2, 1); //垂直方向Sobel算子

	Sobel(gray_src, sx, CV_32F, 1, 0, 3);  //x方向梯度为1,y方向梯度为0 
	Sobel(gray_src, sy, CV_32F, 0, 1, 3);  //x方向梯度为0,y方向梯度为1
	//Sobel算子运算后梯度结果并不一定是正数,可能是负数(负梯度);需要调用函数转换为正数,保证信息不遗漏
	//负的绝对值也保留了边缘信息,为了不遗漏边缘信息,这个函数必须有!
	convertScaleAbs(sx, sx);  
	convertScaleAbs(sy, sy);

	imshow("x gray", sx);
	imshow("y gray", sy);

	//方法1:按权重合成
	//Mat fin;
	//addWeighted(sx, 0.5, sy, 0.5, 0, fin); //使用简化的混合方法,没有开平方
	//imshow("x y 混合结果", fin); 

	//方法2:直接像素操作相加,相比于addWeight()效果更好
	Mat xygrad = Mat(sx.size(), sx.type());
	int width = xygrad.cols;
	int height = xygrad.rows;
	printf("type: %d\n", sx.type());
	for (int row = 0; row < height; row++)
	{
   
		for (int col = 0; col < width; col++)
		{
   
			int xg = sx.at<uchar>(row, col);
			int yg = sy.at<uchar>(row, col);
			int xy = xg + yg;
			xygrad.at<uchar>(row, col) = saturate_cast<uchar>(xy);  //有被截断
			
		}
	}
	imshow("Sobel Image", xygrad);


	//Scharr() API使用
	Mat xsch, ysch;
	Scharr(gray_src, xsch, CV_16S, 1, 0);  //X方向Sobel运算
	Scharr(gray_src, ysch, CV_16S, 0, 1);  //Y方向Sobel运算
	convertScaleAbs(xsch, xsch);
	convertScaleAbs(ysch, ysch);
	
	Mat xysch = Mat(xsch.size(), xsch.type());
	int schwidth = xysch.cols;
	int schheight = xysch.rows;

	for (int row = 0; row < schheight; row++)
	{
   
		for (int col = 0; col < schwidth; col++)
		{
   
			int x = xsch.at<uchar>(row, col);
			int y = ysch.at<uchar>(row, col);
			int xys = x + y;
			xysch.at<uchar>(row, col) = saturate_cast<uchar>(xys);
		}
	}
	imshow("scharr image", xysch);
	waitKey(0);
	return 0;
}

效果

SCharr()运算的效果比Sobel更好一点:(纹理更明显)