您的位置:首页 > 运维架构

Opencv图像识别从零到精通(19)----Robert,prewitt,Sobel边缘检测

2016-07-31 17:45 846 查看
图像的边缘检测,是根据灰度的突变或者说不连续来检测,对于其中的算子有一阶导数和二价导数,这里先说基础的三种方法---Robert,prewitt,Sobel边缘检测。

一、梯度

首先介绍下梯度,梯度并非是一个数值,梯度严格意义上是一个向量,这个向量指向当前位置变化最快的方向,可以这么理解,当你站在一个山上,你有360°的方向可以选择,哪个方向下降速度最快(最陡峭),便是梯度方向,梯度的长度,表示为向量的长度,表示最大的变化速率。

梯度的数学表达:



在二维中只取前两项,也就是由x方向的偏微分和y方向的偏微分组成。对于图像f中点(x,y)处的梯度,定义为:



与上面所述保持一致,图像梯度方向给出图像变化最快方向,当前点的梯度长度为:



次长度计算中有平方和开平方,所以将不再是现行操作。

为了简单计算,将上面求距离简化成:



二、RObert

Roberts边缘算子是一个2x2的模板,采用的是对角方向相邻的两个像素之差。从图像处理的实际效果来看,边缘定位较准,对噪声敏感。适用于边缘明显且噪声较少的图像分割。Roberts边缘检测算子是一种利用局部差分算子寻找边缘的算子,Robert算子图像处理后结果边缘不是很平滑。经分析,由于Robert算子通常会在图像边缘附近的区域内产生较宽的响应,故采用上述算子检测的边缘图像常需做细化处理,边缘定位的精度不是很高。标准一阶差分不同,Robert采用对角线差分,边缘定位准,但是对噪声敏感。适用于边缘明显且噪声较少的图像分割。Roberts边缘检测算子是一种利用局部差分算子寻找边缘的算子,Robert算子图像处理后结果边缘不是很平滑。经分析,由于Robert算子通常会在图像边缘附近的区域内产生较宽的响应,故采用上述算子检测的边缘图像常需做细化处理,边缘定位的精度不是很高。

<span style="font-size:18px;">// roberts算子实现
cv::Mat roberts(cv::Mat srcImage)
{
cv::Mat dstImage = srcImage.clone();
int nRows = dstImage.rows;
int nCols = dstImage.cols;
for (int i = 0; i < nRows-1; i++)
{
for (int j = 0; j < nCols-1; j++)
{
int t1 = (srcImage.at<uchar>(i, j) -
srcImage.at<uchar>(i+1, j+1)) *
(srcImage.at<uchar>(i, j) -
srcImage.at<uchar>(i+1, j+1));
int t2 = (srcImage.at<uchar>(i+1, j) -
srcImage.at<uchar>(i, j+1)) *
(srcImage.at<uchar>(i+1, j) -
srcImage.at<uchar>(i, j+1));
dstImage.at<uchar>(i, j) = (uchar)sqrt(t1 + t2);

}
}
return dstImage;
}</span>



三、prewitt

Prewitt算子是一种一阶微分算子的边缘检测,利用像素点上下、左右邻点的灰度差,在边缘处达到极值检测边缘,去掉部分伪边缘,对噪声具有平滑作用 。其原理是在图像空间利用两个方向模板与图像进行邻域卷积来完成的,这两个方向模板一个检测水平边缘,一个检测垂直边缘。

对数字图像f(x,y),Prewitt算子的定义如下:

G(i)=|[f(i-1,j-1)+f(i-1,j)+f(i-1,j+1)]-[f(i+1,j-1)+f(i+1,j)+f(i+1,j+1)]|

G(j)=|[f(i-1,j+1)+f(i,j+1)+f(i+1,j+1)]-[f(i-1,j-1)+f(i,j-1)+f(i+1,j-1)]|

则 P(i,j)=max[G(i),G(j)]或 P(i,j)=G(i)+G(j)

经典Prewitt算子认为:凡灰度新值大于或等于阈值的像素点都是边缘点。即选择适当的阈值T,若P(i,j)≥T,则(i,j)为边缘点,P(i,j)为边缘图像。这种判定是欠合理的,会造成边缘点的误判,因为许多噪声点的灰度值也很大,而且对于幅值较小的边缘点,其边缘反而丢失了。

Prewitt算子对噪声有抑制作用,抑制噪声的原理是通过像素平均,但是像素平均相当于对图像的低通滤波,所以Prewitt算子对边缘的定位不如Roberts算子。

因为平均能减少或消除噪声,Prewitt梯度算子法就是先求平均,再求差分来求梯度。水平和垂直梯度模板分别为:
检测水平边沿 横向模板


检测垂直平边沿 纵向模板:



该算子与Sobel算子类似,只是权值有所变化,但两者实现起来功能还是有差距的,据经验得知Sobel要比Prewitt更能准确检测图像边缘。

对噪声有抑制作用,抑制噪声的原理是通过像素平均,但是像素平均相当于对图像的低通滤波,所以Prewitt算子对边缘的定位不如Roberts算子。

// roberts算子实现
Mat prewitt(Mat imageP)
{
cvtColor(imageP,imageP,CV_RGB2GRAY);
float prewittx[9] =
{
-1,0,1,
-1,0,1,
-1,0,1
};
float prewitty[9] =
{
1,1,1,
0,0,0,
-1,-1,-1
};
Mat px=Mat(3,3,CV_32F,prewittx);
cout<<px<<endl;
Mat py=Mat(3,3,CV_32F,prewitty);
cout<<py<<endl;
Mat dstx=Mat(imageP.size(),imageP.type(),imageP.channels());
Mat dsty=Mat(imageP.size(),imageP.type(),imageP.channels());
Mat dst=Mat(imageP.size(),imageP.type(),imageP.channels());
filter2D(imageP,dstx,imageP.depth(),px);
filter2D(imageP,dsty,imageP.depth(),py);
float tempx,tempy,temp;
for(int i=0;i<imageP.rows;i++)
{
for(int j=0;j<imageP.cols;j++)
{
tempx=dstx.at<uchar>(i,j);
tempy=dsty.at<uchar>(i,j);
temp=sqrt(tempx*tempx+tempy*tempy);
dst.at<uchar>(i,j)=temp;
}
}
return dst;
}




四、Sobel

其主要用于边缘检测,在技术上它是以离散型的差分算子,用来运算图像亮度函数的梯度的近似值, Sobel算子是典型的基于一阶导数的边缘检测算子,由于该算子中引入了类似局部平均的运算,因此对噪声具有平滑作用,能很好的消除噪声的影响。Sobel算子对于象素的位置的影响做了加权,与Prewitt算子、Roberts算子相比因此效果更好。

Sobel算子包含两组3x3的矩阵,分别为横向及纵向模板,将之与图像作平面卷积,即可分别得出横向及纵向的亮度差分近似值。实际使用中,常用如下两个模板来检测图像边缘。

检测水平边沿 横向模板 :


检测垂直平边沿 纵向模板:



图像的每一个像素的横向及纵向梯度近似值可用以下的公式结合,来计算梯度的大小。

{G_{x}^{2}@plus;G_{y}^{2}}]



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





在以上例子中,如果以上的角度Θ等于零,即代表图像该处拥有纵向边缘,左方较右方暗。

缺点是Sobel算子并没有将图像的主题与背景严格地区分开来,换言之就是Sobel算子并没有基于图像灰度进行处理,由于Sobel算子并没有严格地模拟人的视觉生理特征,所以提取的图像轮廓有时并不能令人满意。
Sobel 算子是一个主要用作边缘检测的离散微分算子 (discrete differentiation operator)。 它Sobel算子结合了高斯平滑和微分求导,用来计算图像灰度函数的近似梯度。在图像的任何一点使用此算子,将会产生对应的梯度矢量或是其法矢量。

因为Sobel算子结合了高斯平滑和分化(differentiation),因此结果会具有更多的抗噪性。大多数情况下,我们使用sobel函数时,取【xorder = 1,yorder = 0,ksize = 3】来计算图像X方向的导数,【xorder = 0,yorder = 1,ksize = 3】来计算图像y方向的导数。

计算图像X方向的导数,取【xorder= 1,yorder = 0,ksize = 3】情况对应的内核:
C++: void Sobel (
InputArray src,//输入图
OutputArray dst,//输出图
int ddepth,//输出图像的深度
int dx,
int dy,
int ksize=3,
double scale=1,
double delta=0,
int borderType=BORDER_DEFAULT );


第一个参数,InputArray 类型的src,为输入图像,填Mat类型即可。
第二个参数,OutputArray类型的dst,即目标图像,函数的输出参数,需要和源图片有一样的尺寸和类型。
第三个参数,int类型的ddepth,输出图像的深度,支持如下src.depth()和ddepth的组合:

若src.depth() = CV_8U, 取ddepth =-1/CV_16S/CV_32F/CV_64F
若src.depth() = CV_16U/CV_16S, 取ddepth =-1/CV_32F/CV_64F
若src.depth() = CV_32F, 取ddepth =-1/CV_32F/CV_64F
若src.depth() = CV_64F, 取ddepth = -1/CV_64F

第四个参数,int类型dx,x 方向上的差分阶数。
第五个参数,int类型dy,y方向上的差分阶数。
第六个参数,int类型ksize,有默认值3,表示Sobel核的大小;必须取1,3,5或7。
第七个参数,double类型的scale,计算导数值时可选的缩放因子,默认值是1,表示默认情况下是没有应用缩放的。我们可以在文档中查阅getDerivKernels的相关介绍,来得到这个参数的更多信息。
第八个参数,double类型的delta,表示在结果存入目标图(第二个参数dst)之前可选的delta值,有默认值0。
第九个参数, int类型的borderType,我们的老朋友了(万年是最后一个参数),边界模式,默认值为BORDER_DEFAULT。这个参数可以在官方文档中borderInterpolate处得到更详细的信息。

#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <stdlib.h>
#include <stdio.h>

using namespace cv;
int main( int argc, char** argv )
{
Mat src, src_gray;
Mat grad;
char* window_name = "Sobel Demo - Simple Edge Detector";
int scale = 1;//默认值
int delta = 0;//默认值
int ddepth = CV_16S;//防止输出图像深度溢出
int c;
src = imread( "lena.jpg" );
if( !src.data )
{ return -1; }

//高斯模糊
GaussianBlur( src, src, Size(3,3), 0, 0, BORDER_DEFAULT );
//变换为灰度图
cvtColor( src, src_gray, CV_RGB2GRAY );
//创建窗口
namedWindow( window_name, CV_WINDOW_AUTOSIZE );
//生成 grad_x and grad_y
Mat grad_x, grad_y;
Mat abs_grad_x, abs_grad_y;
// Gradient X x方向梯度 1,0:x方向计算微分即导数
//Scharr( src_gray, grad_x, ddepth, 1, 0, scale, delta, BORDER_DEFAULT );
Sobel( src_gray, grad_x, ddepth, 1, 0, 3, scale, delta, BORDER_DEFAULT );
convertScaleAbs( grad_x, abs_grad_x );
// Gradient Y y方向梯度 0,1:y方向计算微分即导数
//Scharr( src_gray, grad_y, ddepth, 0, 1, scale, delta, BORDER_DEFAULT );
Sobel( src_gray, grad_y, ddepth, 0, 1, 3, scale, delta, BORDER_DEFAULT );
convertScaleAbs( grad_y, abs_grad_y );

//近似总的梯度
addWeighted( abs_grad_x, 0.5, abs_grad_y, 0.5, 0, grad );

imshow( window_name, grad );

waitKey(0);

return 0;
}




五、Matlab

clear all;
I=imread('d:\lena.jpg');
I=rgb2gray(I);%灰度图像
subplot(1,4,1);imshow(I);
xlabel('(a)原始图像');
%sobel算子
BW2=edge(I,'sobel');
subplot(1,4,2);imshow(BW2);
xlabel('(c)sobel算子')
%prewitt算子
BW3=edge(I,'prewitt');
subplot(1,4,3);imshow(BW3);
xlabel('(d)prewitt算子')
%roberts算子
BW4=edge(I,'roberts');
subplot(1,4,4);imshow(BW4);
xlabel('(e)roberts算子')

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐