【OpenCV学习笔记】之卷积及卷积算子(convolution)
一、简单理解卷积的概念
1.1卷积的定义:
定义任意两个信号的卷积为
这里的*代表卷积的运算符号, 是中间变量,两个信号的卷积仍是以t为变量的信号。
类似地,离散的信号的卷积和:
1.2 卷积的计算步骤:
(1)将上面的 、 中的自变量t换为 ,得到 、 ;
(2)将函数 以纵坐标为轴折叠,得到折叠信号 ;
(3)将折叠信号 沿 轴平移t,t为变量,从而得到平移信号 ,t<0时左移,t>0时右移;
(4)将 与 相乘,得到相乘信号 的非零区间,从而确定卷积积分的上下限,随着t的变化,积分限也会变化;
(5)计算函数 的曲线下的面积。
1.3 卷积的应用:
图像处理:
用一个模板和一幅图像进行卷积,对于图像上的一个点,让模板的原点和该点重合,然后模板上的点和图像上对应的点相乘,然后各点的积相加,就得到了该点的卷积值。对图像上的每个点都这样处理。由于大多数模板都是对称的,所以模板不旋转。卷积是一种积分运算,用来求两个曲线重叠区域面积。可以看作加权求和,可以用来消除噪声、特征增强。
把一个点的像素值用它周围的点的像素值的加权平均代替。
卷积是一种线性运算,图像处理中常见的mask运算都是卷积,广泛应用于图像滤波。
卷积在数据处理中用来平滑,卷积有平滑效应和展宽效应.
信号处理:
1)卷积实质上是对信号进行滤波;
2)卷积就是用冲激函数表示激励函数,然后根据冲击响应求解系统的零状态响应。卷积是求和(积分)。对于线性时不变的系统,输入可以分解成很多强度不同的冲激的和的形式(对于时域就是积分),那么输出也就是这些冲激分别作用到系统产生的响应的和(或者积分)。所以卷积的物理意义就是表达了时域中输入,系统冲激响应,以及输出之间的关系。
二、理解图像处理中卷积操作的实际过程
卷积:是图像处理中一个操作,是kernel在图像的每个像素上的操作。
Kernel:本质上一个固定大小的矩阵数组,其中心点称为锚点(anchor point)
那么,卷积具体怎么工作的呢?
假设想知道图像中某个特定位置的结果值。卷积的值以下列方式计算:
1) 将核的锚点(中心点)放在要计算像素上,卷积核剩余的部分对应在图像相应的像素上。
2) 用卷积核中的系数和图像中相应的像素值相乘,并求和。
3) 将最终结果赋值给锚点对应的像素。
4) 通过将核在整个图像滑动,重复以上计算过程直到处理完所有的像素。表达式如下:
利用卷积进行滤波处理过程,如下图所示,最左边是模板,中间的绿点是锚点;
但是,图像卷积的时候边界像素,不能被卷积操作,进而产生边缘问题。原因在于边界像素没有完全跟kernel重叠,所以当3x3滤波时候有1个像素的边缘没有被处理,5x5滤波的时候有2个像素的边缘没有被处理。
在卷积开始之前增加边缘像素,填充的像素值为0或者RGB黑色,比如3x3在四周各填充1个像素的边缘,这样就确保图像的边缘被处理,在卷积处理之后再去掉这些边缘。openCV中默认的处理方法是: BORDER_DEFAULT,此外
常用的还有如下几种:
- BORDER_CONSTANT – 填充边缘用指定像素值
- BORDER_REPLICATE – 填充边缘像素用已知的边缘像素值。
- BORDER_WRAP – 用另外一边的像素来补偿填充
- 处理边缘的方法
-
C++ void copyMakeBorder(
-
Mat src, // 输入图像
-
Mat dst, // 添加边缘图像
-
int top, // 边缘长度,一般上下左右都取相同值,
-
int bottom,
-
int left,
-
int right,
-
int borderType // 边缘类型
-
Scalar value )
示例程序:
-
//图像边缘处理(问题由来,图像卷积的时候边界像素,不能被卷积操作,原因在于边界像素没有完全跟kernel重叠。如3x3滤波时候有1个像素的边缘没有被处理)
-
#include "stdafx.h"
-
#include<opencv2/opencv.hpp>
-
#include<iostream>
-
using namespace cv;
-
using namespace std;
-
int main(int argc, char*argv)
-
{
-
Mat src, dst, dst1, dst2;
-
src = imread("C:/Users/59235/Desktop/imag/national customs3.jpg");
-
if (!src.data)
-
{
-
printf("could not load image...\n");
-
return -1;
-
}
-
namedWindow("input", CV_WINDOW_AUTOSIZE);
-
namedWindow("border process", CV_WINDOW_AUTOSIZE);
-
imshow("input", src);
-
int c = 0;
-
int top = (0.05*src.rows);
-
int bottom = (0.05*src.rows);
-
int left = 0.05*src.cols;
-
int right = 0.05*src.cols;
-
int bordertype = BORDER_DEFAULT;//openCV中默认的处理方法
-
//opencv里面卷积操作实际过程:在卷积开始之前增加边缘像素,填充的像素值为0或者RGB黑色,比如3x3在四周各填充1个像素的边缘,这样就确保图像的边缘被处理,在卷积处理之后再去掉这些边缘。(填充边缘opencv里面有四种)
-
RNG rng(12345);
-
while (true)
-
{
-
c = waitKey(500);
-
//ESC键
-
if ((char)c == 27)
-
{
-
break;
-
}
-
else if ((char)c == 'r')
-
{
-
bordertype = BORDER_REPLICATE;//填充边缘像素用已知的边缘像素值。
-
}
-
else if ((char)c == 'w')
-
{
-
bordertype = BORDER_WRAP;//用另外一边的像素来补偿填充
-
}
-
else if ((char)c == 'c')
-
{
-
bordertype = BORDER_CONSTANT;//填充边缘用指定像素值
-
}
-
Scalar color = (rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
-
//给图像添加边缘
-
copyMakeBorder(src, dst, top, bottom, left, right, bordertype, color);
-
imshow("border process", dst);
-
}
-
waitKey(0);
-
return 0;
-
}
三、自定义滤波(customize filter)和卷积算子(convolution operator)
自定义滤波主要是滤波核的建立,以及会用到filter2D这个API:
Opencv里面的API介绍:
- 卷积模板设计:
-
Mat kernel = (Mat_<char>(3,3)<<0,-1,0,-1,5,-1,0,-1,0)//自定义模板
-
Mat kernel_x = (Mat_<int>(3, 3) << -1, 0, 1, -2, 0, 2, -1, 0, 1); //自定义模板
- 自定义卷积模糊
-
C++ void filter2D(
-
Mat src, //输入图像
-
Mat dst, // 模糊图像
-
int depth, // 图像深度32/8
-
Mat kernel, // 卷积核/模板
-
Point anchor, // 锚点位置
-
double delta // 计算出来的像素+delta)其中 kernel是可以自定义的卷积核
几种常见的卷积算子
(Robert 算子)
(Sobel 算子)
(Laplance 算子)
3.1 sobel算子
Sobel 算子是一个离散微分算子 (discrete differentiation operator)。 它结合了高斯平滑和微分求导,用来计算图像灰度函数的近似梯度。用于图像处理和计算机视觉,特别是在边缘检测算法中,它可以创建强调边缘的图像。它以斯坦福人工智能实验室(SAIL)的同事Irwin Sobel和Gary Feldman命名。 Sobel和Feldman在1968年SAIL的演讲中提出了“各向同性3x3图像梯度算子”的概念。从技术上讲,它是一个离散微分算子,计算图像强度函数梯度的近似值。在图像中的每一点,Sobel-Feldman算子的结果是相应的梯度向量或该向量的范数。 Sobel-Feldman算子基于将图像与水平和垂直方向上的小的可分离的整数值滤波器进行卷积,因此在计算方面相对简便。另一方面,它产生的梯度近似相对粗糙,特别是对于图像中的高频变化。
图像边缘, 边缘就是灰度值变化剧烈的地方。表示这一改变的一个方法是使用导数。是像素值发生跃迁的地方,是图像的显著特征之一,在图像特征提取、对象检测、模式识别等方面都有重要的作用。梯度值的大变预示着图像中内容的显著变化。用更加形象的图像来解释,假设我们有一张一维图形。下图2中灰度值的”跃升”表示边缘的存在,图3中使用一阶微分求导我们可以更加清晰的看到边缘”跃升”的存在。
图1、 Lena 图2、像素一维图形
figure 1、lena Figure 2、Pixel one-dimensional graphics
图 3、一阶导数
Figure 3、first derivative
具体是采用卷积的计算方法实现的。假设被作用的图像为 ,在两个方向上求导:
水平变化求导:将 与一个奇数大小的内核 进行卷积。比如,当内核大小为3时, 的计算结果为图4a:
垂直变化求导:将 I 与一个奇数大小的内核 进行卷积。比如,当内核大小为3时, 的计算结果为图4b:
在图像的每一点,结合以上两个结果求出近似 梯度 ,如图4c:
也可简化为:G=GX+|GY|
- 求取导数的近似值,kernel=3时不是很准确,OpenCV使用改进版本Scharr函数,算子如下:
Opencv里面API介绍:
-
C++ void Sobel (
-
InputArray Src // 输入图像
-
OutputArray dst// 输出图像,大小与输入图像一致
-
int depth // 输出图像深度.
-
Int dx // X方向,几阶导数
-
int dy // Y方向,几阶导数
-
int ksize, SOBEL算子kernel大小,必须是1、3、5、7、
-
double scale = 1
-
double delta = 0
-
int borderType = BORDER_DEFAULT)
输入输出图像的深度必须和下表对应:
-
//改进版本的sobel算子:
-
C++ void Scharr (
-
InputArray Src // 输入图像
-
OutputArray dst// 输出图像,大小与输入图像一致
-
int depth // 输出图像深度.
-
Int dx. // X方向,几阶导数
-
int dy // Y方向,几阶导数.
-
double scale = 1
-
double delta = 0
-
int borderType = BORDER_DEFAULT)
3.2 Laplace算子
拉普拉斯算子是最简单的各向同性微分算子,具有旋转不变性。一个二维图像函数 的拉普拉斯变换是各向同性的二阶导数,定义为:
其中,
为了更适合于数字图像处理,将该方程表示为离散形式:
另外,拉普拉斯算子还可以表示成模板的形式,如图5-9所示。图5-9(a)表示离散拉普拉斯算子的模板,图5-9(b)表示其扩展模板,图5-9(c)则分别表示其他两种拉普拉斯的实现模板。从模板形式容易看出,如果在图像中一个较暗的区域中出现了一个亮点,那么用拉普拉斯运算就会使这个亮点变得更亮。因为图像中的边缘就是那些灰度发生跳变的区域,所以拉普拉斯锐化模板在边缘检测中很有用。一般增强技术对于陡峭的边缘和缓慢变化的边缘很难确定其边缘线的位置。但此算子却可用二次微分正峰和负峰之间的过零点来确定,对孤立点或端点更为敏感,因此特别适用于以突出图像中的孤立点、孤立线或线端点为目的的场合。同梯度算子一样,拉普拉斯算子也会增强图像中的噪声,有时用拉普拉斯算子进行边缘检测时,可将图像先进行平滑处理。
图像锐化处理的作用是使灰度反差增强,从而使模糊图像变得更加清晰。图像模糊的实质就是图像受到平均运算或积分运算,因此可以对图像进行逆运算,如微分运算能够突出图像细节,使图像变得更为清晰。由于拉普拉斯是一种微分算子,它的应用可增强图像中灰度突变的区域,减弱灰度的缓慢变化区域。因此,锐化处理可选择拉普拉斯算子对原图像进行处理,产生描述灰度突变的图像,再将拉普拉斯图像与原始图像叠加而产生锐化图像。拉普拉斯锐化的基本方法可以由下式表示:
这种简单的锐化方法既可以产生拉普拉斯锐化处理的效果,同时又能保留背景信息,将原始图像叠加到拉普拉斯变换的处理结果中去,可以使图像中的各灰度值得到保留,使灰度突变处的对比度得到增强,最终结果是在保留图像背景的前提下,突现出图像中小的细节信息。
Opencv里面API的介绍:
处理的步骤:
- 高斯模糊 – 去噪声GaussianBlur()
- 转换为灰度图像cvtColor()
- 拉普拉斯 – 二阶导数计算Laplacian()
- 取绝对值convertScaleAbs()
- 显示结果
-
C++ void Laplacian (
-
InputArray src,
-
OutputArray dst,
-
int depth, //深度CV_16S
-
int kisze, // 3
-
double scale = 1,
-
double delta =0.0,
-
int borderType = 4)