【从零学习OpenCV 4】可分离滤波
重磅干货,第一时间送达
经过几个月的努力,小白终于完成了市面上第一本OpenCV 4入门书籍《OpenCV 4开发详解》。为了更让小伙伴更早的了解最新版的OpenCV 4,小白与出版社沟通,提前在公众号上连载部分内容,请持续关注小白。 |
前面介绍的滤波函数使用的滤波器都是固定形式的滤波器,有时我们需要根据实际需求调整滤波模板,例如在滤波计算过程中滤波器中心位置的像素值不参与计算,滤波器中参与计算的像素值不是一个矩形区域等。OpenCV 4无法根据每种需求单独编写滤波函数,因此OpenCV 4提供了根据自定义滤波器实现图像滤波的函数,就是我们本章最开始介绍的卷积函数filter2D(),不过根据函数的名称,这里称呼为滤波函数更为准确一些,输入的卷积模板也应该称为滤波器或者滤波模板。该函数的使用方式我们在一开始已经介绍,只需要根据需求定义一个卷积模板或者滤波器,便可以实现自定义滤波。
无论是图像卷积还是滤波,在原图像上移动滤波器的过程中每一次的计算结果都不会影响到后面过程的计算结果,因此图像滤波是一个并行的算法,在可以提供并行计算的处理器中可以极大的加快图像滤波的处理速度。除此之外,图像滤波还具有可分离行,这个性质我们在高斯滤波中有简单的接触,可分离性指的是先对X(Y)方向滤波,再对Y(X)方向滤波的结果与将两个方向的滤波器联合后整体滤波的结果相同。两个方向的滤波器的联合就是将两个方向的滤波器相乘,得到一个矩形的滤波器,例如X方向的滤波器为 ,Y方向的滤波器为 ,两个方向联合滤波器可以用式(5.7)计算,无论先进行X方向滤波还是Y方向滤波,两个方向联合滤波器都是相同的。
因此在高斯滤波中,我们利用getGaussianKernel()函数分别得到X方向和Y方向的滤波器,之后通过生成联合滤波器或者分别用两个方向的滤波器进行滤波的计算结果相同。但是两个方向联合滤波需要在使用filter2D()函数滤波之前计算联合滤波器,而两个方向分别滤波需要调用两次filter2D()函数,增加了通过代码实现的复杂性,因此OpenCV 4提供了可以输入两个方向滤波器实现滤波的滤波函数sepFilter2D(),该函数的函数原型在代码清单5-16中给出。
代码清单5-16 sepFilter2D()函数原型
void cv::sepFilter2D(InputArray src,
OutputArray dst,
int ddepth,
InputArray kernelX,
InputArray kernelY,
Point anchor = Point(-1,-1),
double delta = 0,
int borderType = BORDER_DEFAULT
)
src:待滤波图像 dst:输出图像,与输入图像src具有相同的尺寸、通道数和数据类型。 ddepth:输出图像的数据类型(深度),根据输入图像的数据类型不同拥有不同的取值范围,具体的取值范围在表5-1给出,当赋值为-1时,输出图像的数据类型自动选择。 kernelX:X方向的滤波器, kernelY:Y方向的滤波器。 anchor:内核的基准点(锚点),其默认值为(-1,-1)代表内核基准点位于kernel的中心位置。基准点即卷积核中与进行处理的像素点重合的点,其位置必须在卷积核的内部。 delta:偏值,在计算结果中加上偏值。 borderType:像素外推法选择标志,取值范围在表3-5中给出。默认参数为BORDER_DEFAULT,表示不包含边界值倒序填充。
该函数将可分离的线性滤波器分离成X方向和Y方向进行处理,与filter2D()函数不同之处在于,filter2D()函数需要通过滤波器的尺寸区分滤波操作是作用在X方向还是Y方向,例如滤波器尺寸为K×1时是Y方向滤波,1×K尺寸的滤波器是X方向滤波。而sepFilter2D()函数通过不同参数区分滤波器是作用在X方向还是Y方向,无论输入滤波器的尺寸是K×1还是1×K,都不会影响滤波结果。
为了更加了解线性滤波的可分离性,在代码清单5-17中给出了利用filter2D()函数和sepFilter2D()函数实现滤波的示例程序。程序中利用filter2D()函数依次进行Y方向和X方向滤波,将结果与两个方向联合滤波器滤波结果相比较,验证两种方式计算结果的一致性。同时将两个方向的滤波器输入sepFilter2D()函数中,验证该函数计算结果是否与前面的计算结果一致。最后利用自定义的滤波器,对图像依次进行X方向滤波和Y方向滤波,查看滤波结果是否与使用联合滤波器的滤波结果一致。程序的计算结果依次在图5-19、图5-20给出。
代码清单 5-17 myselfFilter.cpp可分离图像滤波
#include <opencv2\opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main()
{
system("color F0"); //更改输出界面颜色
float points[25] = { 1,2,3,4,5,
6,7,8,9,10,
11,12,13,14,15,
16,17,18,19,20,
21,22,23,24,25 };
Mat data(5, 5, CV_32FC1, points);
//X方向、Y方向和联合滤波器的构建
Mat a = (Mat_<float>(3, 1) << -1, 3, -1);
Mat b = a.reshape(1, 1);
Mat ab = a*b;
//验证高斯滤波的可分离性
Mat gaussX = getGaussianKernel(3, 1);
Mat gaussData, gaussDataXY;
GaussianBlur(data, gaussData, Size(3, 3), 1, 1, BORDER_CONSTANT);
sepFilter2D(data,gaussDataXY,-1, gaussX, gaussX, Point(-1,-1),0,BORDER_CONSTANT);
//输入两种高斯滤波的计算结果
cout << "gaussData=" << endl
<< gaussData << endl;
cout << "gaussDataXY=" << endl
<< gaussDataXY << endl;
//线性滤波的可分离性
Mat dataYX, dataY, dataXY, dataXY_sep;
filter2D(data, dataY, -1, a, Point(-1, -1), 0, BORDER_CONSTANT);
filter2D(dataY, dataYX, -1, b, Point(-1, -1), 0, BORDER_CONSTANT);
filter2D(data, dataXY, -1, ab, Point(-1, -1), 0, BORDER_CONSTANT);
sepFilter2D(data, dataXY_sep, -1, b, b, Point(-1, -1), 0, BORDER_CONSTANT);
//输出分离滤波和联合滤波的计算结果
cout << "dataY=" << endl
<< dataY << endl;
cout << "dataYX=" << endl
<< dataYX << endl;
cout << "dataXY=" << endl
<< dataXY << endl;
cout << "dataXY_sep=" << endl
<< dataXY_sep << endl;
//对图像的分离操作
Mat img = imread("lena.png");
if (img.empty())
{
cout << "请确认图像文件名称是否正确" << endl;
return -1;
}
Mat imgYX, imgY, imgXY;
filter2D(img, imgY, -1, a, Point(-1, -1), 0, BORDER_CONSTANT);
filter2D(imgY, imgYX, -1, b, Point(-1, -1), 0, BORDER_CONSTANT);
filter2D(img, imgXY, -1, ab, Point(-1, -1), 0, BORDER_CONSTANT);
imshow("img", img);
imshow("imgY", imgY);
imshow("imgYX", imgYX);
imshow("imgXY", imgXY);
waitKey(0);
return 0;
}
图5-19 myselfFilter.cpp程序中数据矩阵滤波结果
图5-20 myselfFilter.cpp程序中图像滤波结果