专栏首页GiantPandaCVOpenCV图像处理专栏十八 | 手动构造Sobel算子完成边缘检测

OpenCV图像处理专栏十八 | 手动构造Sobel算子完成边缘检测

1. 前言

众所周知,在传统的图像边缘检测算法中,最常用的一种算法是利用Sobel算子完成的。Sobel算子一共有个,一个是检测水平边缘的算子,另一个是检测垂直边缘的算子。

2. Sobel算子优缺点

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

3. 手动构造Sobel算子

我们来看一下怎么构造Sobel算子?

Sobel算子是在一个坐标轴的方向进行非归一化的高斯平滑,在另外一个坐标轴方向做一个差分,大小的Sobel算子是由平滑算子差分算子全卷积得到,其中代表Sobel算子的半径,必须为奇数。

对于窗口大小为的非归一化Sobel平滑算子等于阶的二项式展开式的系数,而Sobel平滑算子等于阶的二项式展开式的系数两侧补,然后向前差分。

举个例子:构造一个阶的Sobel非归一化的Sobel平滑算子和Sobel差分算子

Sobel平滑算子:取二项式的阶数为,然后计算展开式系数为, 也即是,这就是阶的非归一化的Sobel平滑算子。

Sobel差分算子:取二项式的阶数为,然后计算二项展开式的系数,即为:,两侧补 并且前向差分得到,第项差分后可以直接删除。

Sobel算子将阶的Sobel平滑算子和Sobel差分算子进行全卷积,即可得到的Sobel算子。

其中方向的Sobel算子为:

而方向的Sobel算子为:

4. 代码实现

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

5. 效果

经典人物Lena

先进行Y方向的Sobel运算,然后再进行X方向的结果

先进行X方向的Sobel运算,然后再进行Y方向的结果

虞姬原图

先进行Y方向的Sobel运算,然后再进行X方向的结果

先进行X方向的Sobel运算,然后再进行Y方向的结果

可以看到两种不同的操作顺序会获得不完全一样的边缘检测效果。

6. 结论

这篇文章介绍了边缘检测是如何手动构造的,只要熟记二项式展开的系数,以此为出发点就比较好分析了。后面的源码实现也是比较朴素的实现,如果你想加速那么重心可以放在filter2D也即是卷积操作上,以后会来分享的。

本文分享自微信公众号 - GiantPandaCV(BBuf233),作者:BBuf

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-03-16

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • LeetCode第166场周赛题解

    这是LeetCode的第166场周赛的题解,不出意外的又爆炸了,前三题只做了20分钟,第4题因为题意读错了耽误了40分钟,到1小时15分钟左右才写完。排名直接1...

    BBuf
  • Leetcode 第 167 场周赛题解

    BBuf
  • OpenCV图像处理专栏一 | 盘点常见颜色空间互转

    今天是OpenCV传统图像处理算法的第一篇,我们来盘点一下常见的6种颜色空间互转算法,并给出了一些简单的加速方案,希望可以帮助到学习OpenCV图像处理的同学。...

    BBuf
  • 动态规划(1)

    使用动态规划求解问题,最重要的就是确定动态规划三要素: (1)问题的阶段 (2)每个阶段的状态 (3)从前一个阶段转化到后一个阶段之间的...

    用户4492257
  • 【2020HBU天梯赛训练】7-45 悄悄关注

    新浪微博上有个“悄悄关注”,一个用户悄悄关注的人,不出现在这个用户的关注列表上,但系统会推送其悄悄关注的人发表的微博给该用户。现在我们来做一回网络侦探,根据某人...

    韩旭051
  • Linux下配置Apache httpd

    Leshami
  • OpenCV进行图像相似度对比的几种办法

    PSNR(Peak Signal to Noise Ratio),一种全参考的图像质量评价指标。

    用户1539362
  • 野路子搞算法 · 让算法可视化《leetcode03.无重复字符的最长子串》

    在刷了第一道 leetcode 的题以后我一直在思考,怎么才能让小白更清楚的了解到整个算法运行的过程。如果只是单纯的一点点看代码,从中摸清楚整个流程确实还是有一...

    小傅哥
  • SpringDataJPA之PagingAndSortingRepository接口

    PagingAndSortingRepository 接口继承于 CrudRepository 接口,拥有CrudRepository 接口的所有方法, 并新增...

    用户4919348
  • 第三章 C++中的C ----《C++编程思想》

    1 创建函数 2 执行控制语句   break:退出循环,不再执行循环中的生育语句   continue:停止执行当前的循环,返回到循环的起始处开始新的一轮循环...

    用户1154259

扫码关注云+社区

领取腾讯云代金券