在阅读opencv文档和源码时,常常看到InputArray、OutputArray、UMat、Mat_等“奇怪”的类,与我们常用的Mat,好像一样又不完全一样,这些其实主要在opencv内部用,其中有些文档都很少甚至没有,在opencv外部程序还是Mat主打。另外还有Mat的成员create(),copyTo(),clone(),data,empty()等,到底有哪些使用中的注意事项。本文通过源码解析一下。
代理类(proxy)InputArray、OutputArray作为函数的形参,统一接口。opencv用户比较少用到,但是很多内部函数都用来做形参,确实,这两个类只用作形参,不要用来声明新的变量。可以传进来的类包括:
Mat
, Mat_<T>
, Matx<T, m, n>
, std::vector<T>
, std::vector<std::vector<T> >
, std::vector<Mat>
, std::vector<Mat_<T> >
, UMat
, std::vector<UMat>
or double。
- 问题:InputArray、OutputArray有何区别?
两者的关系,InputArray是只读的,相当于const cv::Mat&,OutputArray是可读写的,对应cv::Mat &,一般函数返回之前,如果已经分配了大小类型合适的内存就接着用,否则就create分配空间,这时候需要改写cv::Mat的地址,所以是非常量的引用。InputArray有kind(),getMat()这些方法。一般的操作前,会getMat然后用Mat来编程。可以把InputArray、OutputArray理解成一个菜篮子,只要是菜都能放到篮子里,菜在用的时候,分门别类来用。
mat.inl.hpp
inline _InputArray::_InputArray(const Mat& m)
{ init(MAT+ACCESS_READ, &m); }
inline _OutputArray::_OutputArray(const Mat& m)
{ init(FIXED_TYPE + FIXED_SIZE + MAT + ACCESS_WRITE, &m); }
inline void _InputArray::init(int _flags, const void* _obj)
{ flags = _flags; obj = (void*)_obj; }
inline
bool Mat::empty() const
{
return data == 0 || total() == 0 || dims == 0;
}
- 问题:empty()是怎么判断空Mat?
指针是0,这种情况肯定空,因为0这个地址不能分配。total或dim是0也是空。
- 问题:data怎么理解
Mat的public成员uchar* data。2维的图片,是一行一行排列的,在遍历时,应该是
for(int i=0;i<rows;i++)
.... for(int j=0;j<cols;j++)
i,j的顺序来自c语言的传统。
这里uchar只是个名义上的类型,具体访问这个指针都要转为type对应的类型。this->data通过Mat的at索引读写数据。
Mat的data内存有时不连续,这通常发生在选区之后,读内存要用到step(step>=cols),也就是一行后可能要跳过一段再是下一行。
- 问题:create()能不能避免重复分配内存?
答案:是可以的,如果this已经分配了空间,create就不重复分配;如果改了类型或通道数,重分配之前会释放,所以,放心食用。2维Mat如果col=1,data等效于1维Mat。
Mat用了引用计数。
matrix.cpp
void Mat::create(int d, const int* _sizes, int _type)
{
int i;
CV_Assert(0 <= d && d <= CV_MAX_DIM && _sizes);
_type = CV_MAT_TYPE(_type);
if( data && (d == dims || (d == 1 && dims <= 2)) && _type == type() )
{
if( d == 2 && rows == _sizes[0] && cols == _sizes[1] )
return;
for( i = 0; i < d; i++ )
if( size[i] != _sizes[i] )
break;
if( i == d && (d > 1 || size[1] == 1))
return;
}
int _sizes_backup[CV_MAX_DIM]; // #5991
if (_sizes == (this->size.p))
{
for(i = 0; i < d; i++ )
_sizes_backup[i] = _sizes[i];
_sizes = _sizes_backup;
}
release();
if( d == 0 )
return;
flags = (_type & CV_MAT_TYPE_MASK) | MAGIC_VAL;
setSize(*this, d, _sizes, 0, true);
if( total() > 0 )
{
MatAllocator *a = allocator, *a0 = getDefaultAllocator();
#ifdef HAVE_TGPU
if( !a || a == tegra::getAllocator() )
a = tegra::getAllocator(d, _sizes, _type);
#endif
if(!a)
a = a0;
try
{
u = a->allocate(dims, size, _type, 0, step.p, 0, USAGE_DEFAULT);
CV_Assert(u != 0);
}
catch (...)
{
if (a == a0)
throw;
u = a0->allocate(dims, size, _type, 0, step.p, 0, USAGE_DEFAULT);
CV_Assert(u != 0);
}
CV_Assert( step[dims-1] == (size_t)CV_ELEM_SIZE(flags) );
}
addref();
finalizeHdr(*this);
}
copy.cpp
void Mat::copyTo( OutputArray _dst ) const
{
CV_INSTRUMENT_REGION();
#ifdef HAVE_CUDA
if (_dst.isGpuMat())
{
_dst.getGpuMat().upload(*this);
return;
}
#endif
int dtype = _dst.type();
if( _dst.fixedType() && dtype != type() )
{
CV_Assert( channels() == CV_MAT_CN(dtype) );
convertTo( _dst, dtype );
return;
}
if( empty() )
{
_dst.release();
return;
}
if( _dst.isUMat() )
{
_dst.create( dims, size.p, type() );
UMat dst = _dst.getUMat();
CV_Assert(dst.u != NULL);
size_t i, sz[CV_MAX_DIM] = {0}, dstofs[CV_MAX_DIM], esz = elemSize();
CV_Assert(dims > 0 && dims < CV_MAX_DIM);
for( i = 0; i < (size_t)dims; i++ )
sz[i] = size.p[i];
sz[dims-1] *= esz;
dst.ndoffset(dstofs);
dstofs[dims-1] *= esz;
dst.u->currAllocator->upload(dst.u, data, dims, sz, dstofs, dst.step.p, step.p);
return;
}
if( dims <= 2 )
{
_dst.create( rows, cols, type() );
Mat dst = _dst.getMat();
if( data == dst.data )
return;
if( rows > 0 && cols > 0 )
{
Mat src = *this;
Size sz = getContinuousSize2D(src, dst, (int)elemSize());
CV_CheckGE(sz.width, 0, "");
const uchar* sptr = src.data;
uchar* dptr = dst.data;
#if IPP_VERSION_X100 >= 201700
CV_IPP_RUN_FAST(CV_INSTRUMENT_FUN_IPP(ippiCopy_8u_C1R_L, sptr, (int)src.step, dptr, (int)dst.step, ippiSizeL(sz.width, sz.height)) >= 0)
#endif
for (; sz.height--; sptr += src.step, dptr += dst.step)
memcpy(dptr, sptr, sz.width);
}
return;
}
_dst.create( dims, size, type() );
Mat dst = _dst.getMat();
if( data == dst.data )
return;
if( total() != 0 )
{
const Mat* arrays[] = { this, &dst };
uchar* ptrs[2] = {};
NAryMatIterator it(arrays, ptrs, 2);
size_t sz = it.size*elemSize();
for( size_t i = 0; i < it.nplanes; i++, ++it )
memcpy(ptrs[1], ptrs[0], sz);
}
}
inline
Mat Mat::clone() const
{
Mat m;
copyTo(m);
return m;
}
matrix_wrap.cpp
bool _OutputArray::fixedType() const
{
return (flags & FIXED_TYPE) == FIXED_TYPE;
}
clone和copyTo基本相同,只是CopyTo类型更广一些。新分配的内存保证是连续的。
template<typename _Tp> class Mat_ : public Mat
Mat_的行为有些像Matlab的矩阵,想要更清晰显示类型,和按位置处理数据时可以用。
这几个成员方法基本不会内存出错,但是copyTo有一个情况要注意,当dst是const Mat&时,需要事先分配好内存,大小类型要和src一致。