Sobel算子检测方法对灰度渐变和噪声较多的图像处理效果较好,sobel算子对边缘定位不是很准确,图像的边缘不止一个像素;当对精度要求不是很高时,是一种较为常用的边缘检测方法。
OpenCV中sobel过滤因子的原型为
void cv::Sobel( InputArray _src, OutputArray _dst, int ddepth, int dx, int dy,
int ksize, double scale, double delta, int borderType )
参数解析:
第一个参数:_src 输入的源影像
第二个参数:_dst输出的目标影像,大小和通道数与源影像相同。深度由ddepth来决定
第三个参数:目标影像的深度;当源源影像的深度为CV_8U时,一般ddepth选择为CV_16S
第四个参数:x方向上的导数因子
第五个参数:Y方向上的导数因子
第六个参数:
如果ksize<0的时候,那么使用scharr内核过滤因子。scharr的内核过滤因子大小为3。
dx大于等于0,dy大于等于0,并且dx+dy==1
X方向的内核因子为:
Y方向的内核因子为
如果ksize>0的时候,那么ksize必须为奇数,并且大小应该小于31
当 ( xorder = 1, yorder = 0, ksize = 3) 时,内核因子(即:X方向的检测因子)为:
当 ( xorder = 0, yorder = 1, ksize = 3) 时,内核因子(即Y方向上检测因子)为:
当 ( xorder = 1, yorder = 0, ksize = 5) 时,内核因子(即X方向上检测因子)为:
获取不同sobel因子的程序如下所示
//包含OpenCV的头文件
//参照github https://github.com/yoyoyo-yo/Gasyori100knock
#include <opencv2/opencv.hpp>
#include <iostream>
#include <vector>
using namespace std;
//使用OpenCV的命名空间
using namespace cv;
//
//获取sobel因子
//kernelSize 是内核的大小,并且是奇数,并且小于31
//XOrder 是否是X方向的向量
vector<int> SobelFact(const int kernelSize, bool XOrder)
{
vector<int > kerI(kernelSize+1);
const int ksize = kernelSize;
int i, j;
int order = XOrder?1:0;//修改X,Y的值
int oldval, newval;
kerI[0] = 1;
//归一化其他vector数组中的值
for (i = 0; i < ksize; i++)
kerI[i + 1] = 0;
for (i = 0; i < ksize - order - 1; i++)
{
oldval = kerI[0];
for (j = 1; j <= ksize; j++)
{
newval = kerI[j] + kerI[j - 1];
kerI[j - 1] = oldval;
oldval = newval;
}
}
for (i = 0; i < order; i++)
{
oldval = -kerI[0];
for (j = 1; j <= ksize; j++)
{
newval = kerI[j - 1] - kerI[j];
kerI[j - 1] = oldval;
oldval = newval;
}
}
kerI.resize(kernelSize);
return kerI;
}
int main()
{
vector<int> xVec, yVec;
int KernelSize = 5;
xVec = SobelFact(KernelSize, true);
yVec = SobelFact(KernelSize, false);
Mat m(KernelSize, KernelSize,CV_32SC1);
//X方向上 1行 KernelSize列
Mat xM(1, KernelSize, CV_32SC1, &xVec[0]);
//Y方向上 KernelSize行 1列
Mat yM(KernelSize, 1, CV_32SC1, &yVec[0]);
//需要注意的地方是:必须转换为CV_32FC1才能够参与矩阵相乘算法 即:yM*xM
xM.convertTo(xM, CV_32FC1);
yM.convertTo(yM, CV_32FC1);
cout << xM << endl;
cout << yM << endl;
cout << yM*xM << endl;
return 0;
}
程序输出如下:
第七个参数:scale因子,是sobel过滤内核因子的缩放因子。而非对影像进行的缩放因子
第八个参数:delta,sobel边缘检测之后在影像中加上delta。
第九个参数:边界类型,参照我前面的例子。
OpenCV部分源代码解析:
//_src 输入的源影像
//_dst 处理之后的输出影像
//ddpeth 处理之后影像的位深度,如果源影像是8位的即CV_8U 那么经过sobel之后应该为CV_16S
//_dx X方向上导数因子
//_dy Y方向上导数因子
//ksize sobel 滤波内核的大小
//scale 内核的缩放因子
//delta 生成目标影像中加上delta
//borderType 边界类型
void cv::Sobel( InputArray _src, OutputArray _dst, int ddepth, int dx, int dy,
int ksize, double scale, double delta, int borderType )
{
//获取源影像的数据类型 CV_8UC1,CV_8UC3
//获取源影像的深度 CV_8U
//获取源影像的通道数 3
int stype = _src.type(), sdepth = CV_MAT_DEPTH(stype), cn = CV_MAT_CN(stype);
//如果目标影像的深度小于0的话,那么使用源影像的数据深度
if (ddepth < 0)
ddepth = sdepth;
//制作目标影像的数据类型
int dtype = CV_MAKE_TYPE(ddepth, cn);
//创建目标影像的MAT
_dst.create( _src.size(), dtype );
#ifdef HAVE_TEGRA_OPTIMIZATION
if (tegra::useTegra() && scale == 1.0 && delta == 0)
{
Mat src = _src.getMat(), dst = _dst.getMat();
if (ksize == 3 && tegra::sobel3x3(src, dst, dx, dy, borderType))
return;
if (ksize == -1 && tegra::scharr(src, dst, dx, dy, borderType))
return;
}
#endif
#ifdef HAVE_IPP
CV_IPP_CHECK()
{
if (ksize < 0)
{
if (IPPDerivScharr(_src, _dst, ddepth, dx, dy, scale, delta, borderType))
{
CV_IMPL_ADD(CV_IMPL_IPP);
return;
}
}
else if (0 < ksize)
{
if (IPPDerivSobel(_src, _dst, ddepth, dx, dy, ksize, scale, delta, borderType))
{
CV_IMPL_ADD(CV_IMPL_IPP);
return;
}
}
}
#endif
//内核类型 是在CV_32F 和(目标影像、源影像)之间的最大值
int ktype = std::max(CV_32F, std::max(ddepth, sdepth));
//制作内核函数
Mat kx, ky;
getDerivKernels( kx, ky, dx, dy, ksize, false, ktype );
if( scale != 1 )
{
// usually the smoothing part is the slowest to compute,
// so try to scale it instead of the faster differenciating part
if( dx == 0 )
kx *= scale;
else
ky *= scale;
}
sepFilter2D( _src, _dst, ddepth, kx, ky, Point(-1, -1), delta, borderType );
}
void cv::getDerivKernels( OutputArray kx, OutputArray ky, int dx, int dy,
int ksize, bool normalize, int ktype )
{
//如果内核过滤因子小于0的话,使用Scharr 内核进行过滤
if( ksize <= 0 )
getScharrKernels( kx, ky, dx, dy, normalize, ktype );
else
getSobelKernels( kx, ky, dx, dy, ksize, normalize, ktype );
}
static void getScharrKernels( OutputArray _kx, OutputArray _ky,
int dx, int dy, bool normalize, int ktype )
{
//使用固定内核的大小
const int ksize = 3;
//断言ktype 必须是32或者64位的浮点数
CV_Assert( ktype == CV_32F || ktype == CV_64F );
//创建X方向上的内核过滤因子
//ksize行 1 列
//ktype 32或者64位浮点型
_kx.create(ksize, 1, ktype, -1, true);
_ky.create(ksize, 1, ktype, -1, true);
Mat kx = _kx.getMat();
Mat ky = _ky.getMat();
//断言:dx和dy必须都大于等于0
//dx+dy 必须为1
CV_Assert( dx >= 0 && dy >= 0 && dx+dy == 1 );
//创建内核因子
//当k=0 的时候,求解X方向的内核时,计算出来的内核大小为
//[-1,0,1]
//当k=1的时候,求解X方向的内核时,计算出来的内核大小为
//[3,10,3]
//所以X方向的内核为
//-3 0 3
//-10 0 10
//-3 0 3
for( int k = 0; k < 2; k++ )
{
Mat* kernel = k == 0 ? &kx : &ky;
int order = k == 0 ? dx : dy;
int kerI[3];
if( order == 0 )
kerI[0] = 3, kerI[1] = 10, kerI[2] = 3;
else if( order == 1 )
kerI[0] = -1, kerI[1] = 0, kerI[2] = 1;
Mat temp(kernel->rows, kernel->cols, CV_32S, &kerI[0]);
double scale = !normalize || order == 1 ? 1. : 1./32;
temp.convertTo(*kernel, ktype, scale);
}
}
static void getSobelKernels( OutputArray _kx, OutputArray _ky,
int dx, int dy, int _ksize, bool normalize, int ktype )
{
//X,Y方向的内核大小都是_ksize
int i, j, ksizeX = _ksize, ksizeY = _ksize;
//如果内核大小为1,并且是求解x方向导数时
//X方向的内核大小为3
if( ksizeX == 1 && dx > 0 )
ksizeX = 3;
//求解Y方向与X方向上是相同的
if( ksizeY == 1 && dy > 0 )
ksizeY = 3;
//内核的类型必须为32或者64位的浮点型
CV_Assert( ktype == CV_32F || ktype == CV_64F );
//创建X,Y方向的过滤内核因子
_kx.create(ksizeX, 1, ktype, -1, true);
_ky.create(ksizeY, 1, ktype, -1, true);
Mat kx = _kx.getMat();
Mat ky = _ky.getMat();
//如果内核为偶数 或者内核大于31的话,直接返回
if( _ksize % 2 == 0 || _ksize > 31 )
CV_Error( CV_StsOutOfRange, "The kernel size must be odd and not larger than 31" );
//创建一系列的内核因子,大小为ksize+1
std::vector<int> kerI(std::max(ksizeX, ksizeY) + 1);
//dx必须大于等于0
//dy必须大于等于0
//并且dx和dy不能同时为0
CV_Assert( dx >= 0 && dy >= 0 && dx+dy > 0 );
//当k=0的时候,并且求解X方向内核因子时,返回的内核因子为:
//
for( int k = 0; k < 2; k++ )
{
Mat* kernel = k == 0 ? &kx : &ky;
int order = k == 0 ? dx : dy;
int ksize = k == 0 ? ksizeX : ksizeY;
CV_Assert( ksize > order );
//当ksize为1的时候,那么内核就是1,也就是源影像保持不变
if( ksize == 1 )
kerI[0] = 1;
//当内核大小为3的时候
//当order =0的时候
//[1,2,1]
//当order =1的时候
//[-1,0,1]
//当order为其他的情况下
//[1,-2,1]
else if( ksize == 3 )
{
if( order == 0 )
kerI[0] = 1, kerI[1] = 2, kerI[2] = 1;
else if( order == 1 )
kerI[0] = -1, kerI[1] = 0, kerI[2] = 1;
else
kerI[0] = 1, kerI[1] = -2, kerI[2] = 1;
}
//当内核大小为其他情况下时
else
{
int oldval, newval;
kerI[0] = 1;
//归一化其他vector数组中的值
for( i = 0; i < ksize; i++ )
kerI[i+1] = 0;
for( i = 0; i < ksize - order - 1; i++ )
{
oldval = kerI[0];
for( j = 1; j <= ksize; j++ )
{
newval = kerI[j]+kerI[j-1];
kerI[j-1] = oldval;
oldval = newval;
}
}
for( i = 0; i < order; i++ )
{
oldval = -kerI[0];
for( j = 1; j <= ksize; j++ )
{
newval = kerI[j-1] - kerI[j];
kerI[j-1] = oldval;
oldval = newval;
}
}
}
Mat temp(kernel->rows, kernel->cols, CV_32S, &kerI[0]);
double scale = !normalize ? 1. : 1./(1 << (ksize-order-1));
temp.convertTo(*kernel, ktype, scale);
}
}
OpenCV sobel边缘检测的例子
//包含OpenCV的头文件
//参照github https://github.com/yoyoyo-yo/Gasyori100knock
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace std;
//使用OpenCV的命名空间
using namespace cv;
//
//频道改变
int main()
{
//读取源影像
Mat Src = imread("C:/Users/GuSheng/Desktop/标准测试图片/rubberwhale1.png", IMREAD_COLOR);
if (Src.empty())
{
return 0;
}
//将彩色影像转换为灰色影像
Mat Gray;
cvtColor(Src, Gray, CV_BGR2GRAY);
//X方向上边缘检测的结果
Mat XBorder, YBorder, XYBorder;
Sobel(Gray, XBorder, CV_16S, 1, 0, 3, 1.0, 0);
Sobel(Gray, YBorder, CV_16S, 0, 1, 3, 1.0, 0);
convertScaleAbs(XBorder, XBorder);
convertScaleAbs(YBorder, YBorder);
//XY方向上的因
addWeighted(XBorder, 0.5, YBorder, 0.5, 0, XYBorder);
namedWindow("Src", WINDOW_AUTOSIZE);
namedWindow("XBorder", WINDOW_AUTOSIZE);
namedWindow("YBorder", WINDOW_AUTOSIZE);
namedWindow("XYBorder", WINDOW_AUTOSIZE);
imshow("Src", Gray);
imshow("XBorder", XBorder);
imshow("YBorder", YBorder);
imshow("XYBorder", XYBorder);
waitKey(0);
return 0;
}
源影像