Opencv基础学习------之InputArray和OutputArray类解析

在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_`, `Matx`, `std::vector`, `std::vector >`, `std::vector`, `std::vector >`,`UMat`, `std::vector` or `double`。它也可以由矩阵表达式构造。

关于这个类,有一些关键点要注意:

(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&vec) : flags(STD_VECTOR_MAT),obj((void*)&vec) {}

_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 /

 

templateinline_InputArray::_InputArray(constvector<_Tp>&vec)

: flags(FIXED_TYPE +STD_VECTOR + DataType<_Tp>::type),obj((void*)&vec) {}

 

templateinline_InputArray::_InputArray(constvector >& vec)

: flags(FIXED_TYPE +STD_VECTOR_VECTOR + DataType<_Tp>::type),obj((void*)&vec) {}

 

templateinline_InputArray::_InputArray(constvector >& vec)

: 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&mv) const;

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&v = *(constvector*)obj;

constvector&iv = *(constvector*)obj;

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&v = *(constvector*)obj;

 

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
版权声明:本文为博主原创文章,转载请附上博文链接!

你可能感兴趣的:(Opencv基础学习)