CV02-02:Laplace滤波

  机器视觉中图像滤波处理应该是很基础,很重要的,本主题梳理下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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容

  • 文章目录 1、给定0-1矩阵,求连通域。 二值图像分析最重要的方法就是连通区域标记,它是所有二值图像分析的基础,它...
    王永迪阅读 6,069评论 0 3
  • http://blog.csdn.net/x454045816/article/details/52153250 ...
    G风阅读 7,019评论 0 1
  • 1、阈值分割 1.1 简介 图像阈值化分割是一种传统的最常用的图像分割方法,因其实现简单、计算量小、性能较稳定而成...
    Lornatang阅读 9,448评论 0 5
  • 时间与时光,一个代表了现在与未来,一个代表了过去与岁月,时间里充满了激情,火热,未知,刺激着人们不断向前,可是时光...
    温暖不是光阅读 233评论 0 1
  • 距离通关《战神》已有一段时日,但它的热度却似乎还没有褪去。我依然几乎每天都会看到关于战神的各种消息,也有可能是因为...
    真岛吾朗阅读 405评论 0 0