使用sobel算子进行边缘检测的步骤如下: 

  • 1、高斯模糊,降噪。因为sobel算子对噪声比较敏感,因此要先对原图像进行高斯模糊,降噪
  • 2、将图像转换成灰度图像
  • 3、使用sobel函数,求x和y方向上的导数。
  • 4、将x方向的导数(边缘)和y方向的导数进行叠加。

(1)高斯模糊

	//首先进行高斯模糊,降噪
	Mat gauImage;
	GaussianBlur(srcImage, gauImage, Size(3, 3), 0, 0, 4);

原图如下:

 

(2)转换成灰度图

	//将图片转换成灰度图
	Mat grayImage;
	cvtColor(gauImage, grayImage, COLOR_BGR2GRAY);

(3)使用sobel函数

	//进行x和y方向的soble算子
	Mat xGrand, yGrand;
	Sobel(grayImage, xGrand, CV_16S, 1, 0, 3);
	convertScaleAbs(xGrand, xGrand);
	Sobel(grayImage, yGrand, CV_16S, 0, 1, 3);
	convertScaleAbs(yGrand, yGrand);

 下面详细讲述一下x和y方向导数怎么求解。

对于一元连续函数的导数,我们直接通过公式求导就可以;对于多元连续函数的导数,我们需要求各个方向的偏导。但是对于存储在计算机中的图像来说,计算机只能够处理离散的像素值,不能够使用公式求解导数,因此只能够使用导数的定义来求解x和y方向的导数。比如求解x方向的导数:dx = I(i + 1) – I(i);求解y方向的导数:dx = I(j + 1) – I(j),某点的导数 = 该点x(y)方向的下一个像素值 – 该点的像素值,就可以得到该点x或者y方向的导数。

值得注意的是,因为sobel函数在求x或者y方向的导数时,使用的核是不一样的,得到的导数可能小于0,或者大于255,因此为了保护细节,最好选用16位(CV_16S)的输出图像的深度,并且调用convertScaleAbs(输出图像为8位),将所得结果尽可能的保护下来。

(4)将x和y方向的导数进行叠加

方法一:使用线性混合的方法进行叠加

	//进行线性混合叠加
	Mat dst;
	addWeighted(xGrand, 0.5, yGrand, 0.5, 0, dst, -1);

调用函数所需要的瞬间很短,得到的效果也很一般。最终的图片如下:

 

方法二:使用近似的方法,将x和y方向的导数进行相加

	Mat dst = Mat :: zeros( xGrand.size(), yGrand.type());
	//将x和y的梯度直接相加
	int colsImage = srcImage.cols;
	int rowsImage = srcImage.rows;
	for (int row = 0; row < rowsImage; row++) {
		for (int col = 0; col < colsImage; col++) {
			int xValue = xGrand.at<uchar>(row, col);
			int yValue = yGrand.at<uchar>(row, col);
			dst.at<uchar>(row, col) = saturate_cast<uchar>(xValue + yValue);
		}
	}

这个方法消耗的时间较长,消耗了0.1s左右的时间,但是运行结果显而易见,获得了更多的细节和边缘。

 

方法三:根据勾股定理求出近似梯度

根据向量的加法,如果要求得图像整体的梯度,就要求x和y向量加法的模,也就是x和y构成的直角三角形的斜边。

具体代码如下:

	//使用勾股定理获得更多的细节
	int colsImage = srcImage.cols;
	int rowsImage = srcImage.rows;
	for (int row = 0; row < rowsImage; row++) {
		for (int col = 0; col < colsImage; col++) {
			uchar xValue = xGrand.at<uchar>(row, col);
			uchar yValue = yGrand.at<uchar>(row, col);
			dst.at<uchar>(row, col) = saturate_cast<uchar>(sqrt(xValue * xValue + yValue * yValue));
		}
	}

这个方法消耗的时间比方法二消耗的时间略长一些,但是效果和方法二相差不多。因此,从时间方面考虑,完全可以使用第二种方法代替第三种方法。 

 

最终所有代码如下:

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

using namespace cv;

int main(int argc, char ** argv) {
	Mat srcImage = imread("1.jpg");
	if (srcImage.empty()) {
		printf("could not load this picture\n");
		return -1;
	}
	imshow("原图像", srcImage);
	//首先进行高斯模糊,降噪
	Mat gauImage;
	GaussianBlur(srcImage, gauImage, Size(3, 3), 0, 0, 4);
	//将图片转换成灰度图
	Mat grayImage;
	cvtColor(gauImage, grayImage, COLOR_BGR2GRAY);
	imshow("灰度图", grayImage);
	//进行x和y方向的soble算子
	Mat xGrand, yGrand;
	Sobel(grayImage, xGrand, CV_16S, 1, 0, 3);
	convertScaleAbs(xGrand, xGrand);
	Sobel(grayImage, yGrand, CV_16S, 0, 1, 3);
	convertScaleAbs(yGrand, yGrand);
	//进行叠加
	float t1 = getTickCount();
	Mat dst = Mat :: zeros( xGrand.size(), yGrand.type());
	//使用线性混合的方法
	//addWeighted(xGrand, 0.5, yGrand, 0.5, 0, dst, -1);
	
	将x和y的梯度直接相加
	//int colsImage = srcImage.cols;
	//int rowsImage = srcImage.rows;
	//for (int row = 0; row < rowsImage; row++) {
	//	for (int col = 0; col < colsImage; col++) {
	//		uchar xValue = xGrand.at<uchar>(row, col);
	//		uchar yValue = yGrand.at<uchar>(row, col);
	//		dst.at<uchar>(row, col) = saturate_cast<uchar>(xValue + yValue);
	//	}
	//}
	
	//使用勾股定理获得更多的细节
	int colsImage = srcImage.cols;
	int rowsImage = srcImage.rows;
	for (int row = 0; row < rowsImage; row++) {
		for (int col = 0; col < colsImage; col++) {
			uchar xValue = xGrand.at<uchar>(row, col);
			uchar yValue = yGrand.at<uchar>(row, col);
			dst.at<uchar>(row, col) = saturate_cast<uchar>(sqrt(xValue * xValue + yValue * yValue));
		}
	}
	float timeConsume = (getTickCount() - t1) / getTickFrequency();
	printf("线性混合叠加消耗的时间是:%f\n", timeConsume);
	imshow("最终图像", dst);
	imwrite("finalImage.jpg", dst);
	waitKey(0);
	return 0;
}

 


版权声明:本文为qq_38316300原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/qq_38316300/article/details/97130826