常用图像卷积核类型小结(下)

1,170 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第29天,点击查看活动详情

接上文

二阶微分算子

简介
上一小节介绍的Prewitt算子和Sobel算子都是近似对图像进行一阶导数的计算,只能提取出某个具体方向的边缘。由微积分的知识可知,一个函数的二阶导数为0时,代表此处的一阶导数取得极值,对应地也就表明原函数在此处的变化最大。比如著名的Sigmoid函数及其一阶导数、二阶导数的图像如下:

因此往往还可以根据图像的二阶导数过零点的位置,来预测图像中变化最剧烈的地方,也许对应物体的边缘。与一阶微分算子不同,这些二阶微分算子对边缘的计算具有旋转不变性,也就是可以检测出各个方向上的边缘。

Laplace算子

Laplace算子可以近似计算出图像的二阶导数,具有旋转不变性,也就是可以检测出各个方向的边缘。

对于数字图像,拉普拉斯算子可以简化为:21acd7d0c2ec7548d33bccdcff68d981.png

也可以按卷积形式表示:
b6bb83242ae7789f842eaa5f02b65703.png
其中K=1,I=1时H(r,s)取下式,四方面模板:

通过模板可以发现,当邻域内像素灰度相同时,模板的卷积运算结果为0;

当中心像素灰度高于邻域内其他像素的平均灰度时,模板的卷积运算结果为正数;

当中心像素的灰度低于邻域内其他像素的平均灰度时,模板的卷积的负数。

对卷积运算的结果用适当的衰弱因子处理并加在原中心像素上,就可以实现图像的锐化处理。

#coding=utf-8    
import cv2    
import numpy as np      
    
img = cv2.imread("test.jpg", 0)    
gray_lap = cv2.Laplacian(img,cv2.CV_16S,ksize = 3)    
dst = cv2.convertScaleAbs(gray_lap)    
    
cv2.imshow('laplacian',dst)    
cv2.waitKey(0)    
cv2.destroyAllWindows()  

对比:

锐化卷积核计算的是中心像素减去周围像素的差值(中心权重为正,周边权重为负);而Laplace算子则是周围像素之和减去中心像素的差值(中心权重为负,周边权重为正)。

LoG算子

Laplace算子对噪声依然很敏感。因此常常先使用高斯滤波器对图像进行平滑操作,再使用Laplace算子计算二阶微分。二者结合称为LoG算子(Laplacian of Gaussian),该算子可以更加稳定地计算图像的二阶微分。

基本理论

高斯卷积函数定义为:

而原始图像与高斯卷积定义为:

因为:

所以Laplacian of Gaussian(LOG)8579033794368a8de114e1b6e8ec8520.png​编辑可以通过先对高斯函数进行偏导操作,然后进行卷积求解。公式表示为:

因此,我们可以LOG核函数定义为:

9b73ad071499ec0e29d312a8ca606937.bmp

import numpy as np
import  cv2
from matplotlib import pyplot as plt

#定义掩膜
m1 = np.array([[0,0,-1,0,0],[0,-1,-2,-1,0],[-1,-2,16,-2,-1],[0,-1,-2,-1,0],[0,0,-1,0,0]]) #LoG算子模板

img = cv2.imread("lena_1.tiff",0)

#边缘扩充

image = cv2.copyMakeBorder(img, 2, 2, 2, 2, borderType=cv2.BORDER_REPLICATE)
# image = cv2.GaussianBlur(img,(3,3),4)
rows = image.shape[0]
cols = image.shape[1]
temp = 0
image1 = np.zeros(image.shape)

for i in range(2,rows-2):
    for j in range(2,cols-2):
        temp = np.abs(
            (np.dot(np.array([1, 1, 1, 1, 1]), (m1 * image[i - 2:i + 3, j - 2:j + 3])))
                .dot(np.array([[1], [1], [1], [1], [1]])))

        image1[i,j] = int(temp)

        if image1[i, j] > 255:
            image1[i, j] = 255
        else:
            image1[i, j] = 0


cv2.imshow("LoG",image1)

cv2.waitKey(0)

DoG算子

LoG算子的计算量较大,因此有数学家发明了DoG(Difference of Gaussians)算子来近似LoG算子。DoG算子翻译为高斯差分算子,从名称上可以看出,就是使用两个标准差不同的高斯滤波器对图像进行滤波操作,再将滤波后的两个结果相减,最后的结果可以近似LoG算子。其中涉及到的数学理论较为复杂,在此暂不讨论。

是灰度图像增强和角点检测( 也叫特征点提取)的一种方法

基本理论

首先,高斯函数表示定义为:

其次,两幅图像的高斯滤波表示为:

                        ​​​​​​​        69c2a5e3db4ff93342a5be810c59e966.png​编辑

最后,将上面滤波得到的两幅图像g1和g2相减得到:

即:可以DOG表示为:

7369786d0ff23371fee09ab26f963db4.png

高斯模糊代码

import cv2
import numpy as np
img = cv2.imread('D://fangzi.jpg')
cv2.imshow('img',img)
cv2.resizeWindow('img',640,480)
#img_ = cv2.GaussianBlur(img,ksize=(9,9),sigmaX=0,sigmaY=0)
img_ = cv2.GaussianBlur(img,(9,9),2)
cv2.imshow('img_',img_)
cv2.resizeWindow('img_',640,480)
cv2.waitKey()

效果:

4fb8d4d9e587d56ec6d33d39c44acbc9.png

DOG代码

左边是原图和三种不同σ的高斯模糊后的图。右边是对高斯滤波后的图片(相邻状态下)依次进行两两相减可得到右边的三个高斯函数的差分图(简称DOG)。

红色标记为当前像素点,黄色对应的像素点表示当前像素点邻接的点,共26(上图中27个黄点减去一个红点)个,如果该点(红点)是所有邻接像素点(黄点)的最大值或最小值,则红色标记对应的点为特征点。

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace std;
using namespace cv;

int main()
{
    Mat ori_img = imread("D://lena.png");
    Mat gray_img;
    cvtColor(ori_img, gray_img, CV_RGB2GRAY);
    imshow("gray", gray_img);
    gray_img.convertTo(gray_img, CV_32F);// float -像素是在0-1.0之间的任意值,这对于一些数据集的计算很有用,但是它必须通过将每个像素乘以255来转换成8位来保存或显示。

    Mat gauss1, gauss2;
    GaussianBlur(gray_img, gauss1, Size(5, 5), 0.3, 0.3);
    GaussianBlur(gray_img, gauss2, Size(5, 5), 0.4, 0.4);

    Mat DoG1, DoG2, DoG3;
    DoG1 = gauss1 - gauss2;
    imshow("DOG1", DoG1);
    GaussianBlur(gray_img, gauss1, Size(5, 5), 0.6, 0.6);
    GaussianBlur(gray_img, gauss2, Size(5, 5), 0.7, 0.7);
    DoG2 = gauss1 - gauss2;
    imshow("DOG2", DoG2);
    GaussianBlur(gray_img, gauss1, Size(5, 5), 0.7, 0.7);
    GaussianBlur(gray_img, gauss2, Size(5, 5), 0.8, 0.8);
    DoG3 = gauss1 - gauss2;
    imshow("DOG3", DoG3);
    for (int j = 1; j < gray_img.rows - 1; j++)
    {
        for (int i = 1; i < gray_img.cols - 1; i++)
        {
            if (DoG2.at<float>(j, i) < DoG2.at<float>(j - 1, i - 1) && DoG2.at<float>(j, i) < DoG2.at<float>(j - 1, i) &&
                DoG2.at<float>(j, i) < DoG2.at<float>(j - 1, i + 1) && DoG2.at<float>(j, i) < DoG2.at<float>(j, i - 1) && DoG2.at<float>(j, i) < DoG2.at<float>(j, i + 1) &&
                DoG2.at<float>(j, i) < DoG2.at<float>(j + 1, i - 1) && DoG2.at<float>(j, i) < DoG2.at<float>(j + 1, i) && DoG2.at<float>(j, i) < DoG2.at<float>(j + 1, i + 1)
                && DoG2.at<float>(j, i) < DoG1.at<float>(j, i) && DoG2.at<float>(j, i) < DoG1.at<float>(j - 1, i - 1) && DoG2.at<float>(j, i) < DoG1.at<float>(j - 1, i) &&
                DoG2.at<float>(j, i) < DoG1.at<float>(j - 1, i + 1) && DoG2.at<float>(j, i) < DoG1.at<float>(j, i - 1) && DoG2.at<float>(j, i) < DoG1.at<float>(j, i + 1) &&
                DoG2.at<float>(j, i) < DoG1.at<float>(j + 1, i - 1) && DoG2.at<float>(j, i) < DoG1.at<float>(j + 1, i) && DoG2.at<float>(j, i) < DoG1.at<float>(j + 1, i + 1)
                && DoG2.at<float>(j, i) < DoG3.at<float>(j, i) && DoG2.at<float>(j, i) < DoG3.at<float>(j - 1, i - 1) && DoG2.at<float>(j, i) < DoG3.at<float>(j - 1, i) &&
                DoG2.at<float>(j, i) < DoG3.at<float>(j - 1, i + 1) && DoG2.at<float>(j, i) < DoG3.at<float>(j, i - 1) && DoG2.at<float>(j, i) < DoG3.at<float>(j, i + 1) &&
                DoG2.at<float>(j, i) < DoG3.at<float>(j + 1, i - 1) && DoG2.at<float>(j, i) < DoG3.at<float>(j + 1, i) && DoG2.at<float>(j, i) < DoG3.at<float>(j + 1, i + 1))
            {
                //cout << DoG2.at<float>(j, i);
                if (DoG2.at<float>(j, i) < -3)
                {
                    circle(ori_img, Point(i, j), 3, CV_RGB(0, 0, 255));
                }
            }
            else
                if (DoG2.at<float>(j, i) > DoG2.at<float>(j - 1, i - 1) && DoG2.at<float>(j, i) > DoG2.at<float>(j - 1, i) &&
                    DoG2.at<float>(j, i) > DoG2.at<float>(j - 1, i + 1) && DoG2.at<float>(j, i) > DoG2.at<float>(j, i - 1) && DoG2.at<float>(j, i) > DoG2.at<float>(j, i + 1) &&
                    DoG2.at<float>(j, i) > DoG2.at<float>(j + 1, i - 1) && DoG2.at<float>(j, i) > DoG2.at<float>(j + 1, i) && DoG2.at<float>(j, i) > DoG2.at<float>(j + 1, i + 1)
                    && DoG2.at<float>(j, i) > DoG1.at<float>(j, i) && DoG2.at<float>(j, i) > DoG1.at<float>(j - 1, i - 1) && DoG2.at<float>(j, i) > DoG1.at<float>(j - 1, i) &&
                    DoG2.at<float>(j, i) > DoG1.at<float>(j - 1, i + 1) && DoG2.at<float>(j, i) > DoG1.at<float>(j, i - 1) && DoG2.at<float>(j, i) > DoG1.at<float>(j, i + 1) &&
                    DoG2.at<float>(j, i) > DoG1.at<float>(j + 1, i - 1) && DoG2.at<float>(j, i) > DoG1.at<float>(j + 1, i) && DoG2.at<float>(j, i) > DoG1.at<float>(j + 1, i + 1)
                    && DoG2.at<float>(j, i) > DoG3.at<float>(j, i) && DoG2.at<float>(j, i) > DoG3.at<float>(j - 1, i - 1) && DoG2.at<float>(j, i) > DoG3.at<float>(j - 1, i) &&
                    DoG2.at<float>(j, i) > DoG3.at<float>(j - 1, i + 1) && DoG2.at<float>(j, i) > DoG3.at<float>(j, i - 1) && DoG2.at<float>(j, i) > DoG3.at<float>(j, i + 1) &&
                    DoG2.at<float>(j, i) > DoG3.at<float>(j + 1, i - 1) && DoG2.at<float>(j, i) > DoG3.at<float>(j + 1, i) && DoG2.at<float>(j, i) > DoG3.at<float>(j + 1, i + 1))
                {
                    if (DoG2.at<float>(j, i) > 3)
                    {
                        circle(ori_img, Point(i, j), 3, CV_RGB(255, 0, 0));
                    }
                }
        }
    }
    imshow("result", ori_img);
    waitKey(0);

效果:

输入的灰度图:9ab99c706afb8854c0ecd1f30e4d1e7b.png

1:fa51cbb8a6c36853d79488e1a5e26f1a.png

2:e55cfe753caf1ef33a0d8df51b61b23b.png

3:bc135a76127f028931175f13353186d6.png

result: