sobel算子实现原理和c++实现sobel()检测边缘函数

一、sobel算子原理

Sobel算子的原理是以一维高斯算子的二项式近似为基础的,也就是对于二项式展开式的系数而言,可以作为非归一化的高斯平滑算子。

又因为sobel算子的原理是先对图像进行非归一化的高斯平滑,然后再进行差分,所以3阶的Sobel边缘检测算子可以直接通过矩阵运算得到:
在这里插入图片描述

其中第一个式子是n等于2时展开式的高斯平滑算子,也就是二项式展开式的系数,第二个式子表示差分。
最后得到的结果就是3阶sobel边缘检测算子。

至于n阶的sobel算子,是由窗口为n的非归一化的高斯算子(即n-1阶的二项式展开式的系数)和窗口为n的差分算子通过full卷积得到的,现在只要求出窗口为n的差分算子就可以了。

他的构建方法为在n-2阶的二项式展开式的系数两侧补上一个零,然后利用后向差分得到。
举个栗子:
构建4阶的差分算子,令二项式指数为 4-2 = 2,然后计算展开式的系数,即图左,两侧补零后,即图右
在这里插入图片描述
接着后向差分:
在这里插入图片描述
差分后的结果是:
在这里插入图片描述
该结构即为4阶的差分算子。
然后和4阶的平滑算子full卷积,即可得到 4 * 4 的sobel。

通过上述描述,可以清楚的发现,高阶的sobel边缘检测算子是可以分离的,也就是水平方向上的卷积和垂直方向上的卷积可以分别计算出来。

二、sobel算子代码的实现思路

了解了sobel算子的原理,目的是为了充分了解sobel边缘检测算子的原理之后,通过codeblocks来编码实现sobel边缘检测算子。
梳理以上sobel边缘检测算子所需要的功能:
第一个就是阶乘函数,因为高斯平滑算子的近似项为二项式展开式;
接着利用阶乘函数表示出来二项式展开式来定义平滑算子函数;
又因为差分算子可以利用平滑算子得到,所以可以利用平滑算子函数获得平滑算子之后对Mat直接操作来构建差分算子,得到差分算子函数;
现在已经得到了平滑算子以及差分算子,所以可以定义sobel函数来完成图像矩阵与sobel核的卷积。

这里的卷积运算,指的是二维离散卷积,属于基于两个矩阵的一种计算的方式。

假设有两个矩阵I和K,第一步是先将K逆时针翻转180度得到Ki,第二步,Ki沿着I按照先从行开始再进行列的顺序移动,每移动到一个固定的位置的时候,对应的位置就相乘得到一个乘积,然后整体求和,在整个移动的过程中,将对应位置积的和依次存入一个新的矩阵C中,该矩阵就是I和K full卷积的结果,其中K称为卷积核,或者卷积掩码。对于sobel函数的卷积运算,不仅仅属于full卷积,而且还属于same卷积。same卷积可以得到和原图像的高、宽相等的卷积结果,如何做到的呢,通常是在计算的过程中给Ki指定一个特殊点,然后将该点循环的移动到图像矩阵的(a,b)处(这里需满足a,b属于原图像的高、宽范围内的数据),接着依然是对应位置的元素逐个相乘,最后对所有的乘积解都求和来作为图像矩阵在(a,b)处的输出数据。

关于卷积运算,在OpenCV中并没有直接给出相关的函数,不过可以利用两个函数来间接实现。
第一步,利用flip()函数将输入的卷积核逆时针翻转180度。
第二步,利用filter2D()函数完成接下来的矩阵移动操作。

所以为了实现sobel函数不仅需要平滑算子函数以及差分算子函数,还需要卷积运算函数(将flip()函数和filter2D()函数封装后)。又因为sobel边缘检测算子属于可分离的卷积运算,所以还需要分别定义两个离散二维卷积,一个是先进行垂直方向上的绝技,一个是先进行水平方向上的卷积。
综上可知,需要定义四个主要的函数来完成sobel算子检测边缘的任务,另外还需要定义四个辅助的卷积函数一同完成任务

三、 sobel算子的实现代码

#主要函数的核心代码如下:

//得到平滑算子的函数
Mat getPascalSmooth(int n)
{
    Mat pascalSmooth = Mat::zeros(Size(n,1),CV_32FC1);
    for(int i = 0; i < n; i++)
    {
        //利用二项式展开式公式
        pascalSmooth.at<float>(0,i) = factorial(n-1) / (factorial(i) * factorial(n-1-i));
    }
    return pascalSmooth;
}

//得到差分算子的函数
Mat getPascalDiff(int n)
{
    Mat pascalDiff = Mat::zeros(Size(n,1), CV_32FC1);
    //差分算子的前身是n-2阶的二项式展开式,与平滑算子相差1
    Mat pascalSmooth_previous = getPascalSmooth(n - 1);
    //利用差分算子的获得过程,改变Mat
    for (int i = 0; i < n; i++)
    {
        if(i == 0)
            //补0后反向差分一定是1
            pascalDiff.at<float>(0,i) = 1;
        else if( i == n - 1)
        //补0后反向差分一定是-1
            pascalDiff.at<float>(0, i) = -1;
        else
            //实现反向差分
            pascalDiff.at<float>(0, i) = 
pascalSmooth_previous.at<float>(0, i) -
            pascalSmooth_previous.at<float>(0, i-1);
    }
    return pascalDiff;
}


//卷积运算
void conv2D(InputArray src, InputArray kernel,OutputArray dst, int ddepth,
            Point anchor = Point(-1,-1), int borderType = BORDER_DEFAULT )
{
    //卷积运算的第一步:卷积核逆时针翻转180度
    Mat kernelFlip;
    flip(kernel, kernelFlip, -1);
    //卷积运算第二步
    filter2D(src, dst , ddepth, kernelFlip, anchor, 0.0, borderType);

}


//可分离的离散二维卷积,先进行垂直方向上的卷积,然后进行水平方向上的卷积
void sepConv2D_Y_X(InputArray src, OutputArray src_kerY_kerX, int ddepth,
                   InputArray kernelY, InputArray kernelX, Point anchor = Point(-1,-1),
                   int borderType = BORDER_DEFAULT)
                   {
                       //输入矩阵与垂直方向上的卷积核的卷积
                       Mat src_kerY;
                       conv2D(src, kernelY, src_kerY, ddepth, anchor, borderType);
                       //上面得到的卷积结果,接着和水平方向上的卷积核卷积
                       conv2D(src, kernelX, src_kerY_kerX, ddepth, anchor, borderType);

                   }


//可分离的离散二维卷积,先进行水平方向上的卷积,然后进行垂直方向上的卷积
void sepConv2D_X_Y(InputArray src, OutputArray src_kerX_kerY, int ddepth,
                   InputArray kernelX, InputArray kernelY, Point anchor = Point(-1,-1),
                   int borderType = BORDER_DEFAULT)
                   {
                       //输入矩阵与水平方向上的卷积核的卷积
                       Mat src_kerX;
                       conv2D(src, kernelX, src_kerX, ddepth, anchor, borderType);
                       //上面得到的卷积结果,接着和垂直方向上的卷积核卷积
                       conv2D(src, kernelY, src_kerX_kerY, ddepth, anchor, borderType);

                   }



//定义sobel函数,完成图像矩阵与sobel核的卷积
Mat sobel(Mat image, int x_flag, int y_flag, int winSize, int borderType)
{	
    //sobel 卷积核的窗口大小为大于3 的奇数
    CV_Assert(winSize >= 3 && winSize % 2 == 1);
    //利用函数得到平滑算子
    Mat pascalSmooth = getPascalSmooth(winSize);
    //利用函数得到差分算子
    Mat pascalDiff = getPascalDiff(winSize);
    Mat image_con_sobel;
    //当 x_flag != 0时,返回图像与水平方向上的sobel核的卷积
    if( x_flag != 0)
    {
        //根据可分离卷积核的性质
        //先进行一维垂直方向上的平滑,在进行一维水平方向上的差分
        sepConv2D_Y_X(image, image_con_sobel, CV_32FC1, 
pascalSmooth.t(),pascalDiff, Point(-1, -1), borderType);
    }
    //当 x_flag == 0 且 y_flag != 0 时,返回图像与垂直方向上的sobel核的卷积
    if(x_flag == 0 && y_flag != 0 )
    {
        //根据可分离卷积核的性质
        //先进行一维水平方向上的平滑,在进行一维垂直方向上的差分
        sepConv2D_X_Y(image, image_con_sobel, CV_32FC1, 
pascalSmooth,pascalDiff.t(), Point(-1,-1), borderType);
    }
    return image_con_sobel;
}
四、sobel()的使用

sobel()函数的使用效果如下:

原图
原图

效果图
效果图

以上内容参考:
《OpenCV算法精解》

附加

OpenCV中的sobel()函数

Sobel(src,dst,ddepth,dx,dy,ksize=3,scale=1,delta=0,borderType=BORDER_DEFAULT)

实现了边缘检测的功能。
其中
src表示输入矩阵;
dst表示输出矩阵,即src与sobel核得到的卷积;
ddepth表示输出矩阵的数据类型;
dx,dy表示src与差分方向为水平方向上的Sobel核卷积(dx不为0)以及src与差分方向为垂直方向上的Sobel核卷积(dx为0,dy不为0);
ksize表示sobel核的尺寸,值一般取奇数;
scale表示比例系数,呈现的比例;
delta表示平移系数;
borderType表示边界扩充的类型,默认值是ORDER_DEFAULT。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值