常见的边缘检测Mask

12 篇文章 0 订阅
5 篇文章 0 订阅

常见的边缘检测Mask

区分图像中目标(前景)与背景中的方法称之为图像分割,传统中图像分割的方法包括:1. 阈值处理 2. 边缘检测 3. 区域生长。其中阈值处理算法使用较为广泛,但面对复杂的图像时,使用阈值处理难以准确分割,此时就需要使用边缘检测算法或区域生长算法。

边缘是两个不同图像区域之间的边界点所形成的。在实际的应用中,图像的边缘往往被经以为图像局部强度变化最剧烈的部分。这就意味着这些部分区域的梯度很大,因此可以通过求梯度变化剧烈的部分间接的定位边缘位置。

与连续函数求梯度的方式不同,因为图像是以矩阵的方式进行存储,所以我们借助模板对原图像进行卷积运算,来达到相似的效果。对图像求梯度就转化成了利用模板对原图像进行卷积的过程。

Sobel算子

Sobel算子是边缘检测算法中应用最为广泛的一种算法,其优点在于方法简单,处理速度快,且得到的边缘光滑、连续。
Sobel算子模板:
G x = [ − 1 0 1 − 2 0 2 − 1 0 1 ] , G y = [ − 1 − 2 − 1 0 0 0 1 2 1 ] \textbf{G}_x = \begin{bmatrix} -1 & 0 & 1 \\\\ -2 & 0 & 2 \\\\ -1 & 0 & 1 \end{bmatrix},\textbf{G}_y = \begin{bmatrix} -1 & -2 & -1 \\\\ 0 & 0 & 0 \\\\ 1 & 2 & 1 \end{bmatrix} Gx=121000121,Gy=101202101
对Sobel G x \textbf{G}_x Gx方向的模板源码实现:


#include <iostream>
#include "opencv2/core.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp" // 转灰度用


using namespace std;
using namespace cv;

int main(int argc,char** argv){
    Mat src = imread("./imgSrc/2.jpg");
    if(src.empty()){
        cerr<<"Load image fail"<<endl;
        return -1;
    }
    namedWindow("inputImage",WINDOW_AUTOSIZE);
    imshow("inputImage",src);
    Size imageSize = src.size(); // 读取图像的尺寸
    Mat grayImage; // 灰度图像
    cvtColor(src,grayImage,COLOR_BGR2GRAY);
    Mat dst_my = Mat::zeros(imageSize,CV_8UC1); // 输出的结果图像
    // 取图像每一行的头指针进行访问,为了不考虑边界,我直接从第二行第二列开始计算了
    for (int row = 1; row < imageSize.height - 1; ++row) {
        uchar *up_row = grayImage.ptr(row - 1);
        uchar *current_row = grayImage.ptr(row);
        uchar *down_row = grayImage.ptr(row + 1);
        for (int col = 1; col < imageSize.width - 1; ++col) {
            dst_my.at<uchar>(row, col) = saturate_cast<uchar>(
                    up_row[col - 1] * -1 + up_row[col + 1] * 1 + current_row[col - 1] * -2 + current_row[col + 1] * 2 +
                    down_row[col] * -1 + down_row[col] * 1); // 实现上面的公式,saturate_cast函数防止出现大于8U的值出现,将最大值限制到 255

        }
    }
    namedWindow("outputImage",WINDOW_AUTOSIZE);
    imshow("outputImage",dst_my);
    waitKey();
    cout<<"Hello OpenCV World"<<endl;
    return 0;
}

结果:
在这里插入图片描述
当然,自己重复造轮子是没有什么意义的,可以使用OpenCV中的Api实现自己定义卷积模板:

//
// Created by lucas on 2020/8/29.
//

#include <iostream>
#include "opencv2/core.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp"


using namespace std;
using namespace cv;

int main(int argc,char** argv){
    Mat src = imread("./imgSrc/2.jpg");
    if(src.empty()){
        cerr<<"Load image fail"<<endl;
        return -1;
    }
    namedWindow("inputImage",WINDOW_AUTOSIZE);
    imshow("inputImage",src);
    Size imageSize = src.size(); // 读取图像的尺寸
    Mat grayImage; // 灰度图像
    cvtColor(src,grayImage,COLOR_BGR2GRAY);
    Mat dst_my = Mat::zeros(imageSize,CV_8UC1); // 输出的结果图像
    Mat dst_filter2D = Mat::zeros(imageSize,CV_8UC1); // 输出的结果图像
    //
    for (int row = 1; row < imageSize.height - 1; ++row) {
        uchar *up_row = grayImage.ptr(row - 1);
        uchar *current_row = grayImage.ptr(row);
        uchar *down_row = grayImage.ptr(row + 1);
        for (int col = 1; col < imageSize.width - 1; ++col) {
            dst_my.at<uchar>(row, col) = saturate_cast<uchar>((
                    up_row[col - 1] * -1 + up_row[col + 1] * 1 + current_row[col - 1] * -2 + current_row[col + 1] * 2 +
                    down_row[col] * -1 + down_row[col] * 1));

        }
    }
    Mat kernel_Gx = (Mat_<int>(3,3)<<-1,0,1,-2,0,2,-1,0,1);
    filter2D(grayImage,dst_filter2D,-1,kernel_Gx,Point(-1,-1),0,BORDER_DEFAULT);
    namedWindow("outputImage_filter2d",WINDOW_AUTOSIZE);
    imshow("outputImage_filter2d",dst_filter2D);
    namedWindow("outputImage",WINDOW_AUTOSIZE);
    imshow("outputImage",dst_my);
    waitKey();
    cout<<"Hello OpenCV World"<<endl;
    return 0;
}

结果:

在这里插入图片描述
如果只对x方向做差分,可以使用另一个Api-getDerivKernels + sepFilter2D函数实现:

    Mat kernel_Gx,kernel_Gy;
    getDerivKernels(kernel_Gx,kernel_Gy,1,0,3,false,BORDER_DEFAULT);
    sepFilter2D(grayImage,dst_filter2D,-1,kernel_Gx,kernel_Gy,Point(-1,-1),0,BORDER_CONSTANT);

结果:
注意outputImage_filter2d窗口与左边窗口交界处的白线,那是由于在sepFilter2D中我设置了边界为BORDER_CONSTANT.
在这里插入图片描述

有了API的加成,定义其他滤波模板就十分容易。
Scharr模板(严格来说是滤波器,不是算子),与Sobel相比只是没有ksize的大小。

https://blog.csdn.net/sundanping_123/article/details/86503533

Scharr x = [ − 3 0 3 − 10 0 10 − 3 0 3 ] , Scharr y = [ − 3 − 10 − 3 0 0 0 3 10 3 ] \textbf{Scharr}_x = \begin{bmatrix} -3 & 0 & 3 \\\\ -10 & 0 & 10 \\\\ -3 & 0 & 3 \end{bmatrix},\textbf{Scharr}_y = \begin{bmatrix} -3 & -10 & -3 \\\\ 0 & 0 & 0 \\\\ 3 & 10 & 3 \end{bmatrix} Scharrx=31030003103,Scharry=30310010303
Laplace
laplace = [ 0 1 0 1 − 4 1 0 1 0 ] \textbf{laplace} = \begin{bmatrix} 0 & 1 & 0 \\\\ 1 & -4 & 1 \\\\ 0 & 1 & 0 \end{bmatrix} laplace=010141010
Robert
Robert x = [ 1 0 0 − 1 ] , Robert y = [ 0 1 − 1 0 ] \textbf{Robert}_x = \begin{bmatrix} 1 & 0 \\\\ 0& -1 \end{bmatrix},\textbf{Robert}_y = \begin{bmatrix} 0 & 1 \\\\ -1 & 0 \end{bmatrix} Robertx=1001,Roberty=0110
结果(调用的API):

#include <iostream>
#include <string>
#include <vector>

#include "opencv2/core.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp"
//#include "opencv2/opencv.hpp"

using namespace std;
using namespace cv;

int main(int argc, char **argv) {
    Mat src = imread("imgSrc/2.jpg");
    if (src.empty()) {
        cerr << "Error in Src" << endl;
        return -1;
    }
    namedWindow("inputImage", WINDOW_AUTOSIZE);
    imshow("inputImage", src);
    Size imageSize = src.size();
    int imageChannel = src.channels();
    Mat grayImage = Mat(imageSize, CV_8UC1);
    cvtColor(src, grayImage, COLOR_BGR2GRAY);
    Mat dst_my = Mat::zeros(imageSize, CV_8UC1);// 输出图像
    Mat dst_sobel = Mat::zeros(imageSize, CV_8UC1);// 输出图像
    // 对图像进行模糊处理,Sobel处理
    for (int row = 1; row < imageSize.height - 1; ++row) {
        uchar *up_row = grayImage.ptr(row - 1);
        uchar *current_row = grayImage.ptr(row);
        uchar *down_row = grayImage.ptr(row + 1);
        for (int col = 1; col < imageSize.width - 1; ++col) {
            dst_my.at<uchar>(row, col) = saturate_cast<uchar>(
                    up_row[col - 1] * -1 + up_row[col + 1] * 1 + current_row[col - 1] * -2 + current_row[col + 1] * 2 +
                    down_row[col] * -1 + down_row[col] * 1);

        }
    }

    Sobel(grayImage,dst_sobel,-1,1,0,3,1,0,BORDER_DEFAULT); // 已经包含了高斯平滑和差分处理,因此对噪声不敏感,这里只对x方向求一介差分
    namedWindow("源码实现",WINDOW_AUTOSIZE);
    imshow("源码实现",dst_my);
    namedWindow("Sobel_result",WINDOW_AUTOSIZE);
    imshow("Sobel_result",dst_sobel);

    Mat dst_scharr = Mat::zeros(imageSize, CV_8UC1);// 输出图像
    Scharr(grayImage,dst_scharr,-1,1,0,1,0,BORDER_DEFAULT); // 使用Scharr算子计算一阶差分,等同于𝚂𝚘𝚋𝚎𝚕(𝚜𝚛𝚌, 𝚍𝚜𝚝, 𝚍𝚍𝚎𝚙𝚝𝚑, 𝚍𝚡, 𝚍𝚢, 𝙲𝚅_𝚂𝙲𝙷𝙰𝚁𝚁, 𝚜𝚌𝚊𝚕𝚎, 𝚍𝚎𝚕𝚝𝚊, 𝚋𝚘𝚛𝚍𝚎𝚛𝚃𝚢𝚙𝚎).
    namedWindow("Scharr_result",WINDOW_AUTOSIZE);
    imshow("Scharr_result",dst_scharr);

    Mat dst_laplace = Mat::zeros(imageSize, CV_8UC1);// 输出图像
    Laplacian(grayImage,dst_laplace,-1,1,1,0,BORDER_DEFAULT);
    namedWindow("Laplace_result",WINDOW_AUTOSIZE);
    imshow("Laplace_result",dst_laplace);

    Mat dst_canny = Mat::zeros(imageSize, CV_8UC1);// 输出图像
    Canny(grayImage,dst_canny,20,200,3,false);
    namedWindow("Canny_result",WINDOW_AUTOSIZE);
    imshow("Canny_result",dst_canny);

    Mat dst_robert = Mat::zeros(imageSize, CV_8UC1);// 输出图像
    Mat kernel_robert = (Mat_<int>(2,2)<<1,0,0,-1);
    filter2D(grayImage,dst_robert,-1,kernel_robert,Point(-1,-1),0,BORDER_DEFAULT);
    namedWindow("Robert_result",WINDOW_AUTOSIZE);
    imshow("Robert_result",dst_robert);

    Mat dst_My_struct = Mat::zeros(imageSize, CV_8UC1);// 输出图像
    Mat MY_struct = (Mat_<int>(3,3)<< -1,0,1,-2,0,2,-1,0,1);
    filter2D(grayImage,dst_My_struct,-1,MY_struct,Point(-1,-1),0,BORDER_DEFAULT);
    namedWindow("My_struct",WINDOW_AUTOSIZE);
    imshow("My_struct",dst_My_struct);

    waitKey();
    return 0;
}

在这里插入图片描述

Canny的算法就比较麻烦:包含了 非极大值抑制,滞后阈值处理算法×××××
头大……有空再说吧!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值