Sobel算子的理解

Sobel算子的理解sobel算子主要用于获得数字图像的一阶梯度,常见的应用和物理意义是边缘检测。原理算子使用两个33的矩阵(图1)算子使用两个33的矩阵(图1)去和原始图片作卷积,分别得到横向G(x)和纵向G(y)的梯度值,如果梯度值大于某一个阈值,则认为该点为边缘点Gx方向的相关模板:Gy方向的相关模板:看了网上的很多资料,都把卷积和相关的概念给弄糊了,书上

大家好,又见面了,我是你们的朋友全栈君。

sobel算子主要用于获得数字图像的一阶梯度,常见的应用和物理意义是边缘检测。

原理

算子使用两个33的矩阵(图1)算子使用两个33的矩阵(图1)去和原始图片作卷积,分别得到横向G(x)和纵向G(y)的梯度值,如果梯度值大于某一个阈值,则认为该点为边缘点

Gx方向的相关模板:

Sobel算子的理解

Gy方向的相关模板:

这里写图片描述

看了网上的很多资料,都把卷积和相关的概念给弄糊了,书上给的Sobel的模板不是卷积模板,而是相关模板,因为卷积的话要先将模板旋转180度以后再与图像做相关的操作。

所以Sobel的卷积模板是:

Gx=

1

0

-1

 2

0

-2

1

0

-1

Sobel算子的理解

具体计算如下:

图像的每一个像素的横向及纵向灰度值通过以下公式结合,来计算该点灰度的大小:

Sobel算子的理解

通常,为了提高效率使用不开平方的近似值:

Sobel算子的理解

然后可用以下公式计算梯度方向:

Sobel算子的理解

opencv还提供了一个scharr函数,比Sobel算子更为精准,也是3×3的模板。

这里写图片描述

下面放上源代码:

#include "opencv2/imgproc/imgproc.hpp"  #include "opencv2/highgui/highgui.hpp"  #include <iostream>  #include <cmath>using namespace cv;using namespace std;bool sobelEdge(Mat&  srcImage, Mat& resultImageX, Mat& resultImageY, uchar threshold){	CV_Assert(srcImage.channels() == 1);	// 初始化水平核因子	Mat sobelx = (Mat_<double>(3, 3) << -1, 0,		1, -2, 0, 2, -1, 0, 1);	// 初始化垂直核因子	Mat sobely = (Mat_<double>(3, 3) << -1, -2, -1,		0, 0, 0, 1, 2, 1);	resultImageX = Mat::zeros(srcImage.rows - 2,		srcImage.cols - 2, srcImage.type());	resultImageY = Mat::zeros(srcImage.rows - 2,		srcImage.cols - 2, srcImage.type());	double edgeX = 0;	double edgeY = 0;	double graMagX = 0;// 垂直方向上的梯度模长	double graMagY = 0;// 水平方向上的梯度模长	for (int k = 1; k < srcImage.rows - 1; ++k) 	{		for (int n = 1; n < srcImage.cols - 1; ++n) 		{			edgeX = 0;			edgeY = 0;			// 遍历计算水平与垂直梯度			for (int i = -1; i <= 1; ++i) 			{				for (int j = -1; j <= 1; ++j)				{					edgeX += srcImage.at<uchar>(k + i, n + j) *						sobelx.at<double>(1 + i, 1 + j);					edgeY += srcImage.at<uchar>(k + i, n + j) *						sobely.at<double>(1 + i, 1 + j);				}			}			// 计算垂直方向上的梯度模长			graMagX = sqrt(pow(edgeX, 2));			// 计算水平方向上的梯度模长			graMagY = sqrt(pow(edgeY, 2));			// 二值化			resultImageX.at<uchar>(k - 1, n - 1) =				((graMagX > threshold) ? 255 : 0);			// 二值化			resultImageY.at<uchar>(k - 1, n - 1) =				((graMagY > threshold) ? 255 : 0);		}	}	return true;}int OTSU(Mat &srcImage){	int nRows = srcImage.rows;	int nCols = srcImage.cols;	int threshold = 0;	double max = 0.0;	double AvePix[256];	int nSumPix[256];	double nProDis[256];	double nSumProDis[256];						 	for (int i = 0; i < 256; i++)	{		AvePix[i] = 0.0;		nSumPix[i] = 0;		nProDis[i] = 0.0;		nSumProDis[i] = 0.0;	}		for (int i = 0; i < nRows; i++)	{		for (int j = 0; j < nCols; j++)		{			nSumPix[(int)srcImage.at<uchar>(i, j)]++;		}	}		for (int i = 0; i < 256; i++)	{		nProDis[i] = (double)nSumPix[i] / (nRows*nCols);	}		AvePix[0] = 0;	nSumProDis[0] = nProDis[0];		for (int i = 1; i < 256; i++)	{		nSumProDis[i] = nSumProDis[i - 1] + nProDis[i];		AvePix[i] = AvePix[i - 1] + i*nProDis[i];	}	double mean = AvePix[255];	for (int k = 1; k < 256; k++)	{		double PA = nSumProDis[k];		double PB = 1 - nSumProDis[k];		double value = 0.0;		if (fabs(PA) > 0.001 && fabs(PB) > 0.001)		{			double MA = AvePix[k];//前一半的平均			double MB = (mean - PA*MA) / PB;//后一半的平均			value = value = (double)(PA * PB * pow((MA - MB), 2));//类间方差  		         //或者这样value = (double)(PA * PB * pow((MA-MB),2));//类间方差			 //pow(PA,1)* pow((MA - mean),2) + pow(PB,1)* pow((MB - mean),2)			if (value > max)			{				max = value;				threshold = k;			}		}	}	return threshold;}int main(){	Mat srcImage = cv::imread("building.jpg");	if (!srcImage.data)		return -1;	Mat srcGray;	cvtColor(srcImage, srcGray, CV_BGR2GRAY);	imshow("srcGray", srcGray);	//调用二值化函数得到最佳阈值	int otsuThreshold = OTSU(srcGray);	cout << otsuThreshold << endl;	Mat XresultImage;	Mat YresultImage;	sobelEdge(srcGray, XresultImage, YresultImage, otsuThreshold);	Mat resultImage;	//水平垂直边缘叠加	addWeighted(XresultImage, 0.5, YresultImage, 0.5, 0.0, resultImage);	imshow("resx", XresultImage);	imshow("resy", YresultImage);	imshow("res", resultImage);	waitKey(0);	return 0;}

原图灰度图:

Sobel算子的理解

垂直边缘图像:

Sobel算子的理解

水平边缘图像:

Sobel算子的理解

水平垂直合一起:

Sobel算子的理解

从上图可以看出 边缘图还有很多不是边缘点的也被取出来了,下一步就是做非极大值抑制

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/158971.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • ES 安装 elasticsearch-sql插件「建议收藏」

    ES 安装 elasticsearch-sql插件「建议收藏」1、查看sql插件githubhttps://github.com/NLPchina/elasticsearch-sql2、安装1、cdelasticsearch#进入目录2、./bin/elasticsearch-plugininstallhttps://github.com/NLPchina/elasticsearch-sql/releases/download/5.4.3.0/elasticsearch-sql-5.4.3.0.zip3、下载SQL的Serverwget

  • Adminlte教程[通俗易懂]

    Adminlte教程[通俗易懂]1

  • JWT单点登录(源码学习)

    JWT单点登录(源码学习)三、JWT源码学习//登录成功之后,需要生成tokenStringtoken=Jwts.builder().setSubject(“用户名/用户信息”)//主题,可以放用户的详细信息.setIssuedAt(newDate())//token创建时间.setExpiration(newDate(System.currentTimeMillis()+60000))//token过期时间.setId(“用户ID”)//用户ID

  • 使用javascript实现数组截取

    使用javascript实现数组截取前言:在开发项目的过程中遇到这样的一个问题,就是需要对接口查询出来的数据两个两个的进行截取,之后分别两个两个的放入数组中,再把这些数组放到一个新数组中,实现方法如下:方法一:functionarrayChunk(array,size){ letdata=[]; for(leti=0;i<array.length;i+=size){ data.push(array.slice(i,i+size)) } returndata;}arrayChunk([{i

  • java垃圾回收器的工作原理「建议收藏」

    java垃圾回收器的工作原理「建议收藏」出处:Sunnier深入理解java垃圾回收机制—-一、垃圾回收机制的意义  Java语言中一个显著的特点就是引入了垃圾回收机制,使c++程序员最头疼的内存管理的问题迎刃而解,它使得Java程序员在编写程序的时候不再需要考虑内存管理。由于有个垃圾回收机制,Java中的对象不再有“作用域”的概念,只有对象的引用才有“作用域”。垃圾回收可以有效的防止内存泄露,有效的使用

  • decltype 使用

    decltype 使用功能decltype可以将给定的表达式或变量的类型推导出来,包含引用和指针。一般用于复杂表达式作为返回值的类型推导。可以用于补足c++11的auto缺陷.编译阶段的事情,不会任何执行,表达式也不会执行。类型规则规则一:声明类型,类型包含引用就有引用,没有引用也不会自行添加。规则二:返回值则根据返回值类型确定最终类型。规则三:表达式根据默认和重载得到的最终类型。不建议特别复杂的表达式。声明类型分析案例一intmain()..

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号