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

opencv 例程讲解5 ---- 如何实现卷积运算

2014-01-21 16:17 375 查看
图像可以看作一个矩阵,在矩阵上面做掩模操作是一个很普遍的事情,它实际上可以看做利用一个小矩阵对一个大矩阵进行卷积运算,这个小矩阵便是掩模,也称核(kernel)。很多功能的实现都依赖于掩模操作,如图像平滑,锐化,以及腐蚀膨胀等形态学的一些操作。下面我们来看下Opencv中的例程——(TUTORIAL) mat_mask_operations。

例程中使用了一个3x3的掩模对图像进行操作,实现锐化效果。先简单介绍下使用到的掩模,如下所示。



可以看出,为得到新图像中(i,j)位置的灰度值,需要借助一个卷积窗口,窗口大小与掩模大小一致,由以(i,j)为中心的图像像素组成,则卷积运算可以看做是卷积窗口中各像素点的加权和,每个像素点的加权大小对应于掩模中相应位置的值。上图中,一次卷积需要1次乘法,4次加减法。由于不只是对当前像素点遍历,还需要借助相邻像素进行计算,所以例程中使用的图像遍历方法是C的[ ] 下标访问方式。相应的,opencv对于掩模操作有自己的函数,filter2D,下面便是性能对比。



源代码中每种方法只允许了一遍,为了测试的准确性,我在程序中加了一个循环,这个结果是100次运算的平均时间。可以看出使用opencv的filter2D函数比自己手工打造的掩模操作性能上提高了39%

下面我们看下手工打造的掩模操作是如何实现的

void Sharpen(const Mat& myImage,Mat& Result)
{
CV_Assert(myImage.depth() == CV_8U);  // accept only uchar images   // CV_Assert 是cv定义的断言格式,如果条件不符合,会中断程序,抛出异常

const int nChannels = myImage.channels();
Result.create(myImage.size(),myImage.type());

for(int j = 1 ; j < myImage.rows-1; ++j)                             // 第一行和最后一行无法计算
{
const uchar* previous = myImage.ptr<uchar>(j - 1);      //   上一行数据的指针
const uchar* current  = myImage.ptr<uchar>(j    );        //   当前行数据的指针
const uchar* next     = myImage.ptr<uchar>(j + 1);       //  下一行数据的指针

uchar* output = Result.ptr<uchar>(j);                            // 输出图像当前列数据的指针

for(int i= nChannels;i < nChannels*(myImage.cols-1); ++i)   // 同理,忽略第一列和最后一列像素
{
*output++ = saturate_cast<uchar>(5*current[i]
-current[i-nChannels] - current[i+nChannels] - previous[i] - next[i]);         // 计算新值
}
}
// 将边界设为0
Result.row(0).setTo(Scalar(0));
Result.row(Result.rows-1).setTo(Scalar(0));
Result.col(0).setTo(Scalar(0));
Result.col(Result.cols-1).setTo(Scalar(0));
}


可以看出,例程中利用了3个指针,分别用于定位在3行中的像素,实现对相邻像素的操作。输入图像(myImage)中的像素通过[ ]下标读取,而输出图像(output)中通过++操作进行指针移位操作。下面是输入图像和输出图像的对比。可以看出这个掩模实现了锐化的效果。通过改变掩模矩阵中的值,我们还能实现平滑滤波以及形态学操作等。



我们再来看看,如何调用opencv自带的filter2D函数

Mat kern = (Mat_<char>(3,3) <<  0, -1,  0,                          // 生成一个掩模核 大小为 3x3 , 通过<<  输入到矩阵Mat_<char> 中,然后隐式转换成Mat类型
-1,  5, -1,
0, -1,  0);
t = (double)getTickCount();                                               //Opencv中的一种计时方法,获取当前Tick数
for (int i=0; i<times; i++)
{
filter2D(I, K, I.depth(), kern );
}
t = ((double)getTickCount() - t)/getTickFrequency();    // 需要除掉Tick的频率,单位为秒
cout << "Built-in filter2D time passed in seconds:      " << t/times << endl;


filter2D函数的原型为,最小需要提供4个参数,第一个参数为输入,第二参数为输出,第三参数表示depth,可以为CV_8U, CV_16U, CV_8S, CV_16S, CV_32F, CV_64F, 第四个参数为输入的掩模核。

void filter2D( InputArray src, OutputArray dst, int ddepth,
InputArray kernel, Point anchor=Point(-1,-1),
double delta=0, int borderType=BORDER_DEFAULT );
这里介绍下opencv中的输入输出类型, InputArray , OutputArray。

以下一段是opencv reference文档中对InputArray, OutputArray的描述。

Many OpenCV functions process dense 2-dimensional or multi-dimensional numerical arrays. Usually, such functions take cpp:class:Mat as parameters, but in some cases it’s more convenient to use std::vector<> (for a point set, for example) or Matx<> (for 3x3
homography matrix and such). To avoid many duplicates in the API, special “proxy” classes have been introduced. The base “proxy” class is InputArray. It is used for passing read-only arrays on a function input. The derived from InputArray class OutputArray
is used to specify an output array for a function. Normally, you should not care of those intermediate types (and you should not declare variables of those types explicitly) - it will all just work automatically. You can assume that instead of InputArray/OutputArray
you can always use Mat, std::vector<>, Matx<>, Vec<> or Scalar. When a function has an optional input or output array, and you do not have or do not want one, pass cv::noArray().

按照上面的描述,在opencv 函数处理的大部分是2维或者多维的数据,即Mat 类型,但是对于一些点集,或者3x3的homography矩阵,使用vector<>,或者Matx<>类型更加方便,如果对每一种数据类型都写一个接口API的话,会造成API的代码的过多重复,因此,opencv引入 代理(“proxy”)类型 InputArray,OutputArray,对输入输出类型进行统一管理。

在源码中我们可以找到如下的定义, 它被typedef为 类 _InputArray 的常引用,这也是描述中InputArray为 read-only 的原因。

typedef const _InputArray& InputArray;
typedef const _OutputArray& OutputArray;

如果继续看 _InputArray 的定义,

//////////////////////// Input/Output Array Arguments /////////////////////////////////

/*!
Proxy datatype for passing Mat's and vector<>'s as input parameters
*/
class CV_EXPORTS _InputArray
{
public:
enum {
KIND_SHIFT = 16,
FIXED_TYPE = 0x8000 << KIND_SHIFT,
FIXED_SIZE = 0x4000 << KIND_SHIFT,
KIND_MASK = 31 << KIND_SHIFT,

NONE              = 0 << KIND_SHIFT,
MAT               = 1 << KIND_SHIFT,
MATX              = 2 << KIND_SHIFT,
STD_VECTOR        = 3 << KIND_SHIFT,
STD_VECTOR_VECTOR = 4 << KIND_SHIFT,
STD_VECTOR_MAT    = 5 << KIND_SHIFT,
EXPR              = 6 << KIND_SHIFT,
OPENGL_BUFFER     = 7 << KIND_SHIFT,
CUDA_MEM          = 8 << KIND_SHIFT,
GPU_MAT           = 9 << KIND_SHIFT,
OCL_MAT           =10 << KIND_SHIFT,
UMAT              =11 << KIND_SHIFT,
STD_VECTOR_UMAT   =12 << KIND_SHIFT,
UEXPR             =13 << KIND_SHIFT
};

_InputArray();
_InputArray(int _flags, void* _obj);
_InputArray(const Mat& m);
_InputArray(const MatExpr& expr);
_InputArray(const std::vector<Mat>& vec);
template<typename _Tp> _InputArray(const Mat_<_Tp>& m);
template<typename _Tp> _InputArray(const std::vector<_Tp>& vec);
template<typename _Tp> _InputArray(const std::vector<std::vector<_Tp> >& vec);
template<typename _Tp> _InputArray(const std::vector<Mat_<_Tp> >& vec);
template<typename _Tp> _InputArray(const _Tp* vec, int n);
template<typename _Tp, int m, int n> _InputArray(const Matx<_Tp, m, n>& matx);
_InputArray(const double& val);
_InputArray(const cuda::GpuMat& d_mat);
_InputArray(const ogl::Buffer& buf);
_InputArray(const cuda::CudaMem& cuda_mem);
template<typename _Tp> _InputArray(const cudev::GpuMat_<_Tp>& m);
_InputArray(const UMat& um);
_InputArray(const std::vector<UMat>& umv);
_InputArray(const UMatExpr& uexpr);

virtual Mat getMat(int idx=-1) const;
virtual UMat getUMat(int idx=-1) const;
virtual void getMatVector(std::vector<Mat>& mv) const;
virtual cuda::GpuMat getGpuMat() const;
virtual ogl::Buffer getOGlBuffer() const;
void* getObj() const;

virtual int kind() const;
virtual int dims(int i=-1) const;
virtual Size size(int i=-1) const;
virtual int sizend(int* sz, int i=-1) const;
virtual bool sameSize(const _InputArray& arr) const;
virtual size_t total(int i=-1) const;
virtual int type(int i=-1) const;
virtual int depth(int i=-1) const;
virtual int channels(int i=-1) const;
virtual bool isContinuous(int i=-1) const;
virtual bool empty() const;
virtual void copyTo(const _OutputArray& arr) const;
virtual size_t offset(int i=-1) const;
virtual size_t step(int i=-1) const;
bool isMat() const;
bool isUMat() const;
bool isMatVectot() const;
bool isUMatVector() const;
bool isMatx();

virtual ~_InputArray();

protected:
int flags;
void* obj;
Size sz;

void init(int _flags, const void* _obj);
void init(int _flags, const void* _obj, Size _sz);
};


可以看出InputArray 中的数据只有3个,一个Size 类型, 一个 flags 用来记录传人的数据类型,一个则为传人的对象指针;提供了多种类型的隐式转换,将输入类型转换成InputArray传递到函数中,然后在函数中,可以利用getMat() 等方法获得输入对象的指针。

OutputArray 为InputArray的派生类,主要增加了create()等方法,下面为OutputArray的源码。

/*!
Proxy datatype for passing Mat's and vector<>'s as input parameters
*/
class CV_EXPORTS _OutputArray : public _InputArray
{
public:
enum
{
DEPTH_MASK_8U = 1 << CV_8U,
DEPTH_MASK_8S = 1 << CV_8S,
DEPTH_MASK_16U = 1 << CV_16U,
DEPTH_MASK_16S = 1 << CV_16S,
DEPTH_MASK_32S = 1 << CV_32S,
DEPTH_MASK_32F = 1 << CV_32F,
DEPTH_MASK_64F = 1 << CV_64F,
DEPTH_MASK_ALL = (DEPTH_MASK_64F<<1)-1,
DEPTH_MASK_ALL_BUT_8S = DEPTH_MASK_ALL & ~DEPTH_MASK_8S,
DEPTH_MASK_FLT = DEPTH_MASK_32F + DEPTH_MASK_64F
};

_OutputArray();
_OutputArray(int _flags, void* _obj);
_OutputArray(Mat& m);
_OutputArray(std::vector<Mat>& vec);
_OutputArray(cuda::GpuMat& d_mat);
_OutputArray(ogl::Buffer& buf);
_OutputArray(cuda::CudaMem& cuda_mem);
template<typename _Tp> _OutputArray(cudev::GpuMat_<_Tp>& m);
template<typename _Tp> _OutputArray(std::vector<_Tp>& vec);
template<typename _Tp> _OutputArray(std::vector<std::vector<_Tp> >& vec);
template<typename _Tp> _OutputArray(std::vector<Mat_<_Tp> >& vec);
template<typename _Tp> _OutputArray(Mat_<_Tp>& m);
template<typename _Tp> _OutputArray(_Tp* vec, int n);
template<typename _Tp, int m, int n> _OutputArray(Matx<_Tp, m, n>& matx);
_OutputArray(UMat& m);
_OutputArray(std::vector<UMat>& vec);

_OutputArray(const Mat& m);
_OutputArray(const std::vector<Mat>& vec);
_OutputArray(const cuda::GpuMat& d_mat);
_OutputArray(const ogl::Buffer& buf);
_OutputArray(const cuda::CudaMem& cuda_mem);
template<typename _Tp> _OutputArray(const cudev::GpuMat_<_Tp>& m);
template<typename _Tp> _OutputArray(const std::vector<_Tp>& vec);
template<typename _Tp> _OutputArray(const std::vector<std::vector<_Tp> >& vec);
template<typename _Tp> _OutputArray(const std::vector<Mat_<_Tp> >& vec);
template<typename _Tp> _OutputArray(const Mat_<_Tp>& m);
template<typename _Tp> _OutputArray(const _Tp* vec, int n);
template<typename _Tp, int m, int n> _OutputArray(const Matx<_Tp, m, n>& matx);
_OutputArray(const UMat& m);
_OutputArray(const std::vector<UMat>& vec);

virtual bool fixedSize() const;
virtual bool fixedType() const;
virtual bool needed() const;
virtual Mat& getMatRef(int i=-1) const;
virtual cuda::GpuMat& getGpuMatRef() const;
virtual ogl::Buffer& getOGlBufferRef() const;
virtual cuda::CudaMem& getCudaMemRef() const;
virtual void create(Size sz, int type, int i=-1, bool allowTransposed=false, int fixedDepthMask=0) const;
virtual void create(int rows, int cols, int type, int i=-1, bool allowTransposed=false, int fixedDepthMask=0) const;
virtual void create(int dims, const int* size, int type, int i=-1, bool allowTransposed=false, int fixedDepthMask=0) const;
virtual void createSameSize(const _InputArray& arr, int mtype) const;
virtual void release() const;
virtual void clear() const;
virtual void setTo(const _InputArray& value) const;
};
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: