【OpenCV学习】第16课:图像边缘提取 - Sobel算子详细剖析(图像梯度)

仅自学做笔记用,后续有错误会更改

理论

  1. 卷积的应用 - 图像边缘提取:
  • 边缘是什么:是像素值发生跃迁的地方, 是图像的显著特征之一, 再图像特征提取丶对象检测丶模式识别等方面都有重要作用
  • 如何捕捉/提取边缘:对图像求它的一阶导数,delta = f(x) - f(x-1), delta值越大, 说明像素在x方向变化越大,边缘信号越强
    【OpenCV学习】第16课:图像边缘提取 - Sobel算子详细剖析(图像梯度)_第1张图片
  • 如果你已经忘记了数学求导什么的概念, 也不用担心, 直接用Sobel算子进行卷积操作就可以了!
  1. Sobel算子
  • 是离散微分算子(discrete differentiation operator), 用来计算图像灰度的近似梯度
  • Sobel算子功能集合了 高斯模糊和微分求导
  • 又被称为一阶微分算子,求导算子,在水平和垂直两个方向求导,得到图像X方向与Y方向的梯度图像
    【OpenCV学习】第16课:图像边缘提取 - Sobel算子详细剖析(图像梯度)_第2张图片
  • 求取导数的近似值(上图的实际应用公式), kernel=3时不是很准确,容易受到干扰, Opencv使用改进版本Scharr函数, 算子如下:
    【OpenCV学习】第16课:图像边缘提取 - Sobel算子详细剖析(图像梯度)_第3张图片
  1. Sobel算子边缘提取步骤:
  • 先把原图像进行高斯模糊操作
  • 再把图像转灰度图
  • 再通过Sobel算子求X梯度和Y梯度
  • 线性混合X与Y的梯度图(可以不使用addweighted接口,而是手动去写, 去掉权重α与1-α的影响,可以使最后的图更明显),得到最终的振幅图像

相关API

cv::Sobel(
InputArray src, //输入图像
OutputArray dst, //输出图像
int depth, //输出图像深度,填-1表示跟输入图像一致, 由于灰度图是CV_8U,所以Sobel一般使用CV_16S/CV_32F, 需要比输入的灰度图的深度更高, 结果才会更明显
int dx, //x方向,几阶导数, sobel取1
int dy, //y方向,几阶导数, sobel取1
int ksize, //算子(kernel)大小,Sobel算子必须是奇数, 常见的是3
double scale = 1, //输出图像放大或缩小倍数
double delta = 0, //偏移量
int borderType = BORDER_DEFAULT
)

cv::Scharr(
InputArray src, //输入图像
OutputArray dst, //输出图像
int depth, //输出图像深度,填-1表示跟输入图像一致, 由于灰度图是CV_8U,所以Sobel一般使用CV_16S/CV_32F, 需要比输入的灰度图的深度更高, 结果才会更明显
int dx, //x方向,几阶导数, sobel取1
int dy, //y方向,几阶导数, sobel取1
int ksize, //算子(kernel)大小,Sobel算子必须是奇数, 常见的是3
double scale = 1, //输出图像放大或缩小倍数
double delta = 0, //偏移量
int borderType = BORDER_DEFAULT
)

代码示例

using namespace cv;
int main(int argc, char** argv){
	Mat src,dst;
	int ksize = 0;
	src = imread(...);
	if( !src.data ){
		return -1;
	}
	//原图
	char INPUT_WIN[] = "input image";
	namedWindow(INPUT_WIN, CV_WINDOW_AUTOSIZE);
	imshow(INPUT_WIN, src);
	//先高斯模糊, 再转灰度图
	Mat gray_src;
	GaussianBlur(src, dst, Size(3,3), 0, 0);
	cvtColor(dst, gray_src, CV_BGR2GRAY);
	imshow("gray image",gray_src);
	
	/*//cv::Scharr操作,  可以看最后的效果截图, 它的提取效果非常强烈, 根本不怕干扰
	Mat xgrad, ygrad;
	Scharr(gray_src, xgrad, CV_16S, 1, 0);
	Scharr(gray_src, ygrad, CV_16S, 0, 1);*/
	//cv::Sobel操作(输出深度为CV_16S,且有convertScaleAbs转换)
	Mat xgrad, ygrad;
	Scharr(gray_src, xgrad, CV_16S, 1, 0);
	Scharr(gray_src, ygrad, CV_16S, 0, 1);
	Sobel(gray_src, xgrad, CV_16S, 1, 0, 3);	//这里为什么用CV_16S, 因为灰度图是CV_8U,我们设置输出图像的深度比灰度图更大, 就可以容纳更大的特征值, 提取效果也就更明显, 你也可以填-1等同于输入图像的深度,但是反正不能小于输入图像的深度。
	Sobel(gray_src, ygrad, CV_16S, 0, 1, 3);
	convertScaleAbs(xgrad, xgrad);			    //这个函数的作用是保证Sobel算子操作过后有些负数结果值不被置为0, 也就是不被截取掉
	convertScaleAbs(ygrad, ygrad);
	imshow("xgrad", xgrad);
	imshow("ygrad", ygrad);
	
	/*//cv::Sobel操作(输出深度填-1,且没有convertScaleAbs转换)
	Mat xgrad, ygrad;
	Sobel(gray_src, xgrad, -1, 1, 0, 3);
	Sobel(gray_src, ygrad, -1, 0, 1, 3);
	imshow("xgrad", xgrad);
	imshow("ygrad", ygrad);*/


	//最终线性混合图:手动写线性混合,去掉权重影响, 可以看最后的效果截图,更明显
	Mat xygrad = Mat(xgrad.size(), xgrad.type());
	int width = xgrad.cols;
	int height = ygrad.rows;
	for(int row = 0; row < weight; row++){
		for(int col = 0;col < width;col++){
			int xg = xgrad.at(row, col);
			int yg = ygrad.at(row, col);										//因为灰度图是CV_8U,所以用uchar
			int xy = xg + yg;																	//直接相加, 没有权重影响, 更亮更明显
			xygrad.at(row, col) = saturate_cast(xy);	//saturate_cast这个东西的作用是保证最终值不超过0~255
		}
	}
	imshow("Final Result", xygrad);
	
	/*//最终线性混合图:直接调用addWeighted接口
	Mat xygrad;
	addWeighted(xgrad, 0.5, ygrad, 0.5, 0, xygrad);
	imshow("Final Result", xygrad);*/

	waitKey(0);
	return 0;
}

效果截图:
使用cv::Sobel(两个方向的输出深度都填CV_16S且有convertScaleAbs转换) x梯度图y梯度图
【OpenCV学习】第16课:图像边缘提取 - Sobel算子详细剖析(图像梯度)_第4张图片
使用cv::Sobel(两个方向的输出深度都填-1且没有convertScaleAbs转换) x梯度图y梯度图
【OpenCV学习】第16课:图像边缘提取 - Sobel算子详细剖析(图像梯度)_第5张图片
使用cv::Sobel(两个方向的输出深度都填CV_16S且有convertScaleAbs转换, 直接调用addWeighted接口) 最终线性混合图
【OpenCV学习】第16课:图像边缘提取 - Sobel算子详细剖析(图像梯度)_第6张图片
使用cv::Sobel(两个方向的输出深度都填CV_16S且有convertScaleAbs转换, 手动写线性混合) 最终线性混合图
【OpenCV学习】第16课:图像边缘提取 - Sobel算子详细剖析(图像梯度)_第7张图片
使用cv::Scharr(两个方向的输出深度都填CV_16S且有convertScaleAbs转换, 且手动写线性混合) 最终线性混合图

你可能感兴趣的:(OpenCV学习,opencv,学习,计算机视觉)