在OpenCV中,有两个代理类是经常出现的:InputArray和OutputArray,它巧妙地利用了C++的定义转换,辅助完成对矩阵的管理。
定义
typedef const _InputArray& InputArray;
typedef InputArray InputArrayOfArrays;
typedef const _OutputArray& OutputArray;
typedef OutputArray OutputArrayOfArrays;
typedef const _InputOutputArray& InputOutputArray;
typedef InputOutputArray InputOutputArrayOfArrays;
说明
InputArray是一个代理类,用于将只读输入数组传递到OpenCV函数中。
其中,输入数组是可以`Mat`, `Mat_
关于这个类,有一些关键点要注意:
(1)作为可选的输入参数,当需要输入数组或矩阵为空时,传递cv::noArray()即可,或者简单的使用cv::Mat() ,就像我们常做过的那样;
(2)该类仅用于传递参数。也就是说,我们通常不应该声明此类类型的本地成员和本地变量或全局变量;
(3)在函数内部, 我们可以通过_InputArray::getMat()方法构造一个数组(矩阵)的信息头(这不会拷贝数据);
(4)可以使用_InputArray::kind() 来对数据进行类别区分,比如到底是矩阵Mat 还是向量`vector<>`等.
同样,OutputArray有类似InputArray的性质,所以也不应该单独定义此类的成员,如果不需要计算某些输出数组,传入cv::noArray()即可,在应用上,可以用_OutputArray::needed()检查某些数组是否需要计算并输出。
下面是一个OpneCV源码注释中使用InputArray, OutputArray的例子,
void myAffineTransform(InputArray _src, OutputArray _dst, InputArray _m)
{
// 从输入数组(矩阵)中得到 Mat headers
Mat src = _src.getMat(), m = _m.getMat();
// CV_Assert(src.type() == CV_32FC2 && m.type() == CV_32F && m.size() == Size(3, 2));
CV_Assert(src.type() == CV_32FC2);
CV_Assert(m.type() == CV_32F);
CV_Assert(m.size() == Size(3, 2));
// 重新创建 一 个输出数组(矩阵).
_dst.create(src.size(), src.type());
Mat dst = _dst.getMat();
for (int i = 0; i < src.rows; i++)
for (int j = 0; j < src.cols; j++)
{
Point2f pt = src.at(i, j);
dst.at(i, j) = Point2f(m.at(0, 0)*pt.x +
m.at(0, 1)*pt.y +
m.at(0, 2),
m.at(1, 0)*pt.x +
m.at(1, 1)*pt.y +
m.at(1, 2));
}
}
int main()
{
float m[3][2] = {
1,0,0,
0,1,0,
};
Mat mt(2, 3, CV_32F);
for (int i = 0; i(i, j) = m[i][j];
}
}
std::vector vec;
// 圆周上的点
for (int i = 0; i < 30; i++)
vec.push_back(Point2f((float)(100 + 30 * cos(i*CV_PI * 2 / 5)),
(float)(100 - 30 * sin(i*CV_PI * 2 / 5))));
cv::transform(vec, vec, cv::Matx23f(0.707, -0.707, 10, 0.707, 0.707, 20));
Mat dst;
myAffineTransform(vec, dst, mt);
}
在myAffineTransform(InputArray _src, OutputArray _dst, InputArray _m)接受_src, _dst这两个参数的时候,由于定义的转换,代理类被实例化,这些代理类有很多的构造函数,分别针对不同的输入类型,在这里,InputArray的构造函数是
inline _InputArray::_InputArray(const Mat& m) { init(MAT+ACCESS_READ, &m); }
inline void _InputArray::init(int _flags, const void* _obj){ flags = _flags; obj = (void*)_obj; }
我们看到,这里限定了Mat的类型为MAT,并且为只读权限ACCESS_READ,这两个参数被保存到参数flags中,flags相当于当前存储的数据类型和读/写方式,而obj存储的则是数据的内存地址。
OutputArray的构造函数是
inline _OutputArray::_OutputArray(Mat& m) { init(MAT+ACCESS_WRITE, &m); }
因为OutputArray继承了InputArray,所以会和前面的代码一样,MAT+ACCESS_WRITE保存参数flags中。
InputArray和OutputArray两个类都是代理数据类型,用来接收Mat和Vector<>作为输入参数,OutputArray继承自InputArray。
InputArray作为输入参数的时候,传入的参数加了const限定符,即它只接收参数作为纯输入参数,无法更改输入参数的内容。而OutputArray则没有加入限定符,可以对参数的内容进行更改。
InputArray使用一系列的数据类型作为输入实例化自身,通过设定一系列的构造函数来实现。
_InputArray::_InputArray(constMat&m) : flags(MAT),obj((void*)&m) {}
_InputArray::_InputArray(constvector
_InputArray::_InputArray(constdouble&val) : flags(FIXED_TYPE +FIXED_SIZE + MATX +CV_64F), obj((void*)&val),sz(Size(1,1)) {}
.....
可以看到在构造的时候,同时指定了flags和obj,flags用于表明当前存储的数据类型,而obj存储的则是数据的内存地址。
除了这些基本的构造函数外,还有其他支持泛型的构造函数,如下
/// Input/Output Arrays /
template
: flags(FIXED_TYPE +STD_VECTOR + DataType<_Tp>::type),obj((void*)&vec) {}
template
: flags(FIXED_TYPE +STD_VECTOR_VECTOR + DataType<_Tp>::type),obj((void*)&vec) {}
template
: flags(FIXED_TYPE +STD_VECTOR_MAT + DataType<_Tp>::type),obj((void*)&vec) {}
(这里只列出了部分,更详细的可以参见mat.hpp,或者全工程搜索_InputArray::_InputArray),对于泛型的构造函数,可以看出flags除了存放了传入的对象的类型,还存放了传入对象内部数据的类型,也就是里面的DataType<_Tp>::type。有了这个类型之后可以知道当前数据的类型,基于这个,可以实现将传入数据打包成不同的数据,例如将vector数据打包成Mat型数据。
后面可以使用CV_MAT_MASK获取数据元素的类型,在另一篇文章有解说opencv数据类型的位操作。总的来说,flags的低12位存放了数据元素的类型,包括通道channel和深度depth,而在本类里新定义的对象类型,则是从高16位开始的(从下面的enum定义可知),这样两者就互不干扰了。
类型标记
这两个类的作用是使Mat和Vector<>均可以作为统一的参数,将这些类型转换成InputArray的时候,会自动生成对应的类型标记,标记为类里的成员flag,然后在其他地方再根据这个标记解析出实际的类型。
支持这些类型的数据
enum {
KIND_SHIFT = 16,
FIXED_TYPE = 0x8000 < FIXED_SIZE = 0x4000 < KIND_MASK = ~(FIXED_TYPE|FIXED_SIZE) - (1 < NONE = 0 < MAT = 1 < MATX = 2 < STD_VECTOR = 3 < STD_VECTOR_VECTOR = 4 < STD_VECTOR_MAT = 5 < EXPR = 6 < OPENGL_BUFFER = 7 < OPENGL_TEXTURE = 8 < GPU_MAT = 9 < }; 获取类型使用 int_InputArray::kind()const { returnflags &KIND_MASK; } 该函数返回的就是NONE = 0 < 数据转换 为了从InputArray取出具体类型的数据,该类提供了一系列的接口函数,如下: virtualMatgetMat(inti=-1)const; virtualvoidgetMatVector(vector virtualGlBuffergetGlBuffer()const; virtualGlTexturegetGlTexture()const; virtualgpu::GpuMatgetGpuMat()const; 可以看出,这些类型分别对应于flags里面的那些类型,具体的实现可以看里面的代码,但是这里面有比较有意思的地方,即传入的类型可以解压成其他的类型,就如开始时讲到的将vector打包成Mat类型。 要实现不同类型的打包操作需要用到如下几个函数 virtual Size size(int i=-1) const; 该函数返回当前类型的size。仅看该函数里面的vector部分: Size_InputArray::size(inti)const { intk =kind(); if( k == STD_VECTOR ) { CV_Assert( i < 0 ); constvector constvector size_tszb =v.size(), szi = iv.size(); returnszb ==szi ? Size((int)szb, 1) :Size((int)(szb/CV_ELEM_SIZE(flags)), 1); } } 可以看出,首先将将原始的数据转换成最小单位uchar的vector,然后根据它的size就可以得到该数据所占的字节数,再除一个元素(旧事重提,opencv中一个元素对应于一个CV_MAKE_TYPE类型所指定的数据,例如三个通道的RGB类型是CV_MAKE_TYPE(CV_8U,3)即对应三个字节)大小所占的字节即可以得到元素的个数。并且可以看出这里的返回的size仅仅是指定了rows的,cols全部都设为1,也就是说,对于vector转换为Mat,返回的是仅仅有一列的矩阵。 virtual Mat getMat(int i=-1) const; 该函数将数据打包成Mat返回,仅仅vector转Mat的部分 Mat_InputArray::getMat(inti)const { intk =kind(); if( k == STD_VECTOR ) { CV_Assert( i < 0 ); intt =CV_MAT_TYPE(flags); constvector return !v.empty() ?Mat(size(),t, (void*)&v[0]) :Mat(); } } 从这里代码可以看出,首先是从flags里面提取出元素类型编码,然后使用基本单位uchar(计算机中最小的存储单位是8位,即一个字节,这里uchar就是8字节)来将初始数据的内存进行解引用(关于对vector所对应的指针进行解引用的问题,和人讨论过应该是vector重载了解引用运算符*,但是不确定~~),然后就使用size、类型编码以及内存地址就可以构造一个Mat了。
---------------------
作者:Justin__Ko
来源:CSDN
原文:https://blog.csdn.net/kfqcome/article/details/41819835
版权声明:本文为博主原创文章,转载请附上博文链接!