美文网首页互联网@时事传播图像处理OpenCv
【图像处理】OpenCV系列十二 --- 边缘检测之Laplac

【图像处理】OpenCV系列十二 --- 边缘检测之Laplac

作者: 307656af5a04 | 来源:发表于2019-04-25 18:45 被阅读11次

    上一篇我们学习了边缘检测相关的Sobel算了,经过学习之后,我们应该可以对Sobel算子进行熟悉的运用,那么今天,我们一起来学习一下边缘检测相关的Laplacian(拉普拉斯)算子以及scharr滤波器。

    一、理论

    Laplacian 算子是n维欧几里德空间中的一个二阶微分算子,定义为梯度grad()的散度div(),因此如果f是二阶可微的实函数,则f的拉普拉斯算子定义为:

    (1) f的拉普拉斯算子也是笛卡儿坐标系xi中的所有非混合二阶偏导数求和;

    (2) 作为一个二阶微分算子,拉普拉斯算子把C函数映射到C函数,对于k ≥ 2。表达式(1)(或(2))定义了一个算子Δ :C(R) → C(R),或更一般地,定义了一个算子Δ : C(Ω) → C(Ω),对于任何开集Ω。

    根据图像处理的原理我们知道,二阶导数可以用来进行检测边缘 。 因为图像是 “二维”,我们需要在两个方向进行求导,使用Laplacian算子将会使求导过程变得简单。

    由于 Laplacian使用了图像梯度,它内部的代码其实是调用了 Sobel 算子的。

    另附一个小tips:让一幅图像减去它的Laplacian可以增强对比度

    离散函数导数
    离散函数的导数退化成了差分,一维一阶差分公式和二阶差分公式分别为

    一维一阶差分公式和二阶差分公式

    Laplace算子的差分形式
    分别对Laplace算子x,y两个方向的二阶导数进行差分就得到了离散函数的Laplace算子
    在一个二维函数f(x,y)中,x,y两个方向的二阶差分分别为,

    x,y两个方向的二阶差分

    所以Laplace算子的差分形式为,

    Laplacian算子的差分形式

    函数的拉普拉斯算子也是该函数的Hessian 矩阵的迹,可以证明,它具有各向同性,即与坐标轴方向无关,坐标轴旋转后梯度结果不变。如果邻域系统是4 邻域,Laplacian 算子的模板为:

    4邻域Laplacian 算子的模板

    如果邻域系统是8 邻域,Laplacian 算子的模板为:

    8邻域Laplacian 算子的模板

    Laplacian 算子对噪声比较敏感,所以图像一般先经过平滑处理,因为平滑处理也是用模板进行的,所以,通常的分割算法都是把Laplacian 算子和平滑算子结合起来生成一个新的模板。

    二、OpenCV中Laplacian()函数详解

    1、函数原型

    void Laplacian(InputArray src, 
        OutputArray dst, 
        int ddepth, 
        int ksize = 1, 
        double scale = 1, 
        double delta = 0, 
        intborderType = 
        BORDER_DEFAULT);
    

    2、函数功能
    Laplacian函数可以计算出图像经过拉普拉斯变换后的结果。

    3、参数详解

    • 第一个参数,InputArray类型的image,输入图像,即源图像,填Mat类的对象即可,且需为单通道8位图像;

    • 第二个参数,OutputArray类型的edges,输出的边缘图,需要和源图片有一样的尺寸和通道数;

    • 第三个参数,int类型的ddept,目标图像的深度;

    • 第四个参数,int类型的ksize,用于计算二阶导数的滤波器的孔径尺寸,大小必须为正奇数,且有默认值1;
      当ksize>1时,

    ksize

    当ksize = 1,拉普拉斯算子默认采用下面的卷积核

    kszie =1 卷积核
    • 第五个参数,double类型的scale,计算拉普拉斯值的时候可选的比例因子,有默认值1;

    • 第六个参数,double类型的delta,表示在结果存入目标图(第二个参数dst)之前可选的delta值,有默认值0;

    • 第七个参数, int类型的borderType,边界模式,默认值为BORDER_DEFAULT。

    4、实例:实现对摄像头的图像进行Laplacian边缘检测

    #include <opencv2/videoio.hpp>
    #include <opencv2/highgui.hpp>
    #include <opencv2/imgproc.hpp>
    #include <ctype.h>
    #include <stdio.h>
    #include <iostream>
    
    using namespace cv;
    using namespace std;
    
    static void help()
    {
        cout <<
            "\nThis program demonstrates \
            Laplace point/edge detection \
            using OpenCV function Laplacian()\n"
    
            "It captures from the camera of \
            your choice: 0, 1, ... default 0\n"
            "Call:\n"
    
            "./laplace -c=<camera #, default 0> \
            -p=<index of the frame to be \
            decoded/captured next>\n" << endl;
    }
    
    // 三种滤波
    enum { GAUSSIAN, BLUR, MEDIAN };
    
    int sigma = 3;
    
    // 默认滤波为高斯滤波
    int smoothType = GAUSSIAN;
    
    const char* keys =
    {
        "{ c | 0 | }{ p | | }"
    };
    
    int main(int argc, char** argv)
    {
        cv::CommandLineParser parser(argc, 
            argv, keys );
    
        help();
    
        VideoCapture cap;  // 摄像机接口
    
        // 获取摄像机参数
        string camera = parser.get<string>("c");
        if (camera.size() == 1 && isdigit(camera[0]))
            cap.open(parser.get<int>("c"));
        else
            cap.open(samples::findFileOrKeep(camera));
    
        // 判断相机是否打开
        if (!cap.isOpened())
        {
            cerr << "Can't open camera/video stream: " 
                << camera << endl;
    
            return 1;
        }
        // 打开相机后,输出相机的相关信息
        cout << "Video " << parser.get<string>("c") <<
            ": width=" << cap.get(CAP_PROP_FRAME_WIDTH) <<
            ", height=" << cap.get(CAP_PROP_FRAME_HEIGHT) <<
            ", nframes=" << cap.get(CAP_PROP_FRAME_COUNT) << endl;
    
        int pos = 0;
    
        if (parser.has("p"))
        {
            pos = parser.get<int>("p");
        }
    
        if (!parser.check())
        {
            parser.printErrors();
            return -1;
        }
    
        if (pos != 0)
        {
            cout << "seeking to frame #" << pos << endl;
    
            // 设置视频的播放位置到pos
            if (!cap.set(CAP_PROP_POS_FRAMES, pos))
            {
                cerr << "ERROR: seekeing is not supported" << endl;
            }
        }
    
        namedWindow("Laplacian", 
            WINDOW_AUTOSIZE);
    
        // 创建滑动条
        createTrackbar("Sigma", 
            "Laplacian", 
            &sigma, 
            15,
            0);
    
        Mat smoothed, laplace, result;
    
        for (;;)
        {
            Mat frame;
            // 将相机中的每一帧图像存储到frame中
            cap >> frame;
    
            if (frame.empty())
                break;
    
            // 卷积核大小
            int ksize = (sigma * 5) | 1;
    
            // 高斯滤波
            if (smoothType == GAUSSIAN)
                GaussianBlur(frame, smoothed, 
                    Size(ksize, ksize), 
                    sigma, sigma);
    
            // 均值滤波
            else if (smoothType == BLUR)
                blur(frame, smoothed, 
                    Size(ksize, ksize));
    
            // 中值滤波
            else
                medianBlur(frame, smoothed, ksize);
    
            // 进行拉普拉斯边缘检测处理
            Laplacian(smoothed, laplace, CV_16S, 5);
    
            convertScaleAbs(laplace, result, 
                (sigma + 1)*0.25);
    
            imshow("Laplacian", result);
    
            // 等待30毫秒在读取下一帧图像
            // 即帧率为30
            char c = (char)waitKey(30);
            
            // 单击空格切换各种滤波模式
            if (c == ' ')
                smoothType = smoothType == GAUSSIAN ? BLUR : 
                smoothType == BLUR ? MEDIAN : GAUSSIAN;
    
            // 退出循环
            if (c == 'q' || c == 'Q' || c == 27)
                break;
        }
        return 0;
    }
    

    三、实例中一些函数的解释

    1、CommandLineParser类

    功能:对命令行进行解析。
    构造函数:

    CommandLineParser(int argc, 
        const char* const argv[], 
        const String& keys);
    

    参数详解:
    第一个参数:argc 命令行参数的数量;
    第二个参数:命令行参数的命令;
    第三个参数:第3个就是刚刚定义的keys了,keys的结构有一定规律,比如说"{c |camera |false | use camera or not}" 都是用大括号和双引号引起来,然后中间的内容分成4断,用”|”分隔开,分别表示简称,文件来源,文件值和帮助语句。第二行和第三行表示打开摄像头和打开文件,文件的文件名等都在keys指针中了。

    2、convertScaleAbs()函数
    函数原型:

    void convertScaleAbs(InputArray src,
        OutputArray dst,
        double alpha = 1, 
        double beta = 0);
    

    函数功能:
    计算图像每个像素的绝对值,并将结果转换为8位无符号整数。

    参数详解:

    • 第一个参数:src ,原图像
    • 第二个参数:dst ,目标图像 (深度为 8u).
    • 第三个参数:scale ,成数因子.
    • 第四个参数:shift,偏移量

    转换公式

    convertScaleAbs公式

    四、scharr滤波器

    scharr滤波器在OpenCV中主要是配合Sobel算子的运算而存在的。

    函数原型

    void Scharr(InputArray src, 
        OutputArray dst, 
        int ddepth,
        int dx, 
        int dy, 
        double scale = 1, 
        double delta = 0,
        int borderType = 
        BORDER_DEFAULT);
    

    函数功能
    该函数的功能与sobel算子基本上上一样,但是Scharr滤波器仅作用于大小为3的内核,具有和sobel算子一样的速度,但结果更为精确。

    参数详解

    • 第一个参数,InputArray 类型的src,为输入图像,填Mat类型即可;

    • 第二个参数,OutputArray类型的dst,即目标图像,函数的输出参数,需要和源图片有一样的尺寸和类型;

    • 第三个参数,int类型的ddepth,输出图像的深度,支持如下src.depth()和ddepth的组合:

        若src.depth() = CV_8U, 
        取ddepth = -1 / CV_16S / CV_32F / CV_64F
      
        若src.depth() = CV_16U / CV_16S, 
        取ddepth = -1 / CV_32F / CV_64F
      
        若src.depth() = CV_32F, 
        取ddepth = -1 / CV_32F / CV_64F
      
        若src.depth() = CV_64F, 
        取ddepth = -1 / CV_64F
      
    • 第四个参数,int类型dx,x方向上的差分阶数;

    • 第五个参数,int类型dy,y方向上的差分阶数;

    • 第六个参数,double类型的scale,计算导数值时可选的缩放因子,默认值是1,表示默认情况下是没有应用缩放的。我们可以在文档中查阅getDerivKernels的相关介绍,来得到这个参数的更多信息

    • 第七个参数,double类型的delta,表示在结果存入目标图(第二个参数dst)之前可选的delta值,有默认值0;

    • 第八个参数, int类型的borderType,边界模式,默认值为BORDER_DEFAULT。

    实例

    #include <opencv2/opencv.hpp>
    
    using namespace cv;
    
    int main()
    {
        //创建 grad_x 和 grad_y 矩阵
        Mat grad_x, grad_y;
        Mat abs_grad_x, abs_grad_y, dst;
    
        //载入原始图  
        Mat src = imread("lena.png");
    
        //显示原始图 
        imshow("【原始图】Scharr滤波器", src);
    
        //求 X方向梯度
        Scharr(src, grad_x, CV_16S, 
            1, 0, 1, 0, BORDER_DEFAULT);
    
        convertScaleAbs(grad_x, abs_grad_x);
    
        imshow("【效果图】 X方向Scharr", abs_grad_x);
    
        //求Y方向梯度
        Scharr(src, grad_y, CV_16S,
            0, 1, 1, 0, BORDER_DEFAULT);
    
        convertScaleAbs(grad_y, abs_grad_y);
    
        imshow("【效果图】Y方向Scharr", abs_grad_y);
    
        //合并梯度(近似)
        addWeighted(abs_grad_x, 0.5, 
            abs_grad_y, 0.5, 
            0, dst);
    
        //显示效果图
        imshow("【效果图】合并梯度后Scharr", dst);
    
        waitKey(0);
        return 0;
    }
    

    实验结果:

    Scharr原图 Scharr效果图

    好了,今天的OpenCV学到这里就结束了,喜欢的朋友可以给我点个赞哦!!!

    相关文章

      网友评论

        本文标题:【图像处理】OpenCV系列十二 --- 边缘检测之Laplac

        本文链接:https://www.haomeiwen.com/subject/gquwgqtx.html