OpenCV Sobel 边缘检测

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;
}

源影像

源影像

 

X方向上边缘检测
Y方向上边缘检测

 

X和Y方向的边缘检测
  • 4
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值