简介
在图像增强中,我们经常要使用一个滤波器对图像进行操作。所谓图像的滤波操作就是根据给定的滤波器(一个掩码矩阵,也称作核)重新计算图像中每个像素的值。
在我们使用拉普拉斯算子来增强图像时,经常使用的滤波器可以表示成为:
0 | -1 | 0 |
-1 | 5 | -1 |
0 | -1 | 0 |
I(x, y) = 5 * I( x, y) - I( x - 1, y) - I( x + 1, y ) - I( x, y - 1 ) - I( x, y + 1)
在上一篇的高效的方法访问图像矩阵时,我们知道,可以通过[]操作符来对图像的像素进行操作。即我们可以继续使用这种方法来完成图像的滤波操作,代码如下:
const int nChannels = input.channels();
for( int i = 1; i < input.rows - 1; ++ i )
{
const uchar * previous = input.ptr<uchar>(i-1);
const uchar * current = input.ptr<uchar>(i);
const uchar * next = input.ptr<uchar>(i+1);
uchar* outdata = output.ptr<uchar>(i);
for( int j = nChannels; j < nChannels * (input.cols - 1); ++ j )
{
outdata[j] = saturate_cast<uchar>(5 * current[j] - current[j - nChannels] - current[j + nChannels] - previous[j] - next[j]);
}
}
在上面的操作中,因为图像的第一行,第一列,最后一行和最后一列的值无法计算。所以,在实际操作中注意避开。在上述代码中,我们可以通过定义三个指针,分别指向当前操作图像矩阵的前一行,当前行,下一行的指针。 然后定义一个指向输出图像当前行的指针。之后,利用上面的指针找到对应的相邻像素点并乘以对应的权值,计算新的像素值。这里需要注意的是,OpenCV为我们提供saturate_cast<uchar>(int)的转换类型。以保证我们所得到的值在0-255之间。一般来讲当int值小于0值取0值,当int值大于255时,取255。另外还需要注意的是,对灰度图像和彩色图像存储时,对应列数的不同。所以在这里,对列数进行循环的时候乘上了通道数。
OK了,图像的滤波操作,就这样被我们编程实现了,但是,这样做是最好的选择吗?由于滤波器在图像处理中应用的太广泛了,所以OpenCV为了减轻我们的负担,OpenCV为我们提供了filter2D函数来帮助我们更方便的实现这种功能。filter2D的原型如下:
//! applies non-separable 2D linear filter to the image
CV_EXPORTS_W void filter2D( InputArray src, OutputArray dst, int ddepth,
InputArray kernel, Point anchor=Point(-1,-1),
double delta=0, int borderType=BORDER_DEFAULT );
src是输入图像,dst为输出图像,ddepth为输出图像的深度,kernel为核矩阵,anchor为核的中心,一般取默认值,delta为额外需要增加的值,borderType为边框的默认处理方式。
从原型可以看出,我们需要传入一个kernel的矩阵类型。这个就是核矩阵。我们可以这样来定义它: Mat kern = (Mat_<char>(3, 3) << 0, -1, 0,
-1, 5, -1,
0, -1, 0);
之后,我们可以调用filter2D函数,如下所示:
Mat opencvOutput;
filter2D( input, opencvOutput, input.depth(), kern );
是不是比上面的代码简单了很多?那么它的效率和我们自己动手写的相比如何呢?(我们可以利用上一篇中讲到的方法来对比一下两种方法所需要的时间。)一般来讲,它的处理效率往往比我们自己处理的要高。所以,在今后的代码中,我们尽量使用filter2D函数。
完整程序:
#include <cv.h>
#include <highgui.h>
#include <iostream>
using namespace std;
using namespace cv;
void filter( Mat& input, Mat& output );
int main()
{
Mat input = imread( "E:/TestPic/test1.jpg", CV_LOAD_IMAGE_COLOR );
double time = (double)getTickCount();
Mat ownOutput = input.clone();
filter( input, ownOutput );
time = ((double)getTickCount() - time ) * 1000 / getTickFrequency();
cout << "自己实现的方法花费了" << time << "毫秒" << endl;
time = (double)getTickCount();
Mat kern = (Mat_<char>(3, 3) << 0, -1, 0,
-1, 5, -1,
0, -1, 0);
Mat opencvOutput;
filter2D( input, opencvOutput, input.depth(), kern );
time = ((double)getTickCount() - time) * 1000 / getTickFrequency();
cout << "OpenCV中的方法花费了" << time << "毫秒" << endl;
namedWindow( "src image", CV_WINDOW_AUTOSIZE );
namedWindow( "own dst image", CV_WINDOW_AUTOSIZE );
namedWindow( "opencv dst image", CV_WINDOW_AUTOSIZE );
imshow( "src image", input );
imshow( "own dst image", ownOutput );
imshow( "opencv dst image", opencvOutput );
waitKey(0);
return 0;
}
void filter( Mat& input, Mat& output )
{
const int nChannels = input.channels();
for( int i = 1; i < input.rows - 1; ++ i )
{
const uchar * previous = input.ptr<uchar>(i-1);
const uchar * current = input.ptr<uchar>(i);
const uchar * next = input.ptr<uchar>(i+1);
uchar* outdata = output.ptr<uchar>(i);
for( int j = nChannels; j < nChannels * (input.cols - 1); ++ j )
{
outdata[j] = saturate_cast<uchar>(5 * current[j] - current[j - nChannels] - current[j + nChannels] - previous[j] - next[j]);
}
}
}
运行结果:
可以看到,使用opencv提供的函数和我们自己实现的函数处理的结果是一致的,但是效率更高。变换后的图像相对原图像的边缘更加清晰。