主函数中第一次执行GaussianBlur()非常耗时,执行过一次之后就恢复正常。
分析底层源码
GaussianBlur函数定义(位于sources\modules\imgproc\src\smooth.cpp)
void cv::GaussianBlur( InputArray _src, OutputArray _dst, Size ksize, double sigma1, double sigma2, int borderType )该函数的核心部分:
createGaussianKernels(kx, ky, type, ksize, sigma1, sigma2); sepFilter2D(_src, _dst, CV_MAT_DEPTH(type), kx, ky, Point(-1,-1), 0, borderType );createGaussianKernels计算高斯核,sepFilter2D是可分离2维滤波函数
sepFilter2D函数定义(位于sources\modules\imgproc\src\filter.cpp)
void cv::sepFilter2D( InputArray _src, OutputArray _dst, int ddepth, InputArray _kernelX, InputArray _kernelY, Point anchor, double delta, int borderType )该函数的核心部分:
Ptr<FilterEngine> f = createSeparableLinearFilter(src.type(), dst.type(), kernelX, kernelY, anchor, delta, borderType & ~BORDER_ISOLATED ); f->apply(src, dst, Rect(0,0,-1,-1), Point(), (borderType & BORDER_ISOLATED) != 0 );包括FilterEngine的create和apply,这里就是opencv各种滤波函数的底层实现形式,FilterEngine在opencv2.x版本中开放给用户使用,到了3.0则封闭了。
这是官方论坛对3.0取消FilterEngine的提问,但并没有提供解决办法:
http://answers.opencv.org/question/56498/opencv-30-beta-no-filterengine-class/
http://answers.opencv.org/question/69111/opencv-30-filters-missing/
为了继续深入,这里将FilterEngine添加进我的代码,具体方法见本文最后的“附:opencv 3.0添加FilterEngine类”
继续追查FilterEngine的来源
createSeparableLinearFilter函数的定义:
cv::Ptr<cv::FilterEngine> cv::createSeparableLinearFilter( int _srcType, int _dstType, InputArray __rowKernel, InputArray __columnKernel, Point _anchor, double _delta, int _rowBorderType, int _columnBorderType, const Scalar& _borderValue )该函数的最后:
Ptr<BaseRowFilter> _rowFilter = getLinearRowFilter( _srcType, _bufType, rowKernel, _anchor.x, rtype); Ptr<BaseColumnFilter> _columnFilter = getLinearColumnFilter( _bufType, _dstType, columnKernel, _anchor.y, ctype, _delta, bits ); return Ptr<FilterEngine>( new FilterEngine(Ptr<BaseFilter>(), _rowFilter, _columnFilter, _srcType, _dstType, _bufType, _rowBorderType, _columnBorderType, _borderValue ));
FilterEngine类(声明位于sources\modules\imgproc\src\filterengine.hpp,实现位于opencv\sources\modules\imgproc\src\filter.cpp)
class FilterEngine { public: //! the default constructor FilterEngine(); //! the full constructor. Either _filter2D or both _rowFilter and _columnFilter must be non-empty. FilterEngine(const Ptr<BaseFilter>& _filter2D, const Ptr<BaseRowFilter>& _rowFilter, const Ptr<BaseColumnFilter>& _columnFilter, int srcType, int dstType, int bufType, int _rowBorderType = BORDER_REPLICATE, int _columnBorderType = -1, const Scalar& _borderValue = Scalar()); //! the destructor virtual ~FilterEngine(); //! reinitializes the engine. The previously assigned filters are released. void init(const Ptr<BaseFilter>& _filter2D, const Ptr<BaseRowFilter>& _rowFilter, const Ptr<BaseColumnFilter>& _columnFilter, int srcType, int dstType, int bufType, int _rowBorderType = BORDER_REPLICATE, int _columnBorderType = -1, const Scalar& _borderValue = Scalar()); //! starts filtering of the specified ROI of an image of size wholeSize. virtual int start(Size wholeSize, Rect roi, int maxBufRows = -1); //! starts filtering of the specified ROI of the specified image. virtual int start(const Mat& src, const Rect& srcRoi = Rect(0,0,-1,-1), bool isolated = false, int maxBufRows = -1); //! processes the next srcCount rows of the image. virtual int proceed(const uchar* src, int srcStep, int srcCount, uchar* dst, int dstStep); //! applies filter to the specified ROI of the image. if srcRoi=(0,0,-1,-1), the whole image is filtered. virtual void apply( const Mat& src, Mat& dst, const Rect& srcRoi = Rect(0,0,-1,-1), Point dstOfs = Point(0,0), bool isolated = false); //! returns true if the filter is separable bool isSeparable() const { return !filter2D; } //! returns the number int remainingInputRows() const; int remainingOutputRows() const; int srcType; int dstType; int bufType; Size ksize; Point anchor; int maxWidth; Size wholeSize; Rect roi; int dx1; int dx2; int rowBorderType; int columnBorderType; std::vector<int> borderTab; int borderElemSize; std::vector<uchar> ringBuf; std::vector<uchar> srcRow; std::vector<uchar> constBorderValue; std::vector<uchar> constBorderRow; int bufStep; int startY; int startY0; int endY; int rowCount; int dstY; std::vector<uchar*> rows; Ptr<BaseFilter> filter2D; Ptr<BaseRowFilter> rowFilter; Ptr<BaseColumnFilter> columnFilter; };
实验:
方式一:
//变量定义 Mat &src = input(); //输入源数据 Mat &dst; //输出数据 Mat kx, ky; //x和y方向的滤波核 //方式一:sepFilter2D kx = cv::getGaussianKernel(5,1.0,6);ky = kx; sepFilter2D(src, dst, CV_MAT_DEPTH(CV_32F), kx, ky, cv::Point(-1,-1), 0, cv::BORDER_REFLECT);
方式二:
//方式二:GaussianBlur cv::GaussianBlur(src, dst, Size(5, 5), 1.0, 1.0, cv::BORDER_REFLECT);方式三:
//方式三:调用FilterEngine类 cv::Ptr<cv::FilterEngine> gauss = cv::createGaussianFilter(CV_32F,Size(5,5),1.0,1.0,cv::BORDER_REFLECT); gauss->apply(src,dst,cv::Rect(0,0,-1,-1),cv::Point(0,0),false);注意,三种方式独立运行,不要依次运行
结果:
三种方式的耗时都相近,因为GaussianBlur的底层实现就是sepFilter2D,而sepFilter2D的底层实现则是FilterEngine中的apply方法(见源码分析中的代码)
对于CV_32F的数据处理速度最快,CV_64F次之,最慢是处理CV_16U的数据,所以不要直接对整型数据进行滤波。
每次执行sepFilter2D,都会重新定义一个Ptr<FilterEngine> f,故不存在创建耗费时间的问题,否则就不仅仅是第一次运行GaussianBlur耗时了。
三种方式都存在一个问题, 就是在第一次调用时非常耗时(平时的几十倍)往后恢复正常。所以GaussianBlur第一次调用耗时的问题,其原因并非FilterEngine创建耗时
但如果不是这个原因,就显得无法解释了,到底问题的根源在哪里?
参考其它讨论GaussianBlur的博文:
关于高斯模糊与opencv中的GaussianBlur函数
http://blog.csdn.net/hachirou/article/details/6280200
二维高斯模糊和可分离核形式的快速实现
Opencv学习一:高斯滤波
点击打开链接
总结:解决第一次执行GaussianBlur()非常耗时的问题,只能通过在程序开始时执行一遍(经过测试,首次运行GaussianBlur可以是任意大小的输入数据和滤波核,也就是不必跟后面的一致),FilterEngine和sepFilter2D同理。而且,调用函数前先初始化输入变量Mat的值,将加快运行速度。如:
Mat t0 = Mat::zeros(Size(1000,1000),CV_32F); Mat t1 = Mat::zeros(Size(1000,1000),CV_32F); Mat kx = cv::getGaussianKernel(5,1.0,6);Mat ky = kx; cv::sepFilter2D(t0,t1, CV_MAT_DEPTH(CV_32F), kx, ky, cv::Point(-1,-1), 0, cv::BORDER_REFLECT);就比以下用法省时
Mat t0 = Mat(Size(1000,1000),CV_32F); Mat t1 = Mat(Size(1000,1000),CV_32F); Mat kx = cv::getGaussianKernel(5,1.0,6);Mat ky = kx; cv::sepFilter2D(t0,t1, CV_MAT_DEPTH(CV_32F), kx, ky, cv::Point(-1,-1), 0, cv::BORDER_REFLECT);当然,省时针对的是滤波函数本身,Mat::zeros也是有耗费时间的
进一步发现,首次调用涉及运算的opencv函数,其耗时都非常长,也就是说,不仅是滤波函数,连cv::multiply之类的函数也存在该问题。然而,Mat::convertTo、Mat::zeros等矩阵赋值操作则不存在该问题。
最后发现,只有opencv 3.0才存在该问题,2.4.9没有该问题!
推测:opencv3.0第一次执行这些函数(multiply、GaussianBlur等)时进行了一些初始化操作,导致第一次调用某些函数非常耗时,但具体进行了哪些操作无法得知,官方文档也没有就此问题进行解释
解决该问题的办法:程序开始时调用一次multiply函数。
经过测试,执行1000次multiply和GaussianBlur,处理Mat(Size(1000,1000),CV_32F)的数据,opencv3.0并没有什么优势,然而其官方声称IPP大幅提高了其性能,intel这……
附:opencv 3.0添加(重新开放)FilterEngine类
首先说明,为什么要往opencv 3.0中重新开放FilterEngine类?
就运行效率而言,其实上层封装好的各类滤波函数,如GaussianBlur,均是基于FilterEngine类的,所以无论是使用FilterEngine还是使用上层函数,其效率没有差别。因此官方才会封闭该类。
添加FilterEngine类,主要为了兼容旧代码,因为opencv 2.x有的代码中习惯使用FilterEngine的方式。
以下是具体方法:
1、编译opencv源码,必须自行编译一次,因为有一个用到的文件要编译后才能生成(假设编译opencv的project所在目录为D:\opencv\build_pure)
2、新建一个自己的应用程序的project,向其中添加以下文件并作相应修改(假设opencv源码解压目录为D:\opencv),往“文件所在目录”中找到相应的文件并复制到自己的project文件夹,然后再修改
(1)precomp.hpp
文件所在目录:D:\opencv\sources\modules\imgproc\src\precomp.hpp
共1项修改:
△ 删除语句,因为编译时会产生较多冲突
#include "opencv2/core/private.hpp"但去掉后会造成个别函数未定义,故还要手动添加这些函数,这些函数在(4)fiter.cpp中有说明
(2)opencl_kernels_imgproc.hpp和opencl_kernels_imgproc.cpp
文件所在目录:D:\opencv\build_pure\modules\imgproc\,注:这两个文件需要编译opencv源码之后才会生成
共1项修改:
△在D:\opencv\build_pure\install\include\opencv2\core下新建文件夹“opencl”,把位于D:\opencv\sources\modules\core\include\opencv2\core\opencl的ocl_defs.hpp拷贝至D:\opencv\build_pure\install\include\opencv2\core\opencl
(3)filterengine.hpp
文件所在目录:D:\opencv\sources\modules\imgproc\src\filterengine.hpp
共3项修改:
(说明:smooth.cpp位于D:\opencv\sources\modules\imgproc\src\smooth.cpp;templmatch.cpp位于D:\opencv\sources\modules\imgproc\src\templmatch.cpp)
△在smooth.cpp中找到createGaussianKernels函数,在filterengine.hpp中声明该函数,注意要在namespace cv中声明
void createGaussianKernels( Mat & kx, Mat & ky, int type, Size ksize, double sigma1, double sigma2 );
△在smooth.cpp中找到createGaussianFilter函数,在filterengine.hpp中声明该函数
Ptr<cv::FilterEngine> createGaussianFilter( int type, Size ksize, double sigma1, double sigma2, int borderType );
△在templmatch.cpp中找到crossCorr函数,在filterengine.hpp中声明该函数
void crossCorr( const Mat& src, const Mat& templ, Mat& dst, Size corrsize, int ctype, Point anchor=Point(0,0), double delta=0, int borderType=BORDER_REFLECT_101 );
注:以上仅仅包括了高斯滤波相关的函数,如果是其它滤波算法,则自行在opencv源码目录中查找该滤波算法所需的模块(可以通过debug单步跟踪了解某个函数涉及的底层代码);再次声明,这些函数均是filterengine中用到的底层代码,opencv3.0没有暴露给用户,所以需要手动添加,如果查找过程中发现某个函数是开放给用户的,即前面带有CV_EXPORTS关键字的则无需手动添加
(4)filter.cpp
文件所在目录:D:\opencv\sources\modules\imgproc\src\filter.cpp
共4项修改:
(说明:private.hpp位于D:\opencv\sources\modules\core\include\opencv2\core\private.hpp)
△承接(3),把在smooth.cpp中找到的createGaussianKernels函数,其实现拷贝至filter.cpp中
void cv::createGaussianKernels( Mat & kx, Mat & ky, int type, Size ksize, double sigma1, double sigma2 ) { ... }
△承接(3),把在smooth.cpp中找到的createGaussianFilter函数,其实现拷贝至filter.cpp中
cv::Ptr<cv::FilterEngine> cv::createGaussianFilter( int type, Size ksize, double sigma1, double sigma2, int borderType ) { ... }
△承接(3),把在templmatch.cpp中找到的crossCorr函数,其实现拷贝至filter.cpp中
void crossCorr( const Mat& img, const Mat& _templ, Mat& corr, Size corrsize, int ctype, Point anchor, double delta, int borderType ) { ... }△在private.hpp中找到scalarToRawData函数,将其实现拷贝至filter.cpp中
void scalarToRawData(const Scalar& s, void* _buf, int type, int unroll_to) { ... }
3、在程序中引用头文件即可使用FilterEngine类
#include "filterengine.hpp"
这是改好之后的5个文件,添加进去项目就可以使用FilterEngine类,基于VS2012:http://download.csdn.net/detail/kelvin_yan/9203965
----------------------END----------------------------