您的位置:首页 > 运维架构

opencv3.0的GaussianBlur问题

2015-10-22 17:31 267 查看

问题

主函数中第一次执行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 ));


可见,这里终于真正意义上创建了滤波引擎,通过new一个Ptr<FilterEngine>对象

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;
};


推测可能原因:创建FilterEngine耗费时间

实验:

方式一:

//变量定义
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函数

/article/10846426.html

二维高斯模糊和可分离核形式的快速实现

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----------------------------
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: