美文网首页
CV02-02:Laplace滤波

CV02-02:Laplace滤波

作者: 杨强AT南京 | 来源:发表于2019-11-16 17:29 被阅读0次

      机器视觉中图像滤波处理应该是很基础,很重要的,本主题梳理下Laplace滤波的原理与实现;


    拉普拉斯算子

    数学表示

    • \Delta ^2 f = \dfrac{\partial^2 f}{\partial x^2} + \dfrac{\partial^2 f}{\partial y^2}

    二阶导数的求值算法

    • 假设计算图像中(x,y)位置的二阶导数,f(x,y)表示图像(x,y)位置的像素值。

    二阶导数的离散表示

    • \Delta^2 f = f(x+1,y) + f(x-1,y) + f(x,y+1) + f(x,y-1) - 4f(x,y)

    • 提示:

      • 该公式的推导,见后面附录。

    矩阵的哈达玛(Hadamard)积表示

    • 矩阵Hadamard积,使用H表示:
      • \begin{aligned} H&= \begin{bmatrix} {0}&{1}&{0}\\{1}&{-4}&{1}\\{0}&{1}&{0}\\\end{bmatrix} \ast \begin{bmatrix} {f(x-1, y-1)}&{f(x, y-1)}&{f(x+1, y-1)}\\{f(x-1, y)}&{f(x,y)}&{f(x+1, y)}\\{f(x-1, y+1)}&{f(x, y+1)}&{f(x+1, y+1)}\\\end{bmatrix} \\ \\ &= \begin{bmatrix} {0}&{f(x, y-1)}&{0}\\{f(x-1, y)}&{-4f(x,y)}&{f(x+1, y)}\\{0}&{f(x, y+1)}&{0}\\\end{bmatrix} \\ \\ &= f(x+1,y) + f(x-1,y) + f(x,y+1) + f(x,y-1) - 4f(x,y) \end{aligned}

    二阶导数的Hadamard积的和表示

    • \Delta^2 f = \sum H

    • 提示:

      • 到目前为止,完美的使用矩阵运算表示了离散图像的二阶导数表示。

    附录一:离散二阶导数的推导

    连续微分

    • 假设函数为f(x)
    1. 一阶导数表示:

      • \dfrac{\partial f}{\partial x} = f ^ \prime
    2. 一阶导数的极限定义:

      • \dfrac{\partial f}{\partial x} = \lim \limits _{\epsilon \to 0} \dfrac{f(x+ \epsilon) - f(x - \epsilon)}{2 \epsilon}
      • \dfrac{\partial f}{\partial x} = \lim \limits _{\epsilon \to 0+} \dfrac{f(x+ \epsilon) - f(x)}{\epsilon}
      • \dfrac{\partial f}{\partial x} = \lim \limits _{\epsilon \to 0-} \dfrac{f(x) - f(x -\epsilon)}{\epsilon}
    3. 二阶微分的定义

      • \dfrac{\partial ^2 f}{\partial x ^2} = \lim \limits _{\epsilon \to 0} \dfrac{f ^\prime(x+ \epsilon) - f^\prime(x - \epsilon)}{2 \epsilon}

    离散导数

    • 对离散的图像函数表示为f(x, y),其中(x,y)表示像素位置。
      • 因为离散,所有无穷小量\epsilon取值为1
    1. 一阶x右导数近似表示:

      • \dfrac{\partial f}{\partial x_+} = f ^\prime _+ = f(x+1,y) - f(x,y)
        • \epsilon为1
    2. 一阶x左导数近似表示:

      • \dfrac{\partial f}{\partial x_-} = f ^\prime _- = f(x,y) - f(x-1,y)
        • \epsilon为1
    3. 二阶x导数近似表示:

      • \begin{aligned} \dfrac{\partial^2 f}{\partial x^2} &= f ^\prime _+ - f ^\prime _-\\ &= f(x+1,y) - f(x,y) - (f(x,y) - f(x-1,y)) \\ &= f(x+1,y) + f(x-1,y) - 2f(x,y) \end{aligned}
        • \epsilon为1
    4. 二阶y导数近似表示:

      • \begin{aligned} \dfrac{\partial^2 f}{\partial y^2} &= f(x,y+1) - f(x,y) - (f(x,y) - f(x,y-1)) \\ &= f(x,y+1) + f(x,y-1) - 2f(x,y) \end{aligned}
    5. Laplace算子:

      • \begin{aligned} \Delta^2 f &= \dfrac{\partial^2 f}{\partial x^2} + \dfrac{\partial^2 f}{\partial y^2} \\ &= f(x+1,y) + f(x-1,y) - 2f(x,y) + f(x,y+1) + f(x,y-1) - 2f(x,y) \\ &= (f(x+1,y) + f(x-1,y) + f(x,y+1) + f(x,y-1)) - 4f(x,y) \end{aligned}
    6. laplace算子的扩展与变形

    说明

    • 正是通过导数(梯度:变化状态或者变化速度),检测像素的变化强度;从而可以检测到图像的边缘。
      • Sobel算子处理结果是像素的变化度效果;
    • 二阶导数,就是最值点(严格来说是极值点),这样也可以检测到图像边缘(严肃变化最大处就是边缘);
      • 拉普拉斯算子处理结果是像素边缘效果(可能存在误报,比如:图像中的山背,很可能检测为边缘,实际可能不是物体边缘)

    一阶导数的图像意义

    • 一阶导数表示的梯度,梯度越大,表示图像像素变化越大。
      • 周围没有变化的像素的梯度为0。
      • 边缘处的像素肯定有比较强烈的变化,所以图像的一阶导数,可以检测到边缘,旦未必都是边缘。

    二阶导数的图像意义

    • 二阶导数可以衡量像素的变化强度,变化均匀的属于同一区域,变化最大的就应该是边缘,所以二阶导数用来检测边缘。
      • 图像中像边缘但不是边缘的部分也会被检测到。
      • 在某些无意义的点上,二阶导数也为0的,这样会被认为是边缘。

    拉普拉斯算子实现

    • 下面手工实现拉普拉斯算子,看看与OpenCV实现的差异。

    • 图像如果想锐化处理,可以增强差异部分,产生更加清晰的效果。

      • h(x, y) = \begin{cases} f(x, y) + c \ast (\dfrac{\partial ^ 2 f(x, y)}{ \partial ^2 x} +\dfrac{\partial ^ 2 f(x, y)}{ \partial ^2 y} )\\ f(x, y) - c \ast (\dfrac{\partial ^ 2 f(x, y)}{ \partial ^2 x} +\dfrac{\partial ^ 2 f(x, y)}{ \partial ^2 y} ) \end{cases}

    一个完整实现的类

    #include "imageprocess.h"
    
    ImageProc::ImageProc():
        filename_image(new cv::String("lotus.png")){
        m_src = cv::imread(*filename_image);
    }
    ImageProc::ImageProc(const char *filename):
        filename_image(new cv::String(filename)){
        m_src = cv::imread(*filename_image);
    }
    ImageProc::~ImageProc(){
        delete filename_image;
        m_filter2d.release();
        m_laplace.release();
        m_channel.release();
        m_channels.release();
        m_src.release();
    }
    void ImageProc::filter2D(){
        cv::Mat kernel = (     // 逗号初始化器
            cv::Mat_<float>(3, 3) <<
                1.0,  1.0, 1.0,
                1.0, -8.0, 1.0,
                1.0,  1.0, 1.0 
        );
        cv::filter2D(this->m_src, this->m_filter2d, -1, kernel, cv::Point(-1,-1), 0.0);
    }
    void ImageProc::laplace(){
        cv::Laplacian(this->m_src, this->m_laplace, -1, 3, 1.0, 0.0);
    }
    void ImageProc::channel(){
        // 转换为浮点数
        cv::Mat in_img;
        m_src.convertTo(in_img, CV_32FC3);    // 从三通道单字节无符号整数,转换为三通道单精度小数
    
        // 定义Laplace差分核(二阶导数)
        cv::Mat kernel = (                      // 便于cv::Mat,定义三通道单精度核。
            cv::Mat_<cv::Vec3f>(3,3) << 
                cv::Vec3f(1.0, 1.0, 1.0), cv::Vec3f( 1.0,  1.0,  1.0), cv::Vec3f(1.0, 1.0, 1.0),
                cv::Vec3f(1.0, 1.0, 1.0), cv::Vec3f(-8.0, -8.0, -8.0), cv::Vec3f(1.0, 1.0, 1.0),
                cv::Vec3f(1.0, 1.0, 1.0), cv::Vec3f( 1.0,  1.0,  1.0), cv::Vec3f(1.0, 1.0, 1.0)
        );
        // 获取图像行列
        int rows = in_img.rows;
        int cols = in_img.cols;
        // 构造一个Padding图像,Padding补0, padding大小(1,1)
        cv::Mat p_img(rows + 2 , cols + 2, in_img.type(), cv::Scalar_<float>(0.0, 0.0, 0.0));
        // 拷贝图像到padding图像
        in_img.copyTo(p_img(cv::Range(1,rows+1), cv::Range(1, cols+1))); 
        // 定义输出(与源图像同大小与类型)
        cv::Mat out_img(rows, cols, in_img.type());
        // 5. 循环计算没有输出像素
        for(int y = 0; y< rows; y++){
            for(int x = 0; x< cols; x++){
                // 获取padding图像的子图
                cv::Mat sub = p_img(cv::Range(y, y + 3), cv::Range(x, x + 3)); // 子图大小为3
                // 计算矩阵hadamard乘积
                cv::Mat prod = sub.mul(kernel); // 子图与kernel矩阵的乘积 
                // 计算矩阵的和
                cv::Scalar pixel = cv::sum(prod);
                // 赋值结果到输出图像
                out_img.at<cv::Vec3f>(y, x) = cv::Vec3f(pixel[0], pixel[1], pixel[2]);
            }
        }
        out_img.convertTo(m_channel, CV_8UC3);
    }
    void ImageProc::channels(){
        // 卷积核:三通道无符号字节整数
        cv::Mat kernel = (
            cv::Mat_<cv::Vec3b>(3,3) << 
                cv::Vec3b(1, 1, 1), cv::Vec3b( 1,  1,  1), cv::Vec3b(1, 1, 1),
                cv::Vec3b(1, 1, 1), cv::Vec3b(-8, -8, -8), cv::Vec3b(1, 1, 1),
                cv::Vec3b(1, 1, 1), cv::Vec3b( 1,  1,  1), cv::Vec3b(1, 1, 1)
        );
        // 获取图像行列
        int rows = m_src.rows;
        int cols = m_src.cols;
        // 构造一个Padding图像,Padding补0
        cv::Mat p_img(rows + 2 , cols + 2, m_src.type(), cv::Scalar_<u_char>(0, 0, 0));
        // 拷贝图像到padding图像
        m_src.copyTo(p_img(cv::Range(1, rows + 1), cv::Range(1, cols + 1))); 
        // 定义输出(与源图像同大小与类型)
        cv::Mat m_out(rows, cols, m_src.type());
        // 5. 循环计算没有输出像素
        for(int y = 0; y< rows; y++){
            for(int x = 0; x< cols; x++){
                // 获取padding图像的子图
                cv::Mat sub = p_img(cv::Range(y, y + 3), cv::Range(x, x + 3)); // 子图大小为3
                // 计算矩阵hadamard乘积
                cv::Mat prod = sub.mul(kernel); // 子图与kernel矩阵的乘积 
                // 计算矩阵的和
                cv::Scalar pixel = cv::sum(prod);
                // 赋值结果到输出图像
                m_out.at<cv::Vec3b>(y, x) = cv::Vec3b(pixel[0], pixel[1], pixel[2]);
            }
        }
        m_out.copyTo(m_channels);
    }
    
    
    

    filter2D滤波效果

    通用卷积计算效果

    Laplacian滤波效果

    OpenCV的拉普拉斯滤波效果

    小数计算滤波效果

    使用cv::Mat实现的Laplace滤波

    无符号字节整数计算滤波效果

    数据溢出的滤波效果

    思考

    1. 如果对图像2次一阶差分计算,产生的效果理论上应该是是一样的,但实际因为近似定义的模板,运行结果应该有差异。

    2. 锐化图像处理,其中二阶差分的系数在一般图像是否有一个在某个范围内的经验值?是的锐化图像效果最好?异或使用神经网络训练出一个服务于分类的最优值?

    3. 由于laplace算法本身的差分理论,所以对图像的噪音像素会产生放大效果。在图像预处理的时候,可能会使用高斯模糊先平滑处理,去掉部分噪音后在使用laplace提取边缘与轮廓。

    4. 因为颜色的通道尽管不一样,理论上三个通道的像素值的变化激烈度是一样的,所以可以肯定滴说,laplace不管采用什么方法计算,最后肯定是灰度图(三个通道的值一样)。

      • 如果是彩色的,则只有两个原因:计算错误或者数据溢出产生的误差。
    5. 从运行效果来说,OpenCv的Laplacian滤波函数,使用的是4个方向的滤波核。

      • 两个方向的滤波核的效果没有那个差异大。
      • \begin{bmatrix} {1}&{1}&{1}\\{1}&{\color{red}{-8}}&{1}\\{1}&{1}&{1}\\\end{bmatrix}

    附录:代码结构

    1. 界面类头文件
        #ifndef DLG_OPENCV_H
        #define DLG_OPENCV_H
        #include <opencv2/opencv.hpp>
        #include "ui_output.h"
        #include "imageprocess.h"
    
        class DlgLaplace: public QDialog{
        Q_OBJECT    // 使用signal与slot,记得添加这个宏(如果继承QObject类型,则可选)。
        private:
            Ui::ui_output  *dlg;
            ImageProc  *proc;
            void showImage();
            void showImageOut(cv::Mat im);
        public:
            DlgLaplace(QWidget *parent = 0);     // 编程套路
            ~DlgLaplace();
        protected:
            virtual void showEvent(QShowEvent *event);
        private slots:
            void channel();
            void channels();
            void filter2D();
            void laplace();
        };
        #endif
    
    
    1. 计算类头文件
    #ifndef  IMAGE_PROCESS_H
    #define IMAGE_PROCESS_H
    #include <opencv2/opencv.hpp>
    class ImageProc{
    private:
        cv::String *filename_image;     // 文件名:std::string
    
    public:
        cv::Mat m_src;                  // 原始图像的数据结构
        cv::Mat m_filter2d;            // 输出图像:cv::filter2D
        cv::Mat m_laplace;            // 输出图像:cv::Laplacian 
        cv::Mat m_channel;           // 输出图像:小数运算通道处理;
        cv::Mat m_channels;          // 输出图像:字节运算通道处理;
    public:
        ImageProc();
        ImageProc(const char *filename);
        ~ImageProc();
        void channel();
        void channels();
        void filter2D();
        void laplace();
    };
    #endif // ! IMAGE_PROCESS_H
    

    相关文章

      网友评论

          本文标题:CV02-02:Laplace滤波

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