OpenCV的滤波与卷积

目录

预备知识

滤波、核和卷积

边界外推和边界处理

阈值化操作

Otsu算法

自适应阈值

平滑

简单模糊和方框型滤波器

中值滤波器

高斯滤波器

双边滤波器

导数和梯度

索贝尔导数

Scharr滤波器

拉普拉斯变换

图像形态学

膨胀和腐蚀

通用形态学函数

开操作和闭操作

形态学梯度

顶帽和黑帽

自定义核

用任意线性滤波器做卷积

用cv::filter2D()进行卷积

通过cv::sepFilter2D使用可分核

生成卷积核


预备知识

滤波、核和卷积

滤波器指的是一种由一幅图像 I(x,y)根据像素点x,y附近的区域计算得到一幅新图像 I'(x,y)的算法。其中,模板规定了滤波器的形状以及这个区域内像素的值的组成规律,也称“滤波器”或“核”。在下面的介绍中多采用的是线性核,即 I'(x,y)的像素的值由 I(x,y)及其周围的像素的值的加权相加得来的。可由以下方程表示:

I'(x, y)=\sum _{i, \: j\in kernal}k_{i,\: j}\cdot I(x+i,\: y+j)

OpenCV的滤波与卷积_第1张图片 OpenCV的滤波与卷积_第2张图片 \begin{bmatrix} -1 & 0 &1 \\ -2 &\mathbf{0} & 2\\ -1& 0& 1 \end{bmatrix} OpenCV的滤波与卷积_第3张图片
(A)5×5盒状核 (B)规范化的5×5盒状核 (C)3×3的Sobel核 (D)5×5规范化高斯核

注:“锚点”均用粗体表示

边界外推和边界处理

自定义边框

在处理图像时,只要告诉调用的函数添加虚拟像素的规则,库函数就会自动创建虚拟像素。cv::copyMakeBorder()就是一个为图像创建边框的函数。

cv::copyMakeBorder(InputArray src, InputArray dst, int top, int bottom, int left, int right, int borderType, const cv::Scalar& value=cv::Scalar())

作用:通过指定两幅图像,同时指明填充方法,该函数就会将第一幅图填补后的结果保存在第二幅图像中。其中,src是原图像,dst是填充后的图像,top、bottom、left、right分别是四个方向上的尺寸,borderType是像素填充的方式,value是常量填充时的值。

borderType的取值及效果
取值 效果
cv::BORDER_CONSTANT 为每个边框像素赋予一个相同的值。
cv::BORDER_WRAP 类似于平铺扩充
cv::BORDER_REPLICATE 复制边缘的像素扩充
cv::BORDER_REFLECT 通过镜像复制扩充
cv::BORDER_REFLECT_101 通过镜像复制扩充,边界像素除外
cv::BORDER_DEFAULT cv::BORDER_REFLECT_101

OpenCV的滤波与卷积_第4张图片

自定义外推

 int cv::borderInterpolate(int p, int len, int borderType)

作用:计算一个维度上的外推,p为原图上一个坐标,len是p指维度上的大小,borderType是边界类型。

例子:混合的边界条件下计算一个特定像素的值,在一维中使用BORDER_REFLECT_101,在二维中使用BORDER_WRAP:

float val = img.at(
    cv::borderInterpolate(100, img.rows, cv::BORDER_REFLECT_101),
    cv::borderInterpolate(-5, img.cols, cv::BORDER_WRAP)
)

阈值化操作

阈值化操作的原理是对于数组中每个值,根据其高于或低于某个阈值做出相对应的处理,OpenCV中提供了实现这种功能的方法cv::threshold()。

double cv::threshold(InputArray src, cv::OutputArray dst, double thresh, double maxValue, int thresholdType)

thresholdType的可选项及其操作
阈值类型 操作
cv::THRESH_BINARY DST_i=(SRC_i > thresh)\: ?\: MAXVALUE:0
cv::THRESH_BINARY_INV DST_i=(SRC_i > thresh)\: ?\: 0:MAXVALUE
cv::THRESH_TRUNC DST_i=(SRC_i > thresh)\: ?\: THRESH:SRC_i
cv::THRESH_TOZERO DST_i=(SRC_i > thresh)\: ?\: SRC_i:0
cv::THRESH_TOZERO_INV DST_i=(SRC_i > thresh)\: ?\: 0:SRC_i
OpenCV的滤波与卷积_第5张图片 不同阈值类型对应的结果

 

例一:将一幅图像的三个通道相加并将像素值限制在100以内

#include "stdafx.h"
#include 
#include 

using namespace std;

void sum_rgb(const cv::Mat& src, cv::Mat& dst) {
	// 分通道
	vector planes;
	cv::split(src, planes);

	cv::Mat b = planes[0];
	cv::Mat g = planes[1];
	cv::Mat r = planes[2];
	cv::Mat s;

	// 加权融合,防止越界
	cv::addWeighted(r, 1. / 3, g, 1. / 3, 0.0, s);
	cv::addWeighted(s, 1., r, 1. / 3, 0.0, s);

	// 阈值化操作
	cv::threshold(s, dst, 100, 100, cv::THRESH_TRUNC);
}

void help() {
	cout << "Call: ./ch10_ex10_1 faceScene.jpg" << endl;
	cout << "Show use of alpha blending (addWeighted) and threshold" << endl;
}

int main()
{
	help();

	cv::Mat src = cv::imread("D:\\personal-data\\wallpapers\\test.png");
	cv::Mat dst;
	if (src.empty())
	{
		cout << "can not load the image" << endl;
		return -1;
	}

	sum_rgb(src, dst);
	cv::imshow("img", dst);
	cv::waitKey(0);
	cv::destroyAllWindows();
    return 0;
}

运行结果: 

OpenCV的滤波与卷积_第6张图片

例二:对于浮点型图像进行三个通道相加并将像素值限制在100以内

#include "stdafx.h"
#include 
#include 

using namespace std;

void sum_rgb(const cv::Mat& src, cv::Mat& dst) {
	// 分通道
	vector planes;
	cv::split(src, planes);

	cv::Mat b = planes[0];
	cv::Mat g = planes[1];
	cv::Mat r = planes[2];

	// 全0初始化s矩阵
	cv::Mat s = cv::Mat::zeros(b.size(), CV_32F);
	// accumulate可将8位整型的图像累加到一幅浮点型的图像中
	cv::accumulate(b, s);
	cv::accumulate(g, s);
	cv::accumulate(r, s);

	// 阈值化操作
	cv::threshold(s, dst, 100, 100, cv::THRESH_TRUNC);
	s.convertTo(dst, b.type());
}

void help() {
	cout << "Call: ./ch10_ex10_1 faceScene.jpg" << endl;
	cout << "Show use of alpha blending (addWeighted) and threshold" << endl;
}

int main()
{
	help();

	cv::Mat src = cv::imread("D:\\personal-data\\wallpapers\\test.png");
	cv::Mat dst;
	if (src.empty())
	{
		cout << "can not load the image" << endl;
		return -1;
	}

	sum_rgb(src, dst);
	cv::imshow("img", dst);
	cv::waitKey(0);
	cv::destroyAllWindows();
	return 0;
}

运行结果:

OpenCV的滤波与卷积_第7张图片

Otsu算法

函数cv::threshold()也可以自动决定最优的阈值,只需将参数thresh传递值cv::THRESH_OTSU即可。

简而言之,Otsu算法就是遍历所有可能的阈值,然后对每个阈值结果的两类像素(低于阈值和高于阈值两类像素)计算方差\sigma _i^2,然后计算\sigma _w^2的值,取其最小的阈值。

\sigma _w^2\equiv w_1(t)\cdot \sigma _1^2+w_2(t)\cdot \sigma _2^2

式中,w_1(t)w_2(t)是根据两类像素的数量计算而来的权重。由于要遍历所有可能的阈值,所以这并不是一个相对高效的过程。

自适应阈值

自适应阈值方法中阈值在整个过程中自动产生变化,这由OpenCV中的cv::adaptiveThreshold()实现。

void cv::adaptiveThreshold(InputArray src, OutputArray dst, double maxValue, int adaptiveMethod, int threshType, int blockSize, double C)

该方法是逐个像素地计算自适应阈值T(x, y),具体计算过程是:计算每个像素位置周围的blockSize×blockSize区域的加权平均值,然后减去常数C。求均值时所用权重和adaptiveMethod有关,若是cv::ADAPTIVE_THRESH_MEAN_C,则权重相等,若是cv::ADAPTIVE_THRESH_GAUSSIAN_C,则权重由高斯方差得到。

注:该方法只适应与单通道8位或浮点型图像

#include "stdafx.h"
#include 
#include 

using namespace std;

int main()
{
	
	// 设置参数
	double fixed_threshold = 15;
	int threshold_type = 1 ? cv::THRESH_BINARY : cv::THRESH_BINARY_INV;
	int adaptive_method = 1 ? cv::ADAPTIVE_THRESH_MEAN_C : cv::ADAPTIVE_THRESH_GAUSSIAN_C;
	int block_size = 71;
	double offset = 15;
	// 以灰度图形式加载图片
	cv::Mat Igray = cv::imread("D:\\personal-data\\wallpapers\\test.png", cv::IMREAD_GRAYSCALE);

	// 判断图像是否加载成功
	if (Igray.empty()) { 
		cout << "Can not load " << "D:\\personal-data\\wallpapers\\test.png" << endl; 
		return -1; 
	}

	// 声明输出矩阵
	cv::Mat It, Iat;

	// 阈值化操作
	cv::threshold(
		Igray,
		It,
		fixed_threshold,
		255,
		threshold_type);
	// 自适应阈值
	cv::adaptiveThreshold(
		Igray,
		Iat,
		255,
		adaptive_method,
		threshold_type,
		block_size,
		offset
	);

	// 展示结果图像
	cv::imshow("Raw", Igray);
	cv::imshow("Threshold", It);
	cv::imshow("Adaptive Threshold", Iat);
	cv::waitKey(0);
	cv::destroyAllWindows();
    return 0;
}

运行结果:

当threshold_type=1且adaptive_method=1

OpenCV的滤波与卷积_第8张图片

原图

OpenCV的滤波与卷积_第9张图片

阈值化操作

OpenCV的滤波与卷积_第10张图片

自适应阈值化

当threshold_type=0且adaptive_method=0

OpenCV的滤波与卷积_第11张图片

原图

OpenCV的滤波与卷积_第12张图片

阈值化操作

OpenCV的滤波与卷积_第13张图片

自适应阈值化

平滑

平滑也称“模糊”,是一种简单而又常用的图像处理操作。平滑图像的目的有很多,但通常都是为了减少噪声和伪影。在降低图像分辨率的时候,平滑也十分重要,可以防止图片出现锯齿状。

简单模糊和方框型滤波器

void cv::blur(InputArray src,OutputArray dst,Size ksize,Point anchor=Point(-1,-1),int borderType=BORDER_DEFAULT)

作用:实现简单的滤波,目标图像中的每个值都是原图像中相应位置一个窗口(核)中像素的平均值,窗口的尺寸由ksize声明;anchaor指定计算时核与源图像的对齐方式,默认情况下anchor为cv::Point(-1, -1),表示核相对于滤波器居中。

void cv::boxFilter(InputArray src, OutputArray dst, cv::Size ksize, cv::Point anchor=cv::Point(-1,-1), bool normalize=true;  int borderType=cv::BORDER_DEFAULT)

作用:方框滤波器是一种矩形的并且滤波器中所有值k_{i,\: j}全部相等的滤波器。通常,所有的k_{i,\: j}为1或者1/A,其中A是滤波器的面积。后一种滤波器称为“归一化方框型滤波器”,下面所示的是一个5×5的模糊滤波器,也称“归一化方框型滤波器”。

OpenCV的滤波与卷积_第14张图片

通过上述介绍,可以发现cv::boxFilter()是一种一般化的形式,而cv::blur()是一种特殊化的形式。但前者可以以非归一化形式调用,并且输出图像深度可以控制,但后者智能以归一化形式调用,且输出图像深度必须和原图像保持一致。

中值滤波器

中值滤波器(Median Filter)将每个像素替换为围绕这个像素的矩形领域内的中值或“中值”像素(相对于平均像素)。通过均值滤波器对噪声图像,尤其是有较大孤立的异常值非常敏感,少量具有较大偏差的点也会严重影响到均值滤波器。中值滤波器可以采用取其中间点的方式来消除异常值。

void cv::medianBlur(InputArray src, OutputArray dst, Size ksize)

高斯滤波器

关于高斯滤波器,在之前的文章中已做了详细介绍,可以参考OpenCV高斯滤波GaussianBlur

双边滤波器

双边滤波器是一种比较大的图像分析算子,也就是边缘保持平滑。高斯平滑的模糊过程是减缓像素在空间上的变化,因此与邻近的关系紧密,而随机噪声在像素间的变化幅度又会非常的大(即噪声不是空间相关的)。基于这种前提高斯平滑很好地减弱了噪声并且保留了小信号,但是却破坏了边缘信息,边缘也模糊了。

和高斯平滑类似,双边滤波对每个像素及其领域内的像素进行了加权平均。其权重由两部分组成,第一部分同高斯平滑,第二部分也是高斯权重,但是它不是基于空间距离而是色彩强度差计算而来的,在多通道(色彩)图像上强度差由各分量的加权累加代替。可将其当做高斯平滑,指示相似程度更高的像素权值更高,边缘更加明显,对比度更高。

cv::bilateralFilter(InputArray src, OutputArray dst, int d, double sigmaColor, double sigmaSpace, int borderType= cv::BORDER_DEFAULT)

参数:d是像素邻近的最大距离,处理视频时一般不大于5,非实时应用时可放大到9;sigmaColor是色彩空间滤波器的sigma值,该值越大,则色彩强度越大,不连续性越强;sigmaSpace是坐标空间滤波器的sigma值。

导数和梯度

卷积中最重要也是最基本的部分就是(近似)计算导数。

索贝尔导数

一般来说,用来表示微分的最常用的算子是索贝尔(Sobel)导数算子,可以实现任意阶导数和混合偏导数。

void sv::Sobel(InputArray src, OutputArray dst, int ddepth, int xorder, int yorder, cv::Size ksize=3, double scale=1, double delta=0, int borderType=cv::BORDER_DEFAULT)

参数:ddepth指明目标图像的深度或类型;xorder和yorder是求导顺序,其取值可为0,、1或2,其中0代表在该方向不求导,kszie是一个奇数,表示滤波器核的大小, 目前最大到31;阈值和偏移量在结果存入dst前进行调用,公式如下:

dst_i=scale\cdot \left \{ \sum _{i,\: j\in sobel\_kernel}k_{i,\: j}*I(x+i, y+j) \right \}+delta

Sobel算子的好处是可以将核定义为各种大小,并且可以快速迭代式地构造这些核。大的核可以更好地近似导数,因为可以消除噪声影响。其缺点是如果导数在空间上变化剧烈,核太大会使结果发生偏差,并且核比较小时准确度不高。

实际上,由于Sobel算子定义在离散空间上,所以它并不是真正的导数,而是一个多项式,即在x方向上进行Sobel运算表示的并不是二阶导数,而是对抛物线函数的局部拟合。

Scharr滤波器

为了将图像内的信息联系起来,可能需要测量一幅图像:在处理过程中,通过在目标附近组织一幅梯度直方图来收集其形状信息,这些直方图是许多形状分类器训练和使用的基础。因此,梯度角的误差会降低分类器识别的效果。

对于3×3的Sobel滤波器,梯度角距离水平或垂直方向越远,误差越明显。在OpenCV中,调用cv::Sobel()时设置ksize为cv::SCHARR,即可消除3×3这样小但是快的Sobel导数滤波器所带来的误差。Scharr滤波器核Sobel滤波器同样很快,但是前者精度更高。因此选择3×3的滤波器时,应当使用Scharr滤波器。

\begin{bmatrix} -3 & 0 & +3\\ -10 & 0 &+10 \\ -3& 0 & +3 \end{bmatrix}     或     \begin{bmatrix} -3 & -10 & -3\\ 0 & 0 &0 \\ +3& +10 & +3 \end{bmatrix}

拉普拉斯变换

OpenCV中的函数Laplacian实现了对拉普拉斯算子的离散近似:

Laplace(f)=\frac{\partial^2 f}{\partial x^2} + \frac{\partial^2 f}{\partial y^2}

void sv::Laplacian(InputArray src, OutputArray dst, int ddepth, cv::Size ksize=3, double scale=1, double delta=0, int borderType=cv::BORDER_DEFAULT)

只要ksize不为1,Laplacian算子的实现就是直接计算Sobel算子响应之和。当ksize=1时的卷积核如下所示:

\begin{bmatrix} 0 & 1 & 0\\ 1 & 4 &1 \\ 0& 1 & 0 \end{bmatrix}

Laplacian算子可应用于各种场景处理,一种常见的应用就是匹配“斑点”。Laplacian算子就是图像在x和y轴方向的导数之和,这意味着一个被较大值包围的点或小斑点(比ksize小)处的值将会变得很大。相反,被较小值包围的点或小斑点处的值将在负方向上变得很大。

Laplacian算子同样可以用于边缘检测,函数一阶导数在原函数变化大的地方,值会相应变大,同样在图像边缘处也同样变化,所有导数在这些地方将变得很大。因此可以在二阶导数为0的地方搜寻这么一个极大值,原图像中的边缘通过Laplacian算子运算后会变成0。对于有些不是边缘也变成0的问题,可以通过滤掉Sobel一阶导数中较大值的点解决。

图像形态学

OpenCV提供了一种高效且易用的图像形态学变换接口。其中有很多形态学方法,但基本上所有的形态学操作都基于两种原始操作——膨胀与腐蚀。

膨胀和腐蚀

膨胀和腐蚀是最基本的形态学变换,可用于消除噪声、元素分割和连接等。基于这两种操作,可以实现更复杂的形态学操作,用来定位强度峰值或孔洞、另一种形式的图像梯度等。

膨胀是一种卷积操作,它将目标像素的值替换为卷积核覆盖区域的局部最大值,扩张了明亮区域,填充凹面。此卷积核是一个非线性核,是一个四边形或圆形的实心核,其锚点在中心。与膨胀对应,腐蚀是与之相反的操作,腐蚀操作计算的是核覆盖范围内的局部最小值缩减了明亮区,消除凸起。

OpenCV的滤波与卷积_第15张图片

原图

OpenCV的滤波与卷积_第16张图片

膨胀

OpenCV的滤波与卷积_第17张图片

腐蚀

void cv::erode(InputArray src, OutputArray dst, InputArray element, cv::Point anchor=cv::Point(-1, -1), int iterations=1 int borderType=cv::BORDER_CONSTANT, const cv::Scalar& borderValue = cv::morphologyDefaultBorderValue())

void cv::dilate(InputArray src, OutputArray dst, InputArray element, cv::Point anchor=cv::Point(-1, -1), int iterations=1 int borderType=cv::BORDER_CONSTANT, const cv::Scalar& borderValue = cv::morphologyDefaultBorderValue())

以上两个函数分别是膨胀和腐蚀对应的函数,第三个参数是核,可以传递一个未初始化的cv::Mat,会使用默认的锚点在中心的3×3的核。

erode(x, y)=\underset{(i,\: j)\in kernel}{min}src(x+i,\: y+j)

dilate(x, y)=\underset{(i,\: j)\in kernel}{max}src(x+i,\: y+j)

腐蚀操作通常用于消除图中斑点一样的噪声,原理是斑点经过腐蚀后会消失,而大的可见区域不会受影响。膨胀操作通常用于发生连通分支。

通用形态学函数

当处理的对象是二值图像时,像素只能是开(>0)或关(=0)的图像掩膜时,基本的腐蚀和膨胀操作就够用了。需要对灰度图或者彩色图进行处理时,一些其他操作就非常有用了,这些操作可以通过cv::morphologyEx()实现。

void cv::morphologyEx(InputArray src, OutputArray dst, int op, InputArray element, cv::Point anchor=cv::Point(-1, -1), int iterations=1, int borderType=cv::BORDER_CONSTANT, const cv::Scalar& borderValue = cv::morphologyDefaultBorderValue())

cv::morphologyEx()操作op选项
操作值 形态学操作 是否需要临时图像
cv::MORPH_OPEN 开操作
cv::MORPH_CLOSE 闭操作
cv::MORPH_GRADIENT 形态学梯度 总是需要
cv::MORPH_TOPHAT 顶帽操作 就地调用需要(src = dst)
cv::MORPH_BLACKHAT 地貌操作 就地调用需要(src = dst)

开操作和闭操作

开操作先将图像进行腐蚀,然后对腐蚀的结果进行膨胀。开操作常用语对二值图像中的区域进行计算。

闭操作想将图像进行膨胀,然后对膨胀的结果进行腐蚀。闭操作用于复杂连通分支算法中减少无用或噪声驱动的片段。

对于连通分支,通常先进行腐蚀或闭操作消除噪声,然后通过开操作连接相互靠近的大型区域。

对于一幅非布尔型图像进行形态学操作时,闭操作最明显的效果是消除值小于邻域内的点的孤立异常,而开操作消除的是大于邻域内点的孤立异常值。

形态学梯度

gradient(src)=dilate(src)-erode(src)

梯度操作的结果(扩张亮域)减腐蚀操作的结果(缩减亮域)产生了原图像中的目标边缘。对于灰度图像,其结果就是计算明暗变换的趋势。形态学梯度通常用于显示明亮区域的边界,然后便可以将他们看作目标或者目标的部分。用扩张的图像减去了收缩的图像便得到完整的边界。与计算梯度不同,它并不会关注某个物体的周围。

顶帽和黑帽

TopHat(src)=src-open(src)

BlackHat(src)=close(src)-src

顶帽用于显示与其邻近相比更亮的部分;黑帽用于显示与其邻近相比更暗的部分。

#include "stdafx.h"
#include 


int main()
{
	cv::namedWindow("image", cv::WINDOW_NORMAL);
	cv::namedWindow("erosion", cv::WINDOW_NORMAL);
	cv::namedWindow("dilation", cv::WINDOW_NORMAL);
	cv::namedWindow("opening", cv::WINDOW_NORMAL);
	cv::namedWindow("closing", cv::WINDOW_NORMAL);
	cv::namedWindow("gradient", cv::WINDOW_NORMAL);
	cv::namedWindow("topHat", cv::WINDOW_NORMAL);
	cv::namedWindow("blackHat", cv::WINDOW_NORMAL);
	cv::Mat img = cv::imread("D:\\personal-data\\wallpapers\\test.png");
	cv::Mat erosion, dilation, opening, closing, gradient, topHat, blackHat;
	cv::erode(img, erosion, cv::Mat());
	cv::dilate(img, dilation, cv::Mat());
	cv::morphologyEx(img, opening, cv::MORPH_OPEN, cv::Mat());
	cv::morphologyEx(img, closing, cv::MORPH_CLOSE, cv::Mat());
	cv::morphologyEx(img, gradient, cv::MORPH_GRADIENT, cv::Mat());
	cv::morphologyEx(img, topHat, cv::MORPH_TOPHAT, cv::Mat());
	cv::morphologyEx(img, blackHat, cv::MORPH_BLACKHAT, cv::Mat());
	cv::imshow("image", img);
	cv::imshow("erosion", erosion);
	cv::imshow("dilation", dilation);
	cv::imshow("opening", opening);
	cv::imshow("closing", closing);
	cv::imshow("gradient", gradient);
	cv::imshow("topHat", topHat);
	cv::imshow("blackHat", blackHat);
	cv::waitKey(0);
	cv::destroyAllWindows();
    return 0;
}

自定义核

在形态学上,核常常称为“构造元素”,OpenCV提供了创建自定义形态学核的函数cv::getStructuringElement()。

cv::Mat cv::getStructuringElement(int shape, cv::Size ksize, cv::Point anchor=cv::Point(-1, -1))

cv::getStructuringElement()的元素形状
形状值 元素 描述
cv::MOEPH_RECT 矩形 E_{i,\: j}=1,\forall i,j
cv::MOEPH_ELLIPSE 椭圆形 以ksize.width和ksize.height为两个半径做椭圆
cv::MOEPH_CROSS 交叉

E_{i,\: j}=1,当 i == anchor.y或 j == anchor.x

用任意线性滤波器做卷积

\begin{bmatrix} -1 & 0 & +1\\ -2& 0 & +2\\ -1& 0 & +1 \end{bmatrix}=\begin{bmatrix} 1\\ 2\\ 1 \end{bmatrix}\bigotimes \begin{bmatrix} -1 & 0 & +1 \end{bmatrix}                    \begin{bmatrix} 0 & 1 & 0\\ 1 &1 &1 \\ 0 & 1 & 0 \end{bmatrix}

上述两个核中,左边的核是可分的,右边的是不可分的。一个可分核可以理解成两个一维核,在卷积时先调用x内核,然后再调用y内核。两个矩阵进行卷积所产生的消耗可以用两个矩阵的面积之积近似。如此一来,用n×n的核对面积为A的图像进行卷积所需要的时间是An^2,但如果分解为n×1和1×n的两个核,那么代价就是An+An=2An。由此可见,分解卷积核可以提高卷积计算的效率。

用cv::filter2D()进行卷积

void cv::filter2D(InputArray src, OutputArray dst, int ddepth, InputArray kernel, cv::Point anchor=cv::Point(-1, -1), double delta=0, int borderType=cv::BORDER_DEFAULT)

注:如果定义了锚点的位置,那么核的大小可以是偶数,否则必须是奇数。

通过cv::sepFilter2D使用可分核

void cv::sepFilter2D(InputArray src, OutputArray dst, int ddepth, InputArray rowKernel, InputArray  columnKernel, cv::Point anchor=cv::Point(-1, -1), double delta=0, int borderType=cv::BORDER_DEFAULT)

注:两个核的大小应当是n1×1和1×n2,n1和n2不一定相等。

生成卷积核

void cv::getDerivKernel(OutputArray kx, OutputArray ky, int dx, int dy, int ksize, bool normalize=true, int ktype=CV_32F)

作用:生成可分解核,如Sobel和Scharr核。dx和dy是求导顺序;ksize是核的大小,可以为1、3、5、7或cv::SCHARR;normalize指示是否核元素规范化,如果是浮点型图像,设为true,反之设为false;ktype表示滤波器的类型,可以使CV_32F和CV_64F。

cv::Mat cv::getGaussianKernel(int ksize, double sigma, int ktype=CV_32F)

作用:生成高斯核。

k_i=\alpha \cdot e^{-\frac{(i-(ksize-1)^2)^2}{(2\sigma )^2}}

α在滤波器需要规范化的时候才起作用。sigma可以为-1,这样将自动计算,其中\sigma =0.3\cdot (\frac{ksize-1}{2}-1)+0.8

你可能感兴趣的:(人工智能,C/C++,OpenCV)