前言

在边缘检测中,常用的一种算子是Sobel算子。Sobel算子一共有2个,一个检测水平边缘的算子和一个检测垂直边缘的算子。Sobel算子的优点是可以利用快速卷积函数,简单有效,且对像素位置的影响做了加权,可以降低边缘模糊程度,有较好效果。然而Sobel算子并没有基于图像灰度进行处理,所以在提起图像边缘信息的时候可能不会让人的视觉满意。

算法原理

首先来看一下如歌构造Sobel算子?Sobel算子是在一个坐标轴的方向进行非归一化的高斯平滑,在另外一个坐标轴方向做一个差分,kszie*ksize的算子是由平滑算子和差分算子全卷积得到,其中ksize必须为奇数。对于窗口大小为ksize的非归一化Sobel平滑算子等于ksize-1阶的二项式展开式的系数,而Sobel平滑算子等于ksize-2阶的二项式展开式的系数两侧补0,然后向前差分。举个例子:构造一个4阶的Sobel非均一化的Sobel算子和Sobel差分算子:

  • 取二项式系数为n=3,然后计算展开式系数为, [ C 3 0 , C 3 1 , C 3 2 , C 3 3 ] [C_3^0, C_3^1, C_3^2, C_3^3] [C30,C31,C32,C33]即是[1, 3, 3, 3],这就是4阶的非均一化的Sobel平滑算子。
  • 取二项式系数n=4-2=2,然后计算展开式的系数,即为: [ C 2 0 , C 2 1 , C 2 2 ] [C_2^0, C_2^1, C_2^2] [C20,C21,C22],两侧补0并且前向差分得到[1, 1, -1, -1],第5项差分后可以直接删除。
  • 将4阶的Sobel平滑算子和Sobel差分算子进行全卷积,即可得到4*4的Sobel算子。
    s o b l e x = [ <mstyle displaystyle="false" scriptlevel="0"> 1 </mstyle> <mstyle displaystyle="false" scriptlevel="0"> 3 </mstyle> <mstyle displaystyle="false" scriptlevel="0"> 3 </mstyle> <mstyle displaystyle="false" scriptlevel="0"> 1 </mstyle> ] [ <mstyle displaystyle="false" scriptlevel="0"> 1 </mstyle> <mstyle displaystyle="false" scriptlevel="0"> 1 </mstyle> <mstyle displaystyle="false" scriptlevel="0"> 1 </mstyle> <mstyle displaystyle="false" scriptlevel="0"> 1 </mstyle> ] = [ <mstyle displaystyle="false" scriptlevel="0"> 1 </mstyle> <mstyle displaystyle="false" scriptlevel="0"> 1 </mstyle> <mstyle displaystyle="false" scriptlevel="0"> 1 </mstyle> <mstyle displaystyle="false" scriptlevel="0"> 1 </mstyle> <mstyle displaystyle="false" scriptlevel="0"> 3 </mstyle> <mstyle displaystyle="false" scriptlevel="0"> 3 </mstyle> <mstyle displaystyle="false" scriptlevel="0"> 3 </mstyle> <mstyle displaystyle="false" scriptlevel="0"> 3 </mstyle> <mstyle displaystyle="false" scriptlevel="0"> 3 </mstyle> <mstyle displaystyle="false" scriptlevel="0"> 3 </mstyle> <mstyle displaystyle="false" scriptlevel="0"> 3 </mstyle> <mstyle displaystyle="false" scriptlevel="0"> 3 </mstyle> <mstyle displaystyle="false" scriptlevel="0"> 1 </mstyle> <mstyle displaystyle="false" scriptlevel="0"> 1 </mstyle> <mstyle displaystyle="false" scriptlevel="0"> 1 </mstyle> <mstyle displaystyle="false" scriptlevel="0"> 1 </mstyle> ] soble_x=\begin{bmatrix} 1 \\3\\3\\1 \end{bmatrix} * \begin{bmatrix} 1 &amp;1 &amp;-1&amp; -1\end{bmatrix}=\begin{bmatrix} 1 &amp;1&amp;-1&amp;-1\\3&amp;3&amp;-3&amp;-3\\3&amp;3&amp;-3&amp;-3\\1&amp;1&amp;-1&amp;-1 \end{bmatrix} soblex=1331[1111]=1331133113311331
    s o b e l y = [ <mstyle displaystyle="false" scriptlevel="0"> 1 </mstyle> <mstyle displaystyle="false" scriptlevel="0"> 3 </mstyle> <mstyle displaystyle="false" scriptlevel="0"> 3 </mstyle> <mstyle displaystyle="false" scriptlevel="0"> 1 </mstyle> ] [ <mstyle displaystyle="false" scriptlevel="0"> 1 </mstyle> <mstyle displaystyle="false" scriptlevel="0"> 1 </mstyle> <mstyle displaystyle="false" scriptlevel="0"> 1 </mstyle> <mstyle displaystyle="false" scriptlevel="0"> 1 </mstyle> ] = [ <mstyle displaystyle="false" scriptlevel="0"> 1 </mstyle> <mstyle displaystyle="false" scriptlevel="0"> 3 </mstyle> <mstyle displaystyle="false" scriptlevel="0"> 3 </mstyle> <mstyle displaystyle="false" scriptlevel="0"> 1 </mstyle> <mstyle displaystyle="false" scriptlevel="0"> 1 </mstyle> <mstyle displaystyle="false" scriptlevel="0"> 3 </mstyle> <mstyle displaystyle="false" scriptlevel="0"> 3 </mstyle> <mstyle displaystyle="false" scriptlevel="0"> 1 </mstyle> <mstyle displaystyle="false" scriptlevel="0"> 1 </mstyle> <mstyle displaystyle="false" scriptlevel="0"> 3 </mstyle> <mstyle displaystyle="false" scriptlevel="0"> 3 </mstyle> <mstyle displaystyle="false" scriptlevel="0"> 1 </mstyle> <mstyle displaystyle="false" scriptlevel="0"> 1 </mstyle> <mstyle displaystyle="false" scriptlevel="0"> 3 </mstyle> <mstyle displaystyle="false" scriptlevel="0"> 3 </mstyle> <mstyle displaystyle="false" scriptlevel="0"> 1 </mstyle> ] sobel_y=\begin{bmatrix} 1 &amp;3&amp;3&amp;1 \end{bmatrix}*\begin{bmatrix} 1 \\1\\-1\\-1 \end{bmatrix}=\begin{bmatrix} 1 &amp;3&amp;3&amp;1\\1&amp;3&amp;3&amp;1\\-1&amp;-3&amp;-3&amp;-1\\-1&amp;-3&amp;-3&amp;-1 \end{bmatrix} sobely=[1331]1111=1111333333331111

代码实现

#include "opencv2/opencv.hpp"
#include "iostream"
#include "algorithm"
#include "vector"
#include "stdio.h"
using namespace std;
using namespace cv;

const int fac[9]={1, 1, 2, 6, 24, 120, 720, 5040, 40320};
//Sobel平滑算子
Mat getSmmoothKernel(int ksize){
    Mat Smooth = Mat::zeros(Size(ksize, 1), CV_32FC1);
    for(int i = 0; i < ksize; i++){
        Smooth.at<float>(0, i) = float(fac[ksize-1]/(fac[i] * fac[ksize-1-i]));
    }
    return Smooth;
}
//Sobel差分算子
Mat getDiffKernel(int ksize){
    Mat Diff = Mat::zeros(Size(ksize, 1), CV_32FC1);
    Mat preDiff = getSmmoothKernel(ksize-1);
    for(int i = 0; i < ksize; i++){
        if(i == 0){
            Diff.at<float>(0, i) = 1;
        }else if(i == ksize-1){
            Diff.at<float>(0, i) = -1;
        }else{
            Diff.at<float>(0, i) = preDiff.at<float>(0, i) - preDiff.at<float>(0, i-1);
        }
    }
    return Diff;
}
//调用filter2D实现卷积
void conv2D(InputArray src, InputArray kernel, OutputArray dst, int dep, Point anchor=Point(-1,-1), int borderType=BORDER_DEFAULT){
    Mat kernelFlip;
    flip(kernel, kernelFlip, -1);
    filter2D(src, dst, dep, kernelFlip, anchor, 0.0, borderType);
}
//先进行垂直方向的卷积,再进行水平方向的卷积
void sepConv2D_Y_X(InputArray src, OutputArray dst, int dep, InputArray kernelY, InputArray kernelX, Point anchor=Point(-1,-1), int borderType=BORDER_DEFAULT){
    Mat Y;
    conv2D(src, kernelY, Y, dep, anchor, borderType);
    conv2D(Y, kernelX, dst, dep, anchor, borderType);
}
//先进行水平方向的卷积,再进行垂直方向的卷积
void sepConv2D_X_Y(InputArray src, OutputArray dst, int dep, InputArray kernelX, InputArray kernelY, Point anchor=Point(-1,-1), int borderType=BORDER_DEFAULT){
    Mat X;
    conv2D(src, kernelX, X, dep, anchor, borderType);
    conv2D(X, kernelY, dst, dep, anchor, borderType);
}
//Sobel算子提取边缘信息
Mat Sobel(Mat &src, int x_flag, int y_flag, int kSize, int borderType){
    Mat Smooth = getSmmoothKernel(kSize);
    Mat Diff = getDiffKernel(kSize);
    Mat dst;
    if(x_flag){
        sepConv2D_Y_X(src, dst, CV_32FC1, Smooth.t(), Diff, Point(-1, -1), borderType);
    }else if(x_flag == 0 && y_flag){
        sepConv2D_X_Y(src, dst, CV_32FC1, Smooth, Diff.t(), Point(-1, -1), borderType);
    }
    return dst;
}
int main(){
    Mat src = imread("../lena.jpg");
    Mat gray;
    cvtColor(src, gray, CV_BGR2GRAY);
    Mat dst1 = Sobel(gray, 1, 0, 3, BORDER_DEFAULT);
    Mat dst2 = Sobel(gray, 0, 1, 3, BORDER_DEFAULT);
    //转8位灰度图显示
    convertScaleAbs(dst1, dst1);
    convertScaleAbs(dst2, dst2);
    imshow("origin", gray);
    imshow("result-X", dst1);
    imshow("result-Y", dst2);
    imwrite("../result.jpg", dst1);
    imwrite("../result2.jpg", dst2);
    waitKey(0);
    return 0;
}

效果

原图
沿着X方向做Sobel算子
沿着Y方向做边缘检测