引言
在使用OpenCV API 的时候,我们经常遇到如下形式的函数。那么究竟为什么要使用InputArray,OutputArray等类型的参数呢?
void cv::transform(InputArray src, OutputArray dst, InputArray m)
经过我的分析,有以下三种好处:
- InputArray,OutputArray可以接收cv::Mat,std::Vector等类型的参数。
- InputArray,OutputArray可以以统一的接口来操作输入,输出参数。
- InputArray,OutputArray直接使用输入参数中的数据,没有额外的拷贝。
总结起来就是:InputArray,OutputArray代理cv::Mat,std::Vector等输入参数。
官方文档-如何使用
This is the proxy class for passing read-only input arrays into OpenCV functions.
It is defined as:
typedef const _InputArray& InputArray;
where _InputArray is a class that can be constructed from 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. It can also be constructed from a matrix expression.
Since this is mostly implementation-level class, and its interface may change in future versions, we do not describe it in details. There are a few key things, though, that should be kept in mind:
When you see in the reference manual or in OpenCV source code a function that takes InputArray, it means that you can actually pass Mat, Matx, vector<T> etc. (see above the complete list).
Optional input arguments: If some of the input arrays may be empty, pass cv::noArray() (or simply cv::Mat() as you probably did before).
The class is designed solely for passing parameters. That is, normally you should not declare class members, local and global variables of this type.
If you want to design your own function or a class method that can operate of arrays of multiple types, you can use InputArray (or OutputArray) for the respective parameters. Inside a function you should use _InputArray::getMat() method to construct a matrix header for the array (without copying data). _InputArray::kind() can be used to distinguish Mat from vector<> etc., but normally it is not needed.
Here is how you can use a function that takes InputArray :
std::vector<Point2f> vec;
// points or a circle
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));
That is, we form an STL vector containing points, and apply in-place affine transformation to the vector using the 2x3 matrix created inline as Matx<float, 2, 3> instance.
Here is how such a function can be implemented (for simplicity, we implement a very specific case of it, according to the assertion statement inside) :
void myAffineTransform(InputArray _src, OutputArray _dst, InputArray _m)
{
// get Mat headers for input arrays. This is O(1) operation,
// unless _src and/or _m are matrix expressions.
Mat src = _src.getMat(), m = _m.getMat();
CV_Assert( src.type() == CV_32FC2 && m.type() == CV_32F && m.size() == Size(3, 2) );
// [re]create the output array so that it has the proper size and type.
// In case of Mat it calls Mat::create, in case of STL vector it calls vector::resize.
_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<Point2f>(i, j);
dst.at<Point2f>(i, j) = Point2f(m.at<float>(0, 0)*pt.x +
m.at<float>(0, 1)*pt.y +
m.at<float>(0, 2),
m.at<float>(1, 0)*pt.x +
m.at<float>(1, 1)*pt.y +
m.at<float>(1, 2));
}
}
源码解读-具体实现方式
如何构造
InputArray提供多种形式的构造函数,我这里选取构造参数为std::vector的例子来说明。构造函数调用Init函数,直接将输入参数vec保存为obj。
inline void _InputArray::init(int _flags, const void* _obj)
{ flags = _flags; obj = (void*)_obj; }
inline void _InputArray::init(int _flags, const void* _obj, Size _sz)
{ flags = _flags; obj = (void*)_obj; sz = _sz; }
inline _InputArray::_InputArray(const std::vector<Mat>& vec) { init(STD_VECTOR_MAT+ACCESS_READ, &vec); }
template<typename _Tp> inline
_InputArray::_InputArray(const std::vector<_Tp>& vec)
{ init(FIXED_TYPE + STD_VECTOR + traits::Type<_Tp>::value + ACCESS_READ, &vec); }
如何使用
InputArray提供有getMat函数,通过该函数可以众多的其他类型转换为Mat来使用,这样就统一了数据的操作方式。
Mat _InputArray::getMat_(int i) const
{
_InputArray::KindFlag k = kind();
AccessFlag accessFlags = flags & ACCESS_MASK;
if( k == MAT )
{
const Mat* m = (const Mat*)obj;
if( i < 0 )
return *m;
return m->row(i);
}
if( k == UMAT )
{
const UMat* m = (const UMat*)obj;
if( i < 0 )
return m->getMat(accessFlags);
return m->getMat(accessFlags).row(i);
}
if( k == EXPR )
{
CV_Assert( i < 0 );
return (Mat)*((const MatExpr*)obj);
}
if( k == MATX || k == STD_ARRAY )
{
CV_Assert( i < 0 );
return Mat(sz, flags, obj);
}
if( k == STD_VECTOR )
{
CV_Assert( i < 0 );
int t = CV_MAT_TYPE(flags);
const std::vector<uchar>& v = *(const std::vector<uchar>*)obj;
return !v.empty() ? Mat(size(), t, (void*)&v[0]) : Mat();
}
if( k == STD_BOOL_VECTOR )
{
CV_Assert( i < 0 );
int t = CV_8U;
const std::vector<bool>& v = *(const std::vector<bool>*)obj;
int j, n = (int)v.size();
if( n == 0 )
return Mat();
Mat m(1, n, t);
uchar* dst = m.data;
for( j = 0; j < n; j++ )
dst[j] = (uchar)v[j];
return m;
}
if( k == NONE )
return Mat();
if( k == STD_VECTOR_VECTOR )
{
int t = type(i);
const std::vector<std::vector<uchar> >& vv = *(const std::vector<std::vector<uchar> >*)obj;
CV_Assert( 0 <= i && i < (int)vv.size() );
const std::vector<uchar>& v = vv[i];
return !v.empty() ? Mat(size(i), t, (void*)&v[0]) : Mat();
}
if( k == STD_VECTOR_MAT )
{
const std::vector<Mat>& v = *(const std::vector<Mat>*)obj;
CV_Assert( 0 <= i && i < (int)v.size() );
return v[i];
}
if( k == STD_ARRAY_MAT )
{
const Mat* v = (const Mat*)obj;
CV_Assert( 0 <= i && i < sz.height );
return v[i];
}
if( k == STD_VECTOR_UMAT )
{
const std::vector<UMat>& v = *(const std::vector<UMat>*)obj;
CV_Assert( 0 <= i && i < (int)v.size() );
return v[i].getMat(accessFlags);
}
if( k == OPENGL_BUFFER )
{
CV_Assert( i < 0 );
CV_Error(cv::Error::StsNotImplemented, "You should explicitly call mapHost/unmapHost methods for ogl::Buffer object");
}
if( k == CUDA_GPU_MAT )
{
CV_Assert( i < 0 );
CV_Error(cv::Error::StsNotImplemented, "You should explicitly call download method for cuda::GpuMat object");
}
if( k == CUDA_HOST_MEM )
{
CV_Assert( i < 0 );
const cuda::HostMem* cuda_mem = (const cuda::HostMem*)obj;
return cuda_mem->createMatHeader();
}
CV_Error(Error::StsNotImplemented, "Unknown/unsupported array type");
}
深入思考-有没有其他的方式来统一操作?
如果将其他类型的参数直接转换为Mat呢?比如提供形如Mat(vector &vec)的构造函数。
这样做可能会有以下几个缺点:
- Mat将会变的比较复杂
- 灵活性也比InputArray要低,比如InputArray还提供GetUMat等函数