使用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 版权协议,转载请附上原文出处链接和本声明。