OpenCV学习(2): Mat 类源码

Mat类源码

  • Mat类源码
    • 引入
      • 存储物理结构
      • Array Type定义
    • Mat类的构成
      • 构造函数
      • 矩阵操作
    • 简单使用
      • 实例化
      • 元素访问
    • opencv源码
      • 类定义源码
      • 部分函数源码


引入

现时世界中我们所看到的图像经过图像摄取装置转化为数字图像后,在数字设备中存储的是图像中各个对应点的数值。早期的opencv中建立的都是C接口并且利用的是一个叫做IpIImage的结构体来存储,这就需要我们手动地来关心内存的分配,对于小型的项目这个问题不是很大,但大型项目就很难把握了。随着C++的出现,类的使用可以使得程序自动管理内存而让我们不必过分关心内存的分配,但这个对于一些仅支持C接口的嵌入式开发项目来说并不是太友好。

存储物理结构

这个Mat类代表了一个n维的单通道或者多通道数值数组。它可以用来存储实数或者负数向量和矩阵,灰度/彩色图像,体素容量(voxel volumes), 向量域,电云 , 张量, 直方图 ( 高维直方图更适合存储在 SparseMat ). 数据M依据M.step[]中的定义来进行排布, 所以元素的地址 (i0,...,iM.dims1) ( i 0 , . . . , i M . d i m s − 1 ) , 其中 0ik<M.size[k] 0 ≤ i k < M . s i z e [ k ] ,
计算方式如下:

addr(Mi0,...,iM.dims1)=M.data+M.step[0]i0+M.step[1]i1+...+M.step[M.dims1]iM.dims1 a d d r ( M i 0 , . . . , i M . d i m s − 1 ) = M . d a t a + M . s t e p [ 0 ] ∗ i 0 + M . s t e p [ 1 ] ∗ i 1 + . . . + M . s t e p [ M . d i m s − 1 ] ∗ i M . d i m s − 1

在二维数组中,上述公式可以简化为:
[addr(Mi,j)=M.data+M.step[0]i+M.step[1]j [ a d d r ( M i , j ) = M . d a t a + M . s t e p [ 0 ] ∗ i + M . s t e p [ 1 ] ∗ j

注意 M.step[i] >= M.step[i+1] (实际上 M.step[i] >=M.step[i+1]*M.size[i+1] ). 这也就意味着2维的举证是一行接一行存储的,三维的矩阵是平面接平面存储的,以此类推. M.step[M.dims-1] 是最小的且总是等于元素的尺寸 M.elemSize().

因此,Mat数据的排布与CvMat, IplImage, CvMatND 是兼容的. 这也与大部分标准开发套件和SDKs兼容,如Numpy (ndarray), Win32 (independent device bitmaps), 和其他使用steps (或 strides) 来计算像素位置的工具. 基于这种兼容性,这使得可以为用户自己分配的数据生成Mat的头部并利用opencv函数在原址上进行处理.

Array Type定义

CV_[位数][带符号与否][类型前缀]C[通道数]
CV_8UC1表示8位的单通道无符号char型数组, CV_32FC2表示一个2通道的23位浮点型数组

Mat类的构成

构造函数

Mat的构造函数用来实例化一个mat对象,因此需要给出相应的参数完成初始化就可以了。mat类给出的构造函数输入参数大致可以分为以下几种(各种给出的具体方式可能不同):矩阵尺寸、类型、矩阵数据值、步长。
- 矩阵尺寸给出的是矩阵的大小(行列数),通常的方式有枚举形式([rows,cols])、向量形式(vector& size)、数组指针(int * size);
- 类型就是之前定义的Array Type;
- 矩阵数据值通常是一个指向矩阵的指针(int* data);
- 步长也通常是一个数组指针(size_t * step)。

矩阵操作

提取
提取操作是指从矩阵中取出部分数据构成新的矩阵,但这些操作并不会执行拷贝操作,也就是复杂度都是O(1),只是单纯地生成一个的Header来进行“标记”。提取的范围可以通过不同的形式给出行列起止索引甚至是对角线,如opencv自己定义的range类型,Rect类型或者直接给出。利用这个操作可以简单地执行图像的ROI提取。
拷贝
clone()函数是对矩阵包含数据在内的全拷贝且给出的矩阵副本是连续的。
copyTo(params)会在拷贝前调用creat()函数,但该函数不能处理源矩阵与目的矩阵有重叠的情况。
赋值
可以利用zeros、ones、eye等matlab风格的方法来创建特殊矩阵。可以使用赋值运算符“=”来对矩阵的所有或部分元素进行赋值操作,也可以使用setTo(*params*)来进行赋值操作。
数学运算
mat类提供了翻转矩阵(reshape)、element-wise乘除、点积(dot-product)、叉积(cross-product)、转置、求逆等数学运算。

简单使用

Mat类是用于保存图像及其他矩阵的数据结构,默认情况下尺寸为0。需要记住的是:Mat是一个类,可以自动分配和释放内存。Mat中包含两个数据部分:矩阵头(包含矩阵的大小,存储的方式,存储地址等)和一个指向像素值矩阵的指针。

为了减少运算的时间,在对Mat进行拷贝时,仅拷贝矩阵头和指针,而并不会拷贝矩阵。下面几个mat对象都指向的是同一个数据矩阵,但它们的矩阵头不同。

实例化

有许多的方法可以创建Mat对象.最常用的方法如下:

  • 使用 create(nrows, ncols, type) 方法或者类似的 Mat(nrows, ncols, type[, fillValue])构造器.那么一个指定尺寸和类型的新数组就被分配了.类型与cvCreateMat方法中的含义相同.
    // make a 7x7 complex matrix filled with 1+3j.
    Mat M(7,7,CV_32FC2,Scalar(1,3));
    // and now turn M to a 100x60 15-channel 8-bit matrix.
    // The old content will be deallocated
    M.create(100,60,CV_8UC(15));

如之前所述, create() 当形式或者类型与指定的数组不同时会分配一个新的数组.

  • 创建一个多维数组:
    // create a 100x100x100 8-bit array
    int sz[] = {100, 100, 100};
    Mat bigCube(3, sz, CV_8U, Scalar::all(0));

当传递一个维度=1给Mat构造器时会创建一个列数为1的二维数组.因此, Mat::dims总是大于1 (当为空数组时为0).

  • 使用复制构造器或者分配符(右值赋值?).如之前所述,这个array assignment 是一个O(1) 的操作因为它只是复制了头部以及增加了引用计数
    . Mat::clone()方法能够获得整个数组的副本.

  • 为另一个数组的部分设置一个header.它可以使单独的部分行或者列,甚至是对角线.
    这种操作也是 O(1)因为新的header引用了同样的数据.用户可以使用这个特性来改变矩阵的部分内容,如:

    // add the 5-th row, multiplied by 3 to the 3rd row
    M.row(3) = M.row(3) + M.row(5)*3;
    // now copy the 7-th column to the 1-st column
    // M.col(1) = M.col(7); // this will not work
    Mat M1 = M.col(1);
    M.col(7).copyTo(M1);
    // create a new 320x240 image
    Mat img(Size(320,240),CV_8UC3);
    // select a ROI
    Mat roi(img, Rect(10,10,100,100));
    // fill the ROI with (0,255,0) (which is green in RGB space);
    // the original 320x240 image will be modified
    roi = Scalar(0,255,0);

由于存在额外的 datastart和dataend成员,这就可以在主容器中计算相对位置,如使用locateROI():

    Mat A = Mat::eye(10, 10, CV_32S);
    // extracts A columns, 1 (inclusive) to 3 (exclusive).
    Mat B = A(Range::all(), Range(1, 3));
    // extracts B rows, 5 (inclusive) to 9 (exclusive).
    // that is, C \~ A(Range(5, 9), Range(1, 3))
    Mat C = B(Range(5, 9), Range::all());
    Size size; Point ofs;
    C.locateROI(size, ofs);
    // size will be (width=10,height=10) and the ofs will be (x=1, y=5)

对于整个矩阵,如果需要深拷贝,使用 clone() 来提取子矩阵.

  • 为用户自分配的数据生成一个header.可以如下使用:
        void process_video_frame(const unsigned char* pixels,
                                 int width, int height, int step)
        {
            Mat img(height, width, CV_8UC3, pixels, step);
            GaussianBlur(img, img, Size(7,7), 1.5, 1.5);
        }

   \\ Quickly initialize small matrices and/or get a super-fast element access.

        double m[3][3] = {{a, b, c}, {d, e, f}, {g, h, i}};
        Mat M = Mat(3, 3, CV_64F, m).inv();
  • 使用MATLAB-style 数组初始化器, zeros(), ones(), eye(), 如:
    // create a double-precision identity matrix and add it to M.
    M += Mat::eye(M.rows, M.cols, CV_64F);
  • 使用逗号分隔的初始化器:
    //create a 3x3 double-precision identity matrix
    Mat M = (Mat_<double>(3,3) << 1, 0, 0, 0, 1, 0, 0, 0, 1);

使用这种方法时,用户首先调用一个对应参数的构造器,然后将用户可以在<< operator后面接上逗号分隔的表达式如常数、变量、表达式等. 另外,额外括号需要注意添加以防发生编译错误.

一旦数组被创建,会自动地通过引用计数机制进行管理.如果数组头部建立在用户自分配数据的顶部,用户需要自己处理.如果没有指针指向它则会被deallocated.如果想在析构器销毁头部指向的数据之前释放内存,则可以调用 Mat::release().

Mat A, C; // creates just the header parts  

A = imread(argv[1], IMREAD_COLOR); // here we'll know the method used (allocate matrix)  
Mat B(A); // Use the copy constructor  
C = A; // Assignment operator  

对于这种共享同一个数据矩阵的各mat对象来说,它(数据矩阵)的销毁任务通常由最后一个使用它的对象来负责。其中的过程类似C++中的智能指针。计数的增加或者较少和矩阵头是否复制有关。当然,有时候我们需要对数据矩阵进行拷贝(也就是重新开辟空间存储图像图像数据而不单单是信息头的信息),这可以通过以下方法来实现:

Mat F = A.clone();  
Mat G;  
A.copyTo(G);  

mat类存储像素的值,包括所使用的颜色空间和数据类型。不同的颜色空间各有各的优缺点,常用的颜色空间有:RGB、HSV、HLS、YCrCb、CIElab。

元素访问

学习的下一步重点在如元素的访问. 之前讲述了如何计算元素的地址.通常情况下, 并不需要在代码中直接使用公式.如果用户知道元素的类型 (可以使用 Mat::type()查询 ), 可以使用 Mij M i j 来访问一个二维时更要注意的元素:

    M.at<double>(i,j) += 1.f;

其中假定 M是一个双精度浮点型数组。依据不同维度方法可能有所不同 .如需处理二维数组的整行,最有效的方法是获得行的指针然后使用[]操作符:

    // compute sum of positive matrix elements
    // (assuming that M is a double-precision matrix)
    double sum=0;
    for(int i = 0; i < M.rows; i++)
    {
        const double* Mi = M.ptr<double>(i);
        for(int j = 0; j < M.cols; j++)
            sum += std::max(Mi[j], 0.);
    }

针对 element-wise操作. 需要检测 input/output 数组是否连续, 也就是说, 在行的后面没有空隔.如果连续则将其当做一行数据来处理:

    // compute the sum of positive matrix elements, optimized variant
    double sum=0;
    int cols = M.cols, rows = M.rows;
    if(M.isContinuous())
    {
        cols *= rows;
        rows = 1;
    }
    for(int i = 0; i < rows; i++)
    {
        const double* Mi = M.ptr<double>(i);
        for(int j = 0; j < cols; j++)
            sum += std::max(Mi[j], 0.);
    }

对于连续矩阵,外循环只执行一次. 最后,可以使用STL-style 的迭代器,可以非常明智地跳过两个连续行之间的间隔:

    // compute sum of positive matrix elements, iterator-based variant
    double sum=0;
    MatConstIterator_<double> it = M.begin<double>(), it_end = M.end<double>();
    for(; it != it_end; ++it)
        sum += std::max(*it, 0.);

矩阵迭代器是随机存储迭代器,可以传递给任何STL算法,包括std::sort().

opencv源码

类定义源码

class CV_EXPORTS Mat
{
public:
    /**有许多版本的构造器来创建一个矩阵.在 AutomaticAllocation中,通常默认构造器就可以了, OpenCV 函数也会自动分配合适的矩阵.被分配的矩阵可以传递给其他矩阵或者矩阵表达式, 或者可以使用Mat::create分配内存.在之前的例子中, 旧的内容将不会被引用.
    */
    Mat();
    Mat(int rows, int cols, int type);

    /* @overload  @param size 2D 数组尺寸: Size(cols, rows) .  Size()中行列数相反.*/
    Mat(Size size, int type);

    /* @overload @param s s是可选参数用来赋初值. */
    Mat(int rows, int cols, int type, const Scalar& s);

    Mat(Size size, int type, const Scalar& s);

    /** @overload
        @param ndims 数组维度.
        @param sizes  指明一个n通道的数组的大小(每个通道).
    */
    Mat(int ndims, const int* sizes, int type);

    Mat(const std::vector<int>& sizes, int type);

    /** @overload
        @param s 一个可选的参数用来初始化矩阵每个元素。可以实例化对象之后使用赋值运算符来设置所有的元素的值。 Mat::operator=(const Scalar& value) .
    */
    Mat(int ndims, const int* sizes, int type, const Scalar& s);

    Mat(const std::vector<int>& sizes, int type, const Scalar& s);

    /** @overload
        @param m 赋值构造函数,并不发生拷贝。 其中,对象的头部指向 m 的数据或者其子数据,同时引用指针计数也会增加。 所以,当用户使用这个构造函数进行构造时,改变该矩阵同时也会改变用来构建该矩阵的源矩阵。如果用户想拥有独立的内存空间用来拷贝新矩阵,可以使用 Mat::clone() .
    */
    Mat(const Mat& m);

    /** @overload
        @param data 指向用户数据的指针. 矩阵构造器并不会利用数据(data)和步长(step)来为数据分配内存。它仅仅是利用初始化头部(header)来指向指明的数据。
        @param step 每个矩阵行所占有的字节数。这个值应该包含行尾的填充字节.如果缺省该参数,则默认没有填充字节且实际的步长由cols*elemSize()决定. 
    */
    Mat(int rows, int cols, int type, void* data, size_t step=AUTO_STEP);

    Mat(Size size, int type, void* data, size_t step=AUTO_STEP);

    Mat(int ndims, const int* sizes, int type, void* data, const size_t* steps=0);

    Mat(const std::vector<int>& sizes, int type, void* data, const size_t* steps=0);

    /** @overload
        @param m矩阵的行范围,范围左闭右开。 可以使用Range::all() 表示所有行.
        @param colRange m矩阵的列范围,范围左闭右开。 可以使用Range::all() 表示所有列.
    */
    Mat(const Mat& m, const Range& rowRange, const Range& colRange=Range::all());

    /** @overload
        @param roi 兴趣区域.
    */
    Mat(const Mat& m, const Rect& roi);

    /** @overload
        @param ranges Array of selected ranges of m along each dimensionality.
    */
    Mat(const Mat& m, const Range* ranges);

    /** @overload
        @param ranges 沿着每个维度所选择的范围数组.
    */
    Mat(const Mat& m, const std::vector& ranges);

    /** @overload
        @param copyData 拷贝标志。当开启拷贝模式时,会启用引用计数机制;当为共享模式时,引用计数为0且除非矩阵被销毁否则数据不会被释放。
    */
    template explicit Mat(const std::vector<_Tp>& vec, bool copyData=false);

#ifdef CV_CXX11
    /** @overload
    */
    template::value>::type>
    explicit Mat(const std::initializer_list<_Tp> list);

    /** @overload
    */
    template explicit Mat(const std::initializer_list<int> sizes, const std::initializer_list<_Tp> list);
#endif

#ifdef CV_CXX_STD_ARRAY
    /** @overload
    */
    template explicit Mat(const std::array<_Tp, _Nm>& arr, bool copyData=false);
#endif

    /** @overload
    */
    templateint n> explicit Mat(const Vec<_Tp, n>& vec, bool copyData=true);

    /** @overload
    */
    templateint m, int n> explicit Mat(const Matx<_Tp, m, n>& mtx, bool copyData=true);

    /** @overload
    */
    template explicit Mat(const Point_<_Tp>& pt, bool copyData=true);

    /** @overload
    */
    template explicit Mat(const Point3_<_Tp>& pt, bool copyData=true);

    /** @overload
    */
    template explicit Mat(const MatCommaInitializer_<_Tp>& commaInitializer);

    //! 从GpuMat下载数据到本地
    explicit Mat(const cuda::GpuMat& m);

    //! destructor - calls release()
    ~Mat();

    //! destructor - calls release()
    ~Mat();

    /** @brief 赋值运算符

    有多个版本的赋值运算符. 
    @param m 已分配的右值矩阵. 矩阵赋值是O(1)操作. 数据并不会被复制,而是共享与增加引用计数. 
             在赋新数据之前,旧数据会调用Mat::release来消除引用.
     */
    Mat& operator = (const Mat& m);

    /** @overload
        @param expr 已分配的矩阵表达式对象. 与第一种形式相反的是,在第二种行驶中如果已经分配的矩阵有着
                和矩阵表达式的结果相匹配的尺寸和类型. 实函数(real function)可以自动扩展矩阵表达式 . 
                例如 , C=A+B 会被扩展成 add(A, B, C), 且 add 会自动为  C 再分配.
    */
    Mat& operator = (const MatExpr& expr);

    //! 从Mat恢复 UMat  
    UMat getUMat(int accessFlags, UMatUsageFlags usageFlags = USAGE_DEFAULT) const;

    /** @brief 为指定的矩阵行创建header.

    为指定的矩阵行创建header并返回它,是一个O(1)操作,忽略矩阵的大小,与原数据共享内存.
     下面是一个经典矩阵操作的例子,matrix_axpy被LU和许多其他算法使用:
    @code
        inline void matrix_axpy(Mat& A, int i, int j, double alpha)
        {
            A.row(i) += A.row(j)*alpha;
        }
    @endcode
    @注意在当前的代码实现中,下列代码并不可行:
    @code
        Mat A;
        ...
        A.row(i) = A.row(j); // will not work
    @endcode
    这是因为 A.row(i)形成了一个临时的header用于之后赋值给其他header.
    记住这些操作都是 O(1), 也就是说没有数据被拷贝. 因此将j列的值拷贝给i列是不可行的.
    为了达到这个目的, 用户可以转化这个赋值为 expression 或者使用Mat::copyTo method:
    @code
        Mat A;
        ...
        // works, but looks a bit obscure.
        A.row(i) = A.row(j) + 0;
        // this is a bit longer, but the recommended method.
        A.row(j).copyTo(A.row(i));
    @endcode

    @param y是一个从0开始的索引.
     */
    Mat row(int y) const;

    /** @brief 为指定的列创建header.
     */
    Mat col(int x) const;

    /** @brief 为指定的跨行创建header.
        @param startrow 包括该值在内的起始索引.
        @param endrow   不包括该值在内的结束索引.
     */
    Mat rowRange(int startrow, int endrow) const;

    /** @overload
        @param r 包含起始和结束的范围结构类型. 
    */
    Mat rowRange(const Range& r) const;

    /** @brief 为指定的跨列创建header.*/
    Mat colRange(int startcol, int endcol) const;

    Mat colRange(const Range& r) const;

    /** @brief 从矩阵中提取对角线

    为矩阵指定的对角线创建新的header. 新的矩阵看作一个类似于Mat::row and Mat::col的单列矩阵.
    @param d 对角线的索引:
    - `d=0` 主对角线.
    - `d<0` 下半部分的对角线. 例如, d=-1 主对角线下移一步对应的对角线.
    - `d>0` 上半部分的对角线. 例如, d= 1 主对角线上移一步对应的对角线.
    例如:
    @code
        Mat m = (Mat_(3,3) <<
                    1,2,3,
                    4,5,6,
                    7,8,9);
        Mat d0 = m.diag(0);
        Mat d1 = m.diag(1);
        Mat d_1 = m.diag(-1);
    @endcode
    结果如下:
    @code
     d0 =
       [1;
        5;
        9]
     d1 =
       [2;
        6]
     d_1 =
       [4;
        8]
    @endcode
     */
    Mat diag(int d=0) const;

    /** @brief 创建一个对角矩阵

     根据指定的主对角线创建方对角矩阵.
    @param d  代表主对角线的一维矩阵.
     */
    static Mat diag(const Mat& d);

    /** @brief 对矩阵包含数据在内的全拷贝.
         最开始的 step[] 不会考虑在内.因此矩阵副本是一个连续的,占  total()*elemSize() 字节.
     */
    Mat clone() const;

    /** @brief 拷贝矩阵.

    拷贝数据之前,该方法会调用:
    @code
        m.create(this->size(), this->type());
    @endcode
    所以如果需要的话目标矩阵会被重新分配内存. 尽管 m.copyTo(m); 很完美,这个还函数并不能处理源矩阵与目的矩阵部分重叠的情况.
    新分配内存的矩阵会进行0初始化(全部初始化为0).
    @param m 目的矩阵. 如果大小和类型不匹配则重新分配内存.
     */
    void copyTo( OutputArray m ) const;

    /** @overload
    @param m 目的矩阵. 
    @param mask 操作掩码. 如果非0则表示需要拷贝. 掩码类型可与为 CV_8U 且能有1个或者多个通道.
    */
    void copyTo( OutputArray m, InputArray mask ) const;

    /** @brief 以带缩放的形式转化一个矩阵为另一种类型.

    The method converts source pixel values to the target data type. saturate_cast\<\> is applied at
    the end to avoid possible overflows:

    \f[m(x,y) = saturate \_ cast( \alpha (*this)(x,y) +  \beta )\f]
    @param m output matrix; if it does not have a proper size or type before the operation, it is
    reallocated.
    @param rtype desired output matrix type or, rather, the depth since the number of channels are the
    same as the input has; if rtype is negative, the output matrix will have the same type as the input.
    @param alpha optional scale factor.
    @param beta optional delta added to the scaled values.
     */
    void convertTo( OutputArray m, int rtype, double alpha=1, double beta=0 ) const;

    /** @brief 提供一种函数形式的convertTo.

    一个内部被 @ref MatrixExpressions engine调用的方法.
    @param m 目的矩阵.
    @param type 希望目的矩阵的深度( -1 表示与源矩阵相同).
     */
    void assignTo( Mat& m, int type=-1 ) const;

    /** @brief 将所有或一些元素的值设置为某个标量.
    @param s 赋值标量被转换为确切的矩阵形式.
    */
    Mat& operator = (const Scalar& s);

    /** @brief 将所有或一些元素的值设置为某个标量.

    一个 Mat::operator=(const Scalar& s) 运算图的高级变体.
    @param value 赋值标量被转换为确切的矩阵形式.
    @param mask  操作掩码.  非0表示元素需要被复制.  掩码可以是 CV_8U 类型且可以拥有多个通道
     */
    Mat& setTo(InputArray value, InputArray mask=noArray());

    /** @brief 在不拷贝的情况下改变2D矩阵的形状 和/或 通道的数目 
    为元素创建一个新的header. 新的矩阵拥有不同的大小 且/或 不同的通道. 可能的组合方式有:
    -   没有新元素加入或者没有元素被剔除. 那么,rows*cols*channels() 的积前后是相同的.
    -   没有数据被拷贝. 也就是说这是一个O(1)的操作. 那么, 如果用户改变行数或者用户的其他
        操作改变了行的索引,矩阵必定连续. 参考 Mat::isContinuous.

    例如, 如果有一个存储 3D 点的STL向量,用户希望以3xN的矩阵来表达这些点, 方法如下:
    @code
        std::vector vec;
        ...
        Mat pointMat = Mat(vec). // convert vector to Mat, O(1) operation
                          reshape(1). // make Nx3 1-channel matrix out of Nx1 3-channel.
                                      // Also, an O(1) operation
                             t(); // finally, transpose the Nx3 matrix.
                                  // This involves copying all the elements
    @endcode
    @param cn 新通道数. 为0表示不改变.
    @param rows 新行数. 为0表示不改变.
     */
    Mat reshape(int cn, int rows=0) const;

    /** @overload */
    Mat reshape(int cn, int newndims, const int* newsz) const;

    /** @overload */
    Mat reshape(int cn, const std::vector<int>& newshape) const;

    /** @brief 转置矩阵.

    The method performs matrix transposition by means of matrix expressions. It does not perform the
    actual transposition but returns a temporary matrix transposition object that can be further used as
    a part of more complex matrix expressions or can be assigned to a matrix:
    @code
        Mat A1 = A + Mat::eye(A.size(), A.type())*lambda;
        Mat C = A1.t()*A1; // compute (A + lambda*I)^t * (A + lamda*I)
    @endcode
     */
    MatExpr t() const;

    /** @brief 逆矩阵.

    The method performs a matrix inversion by means of matrix expressions. This means that a temporary
    matrix inversion object is returned by the method and can be used further as a part of more complex
    matrix expressions or can be assigned to a matrix.
    @param method Matrix inversion method. One of cv::DecompTypes
     */
    MatExpr inv(int method=DECOMP_LU) const;

    /** @brief 基于 element-wise 的乘法或除法.
    Example:
    @code
        Mat C = A.mul(5/B); // equivalent to divide(A, B, C, 5)
    @endcode
    @param m Another array of the same type and the same size as \*this, or a matrix expression.
    @param scale Optional scale factor.
     */
    MatExpr mul(InputArray m, double scale=1) const;

    /** @brief 计算3-element向量的cross-product.

    The method computes a cross-product of two 3-element vectors. The vectors must be 3-element
    floating-point vectors of the same shape and size. The result is another 3-element vector of the
    same shape and type as operands.
    @param m Another cross-product operand.
     */
    Mat cross(InputArray m) const;

    /** @brief 计算两个向量的dot-product.

    The method computes a dot-product of two matrices. If the matrices are not single-column or
    single-row vectors, the top-to-bottom left-to-right scan ordering is used to treat them as 1D
    vectors. The vectors must have the same size and type. If the matrices have more than one channel,
    the dot products from all the channels are summed together.
    @param m another dot-product operand.
     */
    double dot(InputArray m) const;

    /** @brief 返回指定大小和类型的零矩阵.

    The method returns a Matlab-style zero array initializer. It can be used to quickly form a constant
    array as a function parameter, part of a matrix expression, or as a matrix initializer. :
    @code
        Mat A;
        A = Mat::zeros(3, 3, CV_32F);
    @endcode
    In the example above, a new matrix is allocated only if A is not a 3x3 floating-point matrix.
    Otherwise, the existing matrix A is filled with zeros.
    @param rows Number of rows.
    @param cols Number of columns.
    @param type Created matrix type.
     */
    static MatExpr zeros(int rows, int cols, int type);

    /** @overload
    @param size Alternative to the matrix size specification Size(cols, rows) .
    @param type Created matrix type.
    */
    static MatExpr zeros(Size size, int type);

    /** @overload
    @param ndims Array dimensionality.
    @param sz Array of integers specifying the array shape.
    @param type Created matrix type.
    */
    static MatExpr zeros(int ndims, const int* sz, int type);

    /** @brief 返回指定大小和类型且元素值全为1的矩阵.

    The method returns a Matlab-style 1's array initializer, similarly to Mat::zeros. Note that using
    this method you can initialize an array with an arbitrary value, using the following Matlab idiom:
    @code
        Mat A = Mat::ones(100, 100, CV_8U)*3; // make 100x100 matrix filled with 3.
    @endcode
    The above operation does not form a 100x100 matrix of 1's and then multiply it by 3. Instead, it
    just remembers the scale factor (3 in this case) and use it when actually invoking the matrix
    initializer.
    @param rows Number of rows.
    @param cols Number of columns.
    @param type Created matrix type.
     */
    static MatExpr ones(int rows, int cols, int type);

    /** @overload
    @param size Alternative to the matrix size specification Size(cols, rows) .
    @param type Created matrix type.
    */
    static MatExpr ones(Size size, int type);

    /** @overload
    @param ndims Array dimensionality.
    @param sz Array of integers specifying the array shape.
    @param type Created matrix type.
    */
    static MatExpr ones(int ndims, const int* sz, int type);

    /** @brief 返回指定大小和类型的单位矩阵.

    The method returns a Matlab-style identity matrix initializer, similarly to Mat::zeros. Similarly to
    Mat::ones, you can use a scale operation to create a scaled identity matrix efficiently:
    @code
        // make a 4x4 diagonal matrix with 0.1's on the diagonal.
        Mat A = Mat::eye(4, 4, CV_32F)*0.1;
    @endcode
    @param rows Number of rows.
    @param cols Number of columns.
    @param type Created matrix type.
     */
    static MatExpr eye(int rows, int cols, int type);

    /** @overload
    @param size Alternative matrix size specification as Size(cols, rows) .
    @param type Created matrix type.
    */
    static MatExpr eye(Size size, int type);

    /** @brief 分配新的数组数据.

    This is one of the key Mat methods. Most new-style OpenCV functions and methods that produce arrays
    call this method for each output array. The method uses the following algorithm:

    -# If the current array shape and the type match the new ones, return immediately. Otherwise,
       de-reference the previous data by calling Mat::release.
    -# Initialize the new header.
    -# Allocate the new data of total()\*elemSize() bytes.
    -# Allocate the new, associated with the data, reference counter and set it to 1.

    Such a scheme makes the memory management robust and efficient at the same time and helps avoid
    extra typing for you. This means that usually there is no need to explicitly allocate output arrays.
    That is, instead of writing:
    @code
        Mat color;
        ...
        Mat gray(color.rows, color.cols, color.depth());
        cvtColor(color, gray, COLOR_BGR2GRAY);
    @endcode
    you can simply write:
    @code
        Mat color;
        ...
        Mat gray;
        cvtColor(color, gray, COLOR_BGR2GRAY);
    @endcode
    because cvtColor, as well as the most of OpenCV functions, calls Mat::create() for the output array
    internally.
    @param rows New number of rows.
    @param cols New number of columns.
    @param type New matrix type.
     */
    void create(int rows, int cols, int type);

    /** @overload
    @param size Alternative new matrix size specification: Size(cols, rows)
    @param type New matrix type.
    */
    void create(Size size, int type);

    /** @overload
    @param ndims New array dimensionality.
    @param sizes Array of integers specifying a new array shape.
    @param type New matrix type.
    */
    void create(int ndims, const int* sizes, int type);

    /** @overload
    @param sizes Array of integers specifying a new array shape.
    @param type New matrix type.
    */
    void create(const std::vector<int>& sizes, int type);

    /** @brief 增加引用计数.

    The method increments the reference counter associated with the matrix data. If the matrix header
    points to an external data set (see Mat::Mat ), the reference counter is NULL, and the method has no
    effect in this case. Normally, to avoid memory leaks, the method should not be called explicitly. It
    is called implicitly by the matrix assignment operator. The reference counter increment is an atomic
    operation on the platforms that support it. Thus, it is safe to operate on the same matrices
    asynchronously in different threads.
     */
    void addref();

    /** @brief 减少引用计数并销毁数组数据.

    The method decrements the reference counter associated with the matrix data. When the reference
    counter reaches 0, the matrix data is deallocated and the data and the reference counter pointers
    are set to NULL's. If the matrix header points to an external data set (see Mat::Mat ), the
    reference counter is NULL, and the method has no effect in this case.

    This method can be called manually to force the matrix data deallocation. But since this method is
    automatically called in the destructor, or by any other method that changes the data pointer, it is
    usually not needed. The reference counter decrement and check for 0 is an atomic operation on the
    platforms that support it. Thus, it is safe to operate on the same matrices asynchronously in
    different threads.
     */
    void release();

    //! (内部使用函数), 可以使用 'release' 方法替代; 销毁数据释放内存
    void deallocate();
    //! (内部使用函数); 可能重新分配 _size, _step 的数组
    void copySize(const Mat& m);

    /** @brief 保留确定行数的空间 

    The method reserves space for sz rows. If the matrix already has enough space to store sz rows,
    nothing happens. If the matrix is reallocated, the first Mat::rows rows are preserved. The method
    emulates the corresponding method of the STL vector class.
    @param sz Number of rows.
     */
    void reserve(size_t sz);

    /** @brief 保留确定字节的空间.

    The method reserves space for sz bytes. If the matrix already has enough space to store sz bytes,
    nothing happens. If matrix has to be reallocated its previous content could be lost.
    @param sz Number of bytes.
    */
    void reserveBuffer(size_t sz);

    /** @brief 改变矩阵的行数.

    The methods change the number of matrix rows. If the matrix is reallocated, the first
    min(Mat::rows, sz) rows are preserved. The methods emulate the corresponding methods of the STL
    vector class.
    @param sz New number of rows.
     */
    void resize(size_t sz);

    /** @overload
    @param sz New number of rows.
    @param s Value assigned to the newly added elements.
     */
    void resize(size_t sz, const Scalar& s);

    //! (内部函数)
    void push_back_(const void* elem);

    /** @brief 在矩阵的底部添加元素.

    The methods add one or more elements to the bottom of the matrix. They emulate the corresponding
    method of the STL vector class. When elem is Mat , its type and the number of columns must be the
    same as in the container matrix.
    @param elem Added element(s).
     */
    template void push_back(const _Tp& elem);

    /** @overload
    @param elem Added element(s).
    */
    template void push_back(const Mat_<_Tp>& elem);

    /** @overload
    @param elem Added element(s).
    */
    template void push_back(const std::vector<_Tp>& elem);

    /** @overload
    @param m Added line(s).
    */
    void push_back(const Mat& m);

    /** @brief 在矩阵的底部删除元素.

    The method removes one or more rows from the bottom of the matrix.
    @param nelems Number of removed rows. If it is greater than the total number of rows, an exception
    is thrown.
     */
    void pop_back(size_t nelems=1);

    /** @brief 在源矩阵中找出矩阵header.

    After you extracted a submatrix from a matrix using Mat::row, Mat::col, Mat::rowRange,
    Mat::colRange, and others, the resultant submatrix points just to the part of the original big
    matrix. However, each submatrix contains information (represented by datastart and dataend
    fields) that helps reconstruct the original matrix size and the position of the extracted
    submatrix within the original matrix. The method locateROI does exactly that.
    @param wholeSize Output parameter that contains the size of the whole matrix containing *this*
    as a part.
    @param ofs Output parameter that contains an offset of *this* inside the whole matrix.
     */
    void locateROI( Size& wholeSize, Point& ofs ) const;

    /** @brief 在源矩阵中调整子矩阵的大小和位置.

    The method is complimentary to Mat::locateROI . The typical use of these functions is to determine
    the submatrix position within the parent matrix and then shift the position somehow. Typically, it
    can be required for filtering operations when pixels outside of the ROI should be taken into
    account. When all the method parameters are positive, the ROI needs to grow in all directions by the
    specified amount, for example:
    @code
        A.adjustROI(2, 2, 2, 2);
    @endcode
    In this example, the matrix size is increased by 4 elements in each direction. The matrix is shifted
    by 2 elements to the left and 2 elements up, which brings in all the necessary pixels for the
    filtering with the 5x5 kernel.

    adjustROI forces the adjusted ROI to be inside of the parent matrix that is boundaries of the
    adjusted ROI are constrained by boundaries of the parent matrix. For example, if the submatrix A is
    located in the first row of a parent matrix and you called A.adjustROI(2, 2, 2, 2) then A will not
    be increased in the upward direction.

    The function is used internally by the OpenCV filtering functions, like filter2D , morphological
    operations, and so on.
    @param dtop Shift of the top submatrix boundary upwards.
    @param dbottom Shift of the bottom submatrix boundary downwards.
    @param dleft Shift of the left submatrix boundary to the left.
    @param dright Shift of the right submatrix boundary to the right.
    @sa copyMakeBorder
     */
    Mat& adjustROI( int dtop, int dbottom, int dleft, int dright );

    /** @brief 提取一个方形子矩阵.

    The operators make a new header for the specified sub-array of \*this . They are the most
    generalized forms of Mat::row, Mat::col, Mat::rowRange, and Mat::colRange . For example,
    `A(Range(0, 10), Range::all())` is equivalent to `A.rowRange(0, 10)`. Similarly to all of the above,
    the operators are O(1) operations, that is, no matrix data is copied.
    @param rowRange Start and end row of the extracted submatrix. The upper boundary is not included. To
    select all the rows, use Range::all().
    @param colRange Start and end column of the extracted submatrix. The upper boundary is not included.
    To select all the columns, use Range::all().
     */
    Mat operator()( Range rowRange, Range colRange ) const;

    /** @overload
    @param roi Extracted submatrix specified as a rectangle.
    */
    Mat operator()( const Rect& roi ) const;

    /** @overload
    @param ranges Array of selected ranges along each array dimension.
    */
    Mat operator()( const Range* ranges ) const;

    /** @overload
    @param ranges Array of selected ranges along each array dimension.
    */
    Mat operator()(const std::vector& ranges) const;

    // //! converts header to CvMat; no data is copied
    // operator CvMat() const;
    // //! converts header to CvMatND; no data is copied
    // operator CvMatND() const;
    // //! converts header to IplImage; no data is copied
    // operator IplImage() const;

    template operator std::vector<_Tp>() const;
    templateint n> operator Vec<_Tp, n>() const;
    templateint m, int n> operator Matx<_Tp, m, n>() const;

#ifdef CV_CXX_STD_ARRAY
    template operator std::array<_Tp, _Nm>() const;
#endif

    /** @brief 查看矩阵是否连续.

    The method returns true if the matrix elements are stored continuously without gaps at the end of
    each row. Otherwise, it returns false. Obviously, 1x1 or 1xN matrices are always continuous.
    Matrices created with Mat::create are always continuous. But if you extract a part of the matrix
    using Mat::col, Mat::diag, and so on, or constructed a matrix header for externally allocated data,
    such matrices may no longer have this property.

    The continuity flag is stored as a bit in the Mat::flags field and is computed automatically when
    you construct a matrix header. Thus, the continuity check is a very fast operation, though
    theoretically it could be done as follows:
    @code
        // alternative implementation of Mat::isContinuous()
        bool myCheckMatContinuity(const Mat& m)
        {
            //return (m.flags & Mat::CONTINUOUS_FLAG) != 0;
            return m.rows == 1 || m.step == m.cols*m.elemSize();
        }
    @endcode
    The method is used in quite a few of OpenCV functions. The point is that element-wise operations
    (such as arithmetic and logical operations, math functions, alpha blending, color space
    transformations, and others) do not depend on the image geometry. Thus, if all the input and output
    arrays are continuous, the functions can process them as very long single-row vectors. The example
    below illustrates how an alpha-blending function can be implemented:
    @code
        template
        void alphaBlendRGBA(const Mat& src1, const Mat& src2, Mat& dst)
        {
            const float alpha_scale = (float)std::numeric_limits::max(),
                        inv_scale = 1.f/alpha_scale;

            CV_Assert( src1.type() == src2.type() &&
                       src1.type() == CV_MAKETYPE(traits::Depth::value, 4) &&
                       src1.size() == src2.size());
            Size size = src1.size();
            dst.create(size, src1.type());

            // here is the idiom: check the arrays for continuity and,
            // if this is the case,
            // treat the arrays as 1D vectors
            if( src1.isContinuous() && src2.isContinuous() && dst.isContinuous() )
            {
                size.width *= size.height;
                size.height = 1;
            }
            size.width *= 4;

            for( int i = 0; i < size.height; i++ )
            {
                // when the arrays are continuous,
                // the outer loop is executed only once
                const T* ptr1 = src1.ptr(i);
                const T* ptr2 = src2.ptr(i);
                T* dptr = dst.ptr(i);

                for( int j = 0; j < size.width; j += 4 )
                {
                    float alpha = ptr1[j+3]*inv_scale, beta = ptr2[j+3]*inv_scale;
                    dptr[j] = saturate_cast(ptr1[j]*alpha + ptr2[j]*beta);
                    dptr[j+1] = saturate_cast(ptr1[j+1]*alpha + ptr2[j+1]*beta);
                    dptr[j+2] = saturate_cast(ptr1[j+2]*alpha + ptr2[j+2]*beta);
                    dptr[j+3] = saturate_cast((1 - (1-alpha)*(1-beta))*alpha_scale);
                }
            }
        }
    @endcode
    This approach, while being very simple, can boost the performance of a simple element-operation by
    10-20 percents, especially if the image is rather small and the operation is quite simple.

    Another OpenCV idiom in this function, a call of Mat::create for the destination array, that
    allocates the destination array unless it already has the proper size and type. And while the newly
    allocated arrays are always continuous, you still need to check the destination array because
    Mat::create does not always allocate a new matrix.
     */
    bool isContinuous() const;

    //! 查看矩阵是否是子矩阵
    bool isSubmatrix() const;

    /** @brief 查看元素字节大小.

    The method returns the matrix element size in bytes. For example, if the matrix type is CV_16SC3 ,
    the method returns 3\*sizeof(short) or 6.
     */
    size_t elemSize() const;

    /** @brief 查询每个元素通道的字节大小.

    The method returns the matrix element channel size in bytes, that is, it ignores the number of
    channels. For example, if the matrix type is CV_16SC3 , the method returns sizeof(short) or 2.
     */
    size_t elemSize1() const;

    /** @brief 查询元素的类型.
     */
    int type() const;

    /** @brief 查询矩阵元素的深度.

    The method returns the identifier of the matrix element depth (the type of each individual channel).
    For example, for a 16-bit signed element array, the method returns CV_16S . A complete list of
    matrix types contains the following values:
    -   CV_8U - 8-bit unsigned integers ( 0..255 )
    -   CV_8S - 8-bit signed integers ( -128..127 )
    -   CV_16U - 16-bit unsigned integers ( 0..65535 )
    -   CV_16S - 16-bit signed integers ( -32768..32767 )
    -   CV_32S - 32-bit signed integers ( -2147483648..2147483647 )
    -   CV_32F - 32-bit floating-point numbers ( -FLT_MAX..FLT_MAX, INF, NAN )
    -   CV_64F - 64-bit floating-point numbers ( -DBL_MAX..DBL_MAX, INF, NAN )
     */
    int depth() const;

    /** @brief 查询通道数.

    The method returns the number of matrix channels.
     */
    int channels() const;

    /** @brief 查看标准化步长.

    The method returns a matrix step divided by Mat::elemSize1() . It can be useful to quickly access an
    arbitrary matrix element.
     */
    size_t step1(int i=0) const;

    /** @brief 查询是否空矩阵.

    The method returns true if Mat::total() is 0 or if Mat::data is NULL. Because of pop_back() and
    resize() methods `M.total() == 0` does not imply that `M.data == NULL`.
     */
    bool empty() const;

    /** @brief 查询元素的个数.

    The method returns the number of array elements (a number of pixels if the array represents an
    image).
     */
    size_t total() const;

    /** @brief 查询指定区域的元素数目.

     The method returns the number of elements within a certain sub-array slice with startDim <= dim < endDim
     */
    size_t total(int startDim, int endDim=INT_MAX) const;

    /**
     * @param elemChannels Number of channels or number of columns the matrix should have.
     *                     For a 2-D matrix, when the matrix has only 1 column, then it should have
     *                     elemChannels channels; When the matrix has only 1 channel,
     *                     then it should have elemChannels columns.
     *                     For a 3-D matrix, it should have only one channel. Furthermore,
     *                     if the number of planes is not one, then the number of rows
     *                     within every plane has to be 1; if the number of rows within
     *                     every plane is not 1, then the number of planes has to be 1.
     * @param depth The depth the matrix should have. Set it to -1 when any depth is fine.
     * @param requireContinuous Set it to true to require the matrix to be continuous
     * @return -1 if the requirement is not satisfied.
     *         Otherwise, it returns the number of elements in the matrix. Note
     *         that an element may have multiple channels.
     *
     * The following code demonstrates its usage for a 2-d matrix:
     * @snippet snippets/core_mat_checkVector.cpp example-2d
     *
     * The following code demonstrates its usage for a 3-d matrix:
     * @snippet snippets/core_mat_checkVector.cpp example-3d
     */
    int checkVector(int elemChannels, int depth=-1, bool requireContinuous=true) const;

    /** @brief 返回指定行的指针.

    The methods return `uchar*` or typed pointer to the specified matrix row. See the sample in
    Mat::isContinuous to know how to use these methods.
    @param i0 A 0-based row index.
     */
    uchar* ptr(int i0=0);
    /** @overload */
    const uchar* ptr(int i0=0) const;

    /** @overload
    @param row Index along the dimension 0
    @param col Index along the dimension 1
    */
    uchar* ptr(int row, int col);
    /** @overload
    @param row Index along the dimension 0
    @param col Index along the dimension 1
    */
    const uchar* ptr(int row, int col) const;

    /** @overload */
    uchar* ptr(int i0, int i1, int i2);
    /** @overload */
    const uchar* ptr(int i0, int i1, int i2) const;

    /** @overload */
    uchar* ptr(const int* idx);
    /** @overload */
    const uchar* ptr(const int* idx) const;
    /** @overload */
    template<int n> uchar* ptr(const Vec<int, n>& idx);
    /** @overload */
    template<int n> const uchar* ptr(const Vec<int, n>& idx) const;

    /** @overload */
    template _Tp* ptr(int i0=0);
    /** @overload */
    template const _Tp* ptr(int i0=0) const;
    /** @overload
    @param row Index along the dimension 0
    @param col Index along the dimension 1
    */
    template _Tp* ptr(int row, int col);
    /** @overload
    @param row Index along the dimension 0
    @param col Index along the dimension 1
    */
    template const _Tp* ptr(int row, int col) const;
    /** @overload */
    template _Tp* ptr(int i0, int i1, int i2);
    /** @overload */
    template const _Tp* ptr(int i0, int i1, int i2) const;
    /** @overload */
    template _Tp* ptr(const int* idx);
    /** @overload */
    template const _Tp* ptr(const int* idx) const;
    /** @overload */
    templateint n> _Tp* ptr(const Vec<int, n>& idx);
    /** @overload */
    templateint n> const _Tp* ptr(const Vec<int, n>& idx) const;

    /** @brief 返回指定数组元素的引用.

    The template methods return a reference to the specified array element. For the sake of higher
    performance, the index range checks are only performed in the Debug configuration.

    Note that the variants with a single index (i) can be used to access elements of single-row or
    single-column 2-dimensional arrays. That is, if, for example, A is a 1 x N floating-point matrix and
    B is an M x 1 integer matrix, you can simply write `A.at(k+4)` and `B.at(2*i+1)`
    instead of `A.at(0,k+4)` and `B.at(2*i+1,0)`, respectively.

    The example below initializes a Hilbert matrix:
    @code
        Mat H(100, 100, CV_64F);
        for(int i = 0; i < H.rows; i++)
            for(int j = 0; j < H.cols; j++)
                H.at(i,j)=1./(i+j+1);
    @endcode

    Keep in mind that the size identifier used in the at operator cannot be chosen at random. It depends
    on the image from which you are trying to retrieve the data. The table below gives a better insight in this:
     - If matrix is of type `CV_8U` then use `Mat.at(y,x)`.
     - If matrix is of type `CV_8S` then use `Mat.at(y,x)`.
     - If matrix is of type `CV_16U` then use `Mat.at(y,x)`.
     - If matrix is of type `CV_16S` then use `Mat.at(y,x)`.
     - If matrix is of type `CV_32S`  then use `Mat.at(y,x)`.
     - If matrix is of type `CV_32F`  then use `Mat.at(y,x)`.
     - If matrix is of type `CV_64F` then use `Mat.at(y,x)`.

    @param i0 Index along the dimension 0
     */
    template _Tp& at(int i0=0);
    /** @overload
    @param i0 Index along the dimension 0
    */
    template const _Tp& at(int i0=0) const;
    /** @overload
    @param row Index along the dimension 0
    @param col Index along the dimension 1
    */
    template _Tp& at(int row, int col);
    /** @overload
    @param row Index along the dimension 0
    @param col Index along the dimension 1
    */
    template const _Tp& at(int row, int col) const;

    /** @overload
    @param i0 Index along the dimension 0
    @param i1 Index along the dimension 1
    @param i2 Index along the dimension 2
    */
    template _Tp& at(int i0, int i1, int i2);
    /** @overload
    @param i0 Index along the dimension 0
    @param i1 Index along the dimension 1
    @param i2 Index along the dimension 2
    */
    template const _Tp& at(int i0, int i1, int i2) const;

    /** @overload
    @param idx Array of Mat::dims indices.
    */
    template _Tp& at(const int* idx);
    /** @overload
    @param idx Array of Mat::dims indices.
    */
    template const _Tp& at(const int* idx) const;

    /** @overload */
    templateint n> _Tp& at(const Vec<int, n>& idx);
    /** @overload */
    templateint n> const _Tp& at(const Vec<int, n>& idx) const;

    /** @overload
    special versions for 2D arrays (especially convenient for referencing image pixels)
    @param pt Element position specified as Point(j,i) .
    */
    template _Tp& at(Point pt);
    /** @overload
    special versions for 2D arrays (especially convenient for referencing image pixels)
    @param pt Element position specified as Point(j,i) .
    */
    template const _Tp& at(Point pt) const;

    /** @brief 返回指向矩阵首元素的迭代器.

    The methods return the matrix read-only or read-write iterators. The use of matrix iterators is very
    similar to the use of bi-directional STL iterators. In the example below, the alpha blending
    function is rewritten using the matrix iterators:
    @code
        template
        void alphaBlendRGBA(const Mat& src1, const Mat& src2, Mat& dst)
        {
            typedef Vec VT;

            const float alpha_scale = (float)std::numeric_limits::max(),
                        inv_scale = 1.f/alpha_scale;

            CV_Assert( src1.type() == src2.type() &&
                       src1.type() == traits::Type::value &&
                       src1.size() == src2.size());
            Size size = src1.size();
            dst.create(size, src1.type());

            MatConstIterator_ it1 = src1.begin(), it1_end = src1.end();
            MatConstIterator_ it2 = src2.begin();
            MatIterator_ dst_it = dst.begin();

            for( ; it1 != it1_end; ++it1, ++it2, ++dst_it )
            {
                VT pix1 = *it1, pix2 = *it2;
                float alpha = pix1[3]*inv_scale, beta = pix2[3]*inv_scale;
                *dst_it = VT(saturate_cast(pix1[0]*alpha + pix2[0]*beta),
                             saturate_cast(pix1[1]*alpha + pix2[1]*beta),
                             saturate_cast(pix1[2]*alpha + pix2[2]*beta),
                             saturate_cast((1 - (1-alpha)*(1-beta))*alpha_scale));
            }
        }
    @endcode
     */
    template MatIterator_<_Tp> begin();
    template MatConstIterator_<_Tp> begin() const;

    /** @brief 返回指向矩阵最后一个元素之后下一个位置的迭代器.

    The methods return the matrix read-only or read-write iterators, set to the point following the last
    matrix element.
     */
    template MatIterator_<_Tp> end();
    template MatConstIterator_<_Tp> end() const;

    /** @brief Runs the given functor over all matrix elements in parallel.

    The operation passed as argument has to be a function pointer, a function object or a lambda(C++11).

    Example 1. All of the operations below put 0xFF the first channel of all matrix elements:
    @code
        Mat image(1920, 1080, CV_8UC3);
        typedef cv::Point3_ Pixel;

        // first. raw pointer access.
        for (int r = 0; r < image.rows; ++r) {
            Pixel* ptr = image.ptr(r, 0);
            const Pixel* ptr_end = ptr + image.cols;
            for (; ptr != ptr_end; ++ptr) {
                ptr->x = 255;
            }
        }

        // Using MatIterator. (Simple but there are a Iterator's overhead)
        for (Pixel &p : cv::Mat_(image)) {
            p.x = 255;
        }

        // Parallel execution with function object.
        struct Operator {
            void operator ()(Pixel &pixel, const int * position) {
                pixel.x = 255;
            }
        };
        image.forEach(Operator());

        // Parallel execution using C++11 lambda.
        image.forEach([](Pixel &p, const int * position) -> void {
            p.x = 255;
        });
    @endcode
    Example 2. Using the pixel's position:
    @code
        // Creating 3D matrix (255 x 255 x 255) typed uint8_t
        // and initialize all elements by the value which equals elements position.
        // i.e. pixels (x,y,z) = (1,2,3) is (b,g,r) = (1,2,3).

        int sizes[] = { 255, 255, 255 };
        typedef cv::Point3_ Pixel;

        Mat_ image = Mat::zeros(3, sizes, CV_8UC3);

        image.forEach([&](Pixel& pixel, const int position[]) -> void {
            pixel.x = position[0];
            pixel.y = position[1];
            pixel.z = position[2];
        });
    @endcode
     */
    template void forEach(const Functor& operation);
    /** @overload */
    template void forEach(const Functor& operation) const;

#ifdef CV_CXX_MOVE_SEMANTICS
    Mat(Mat&& m);
    Mat& operator = (Mat&& m);
#endif

    enum { MAGIC_VAL  = 0x42FF0000, AUTO_STEP = 0, CONTINUOUS_FLAG = CV_MAT_CONT_FLAG, SUBMATRIX_FLAG = CV_SUBMAT_FLAG };
    enum { MAGIC_MASK = 0xFFFF0000, TYPE_MASK = 0x00000FFF, DEPTH_MASK = 7 };

    /*! 含有多个位域的标志:
         - the magic signature
         - continuity flag
         - depth
         - number of channels
     */
    int flags;
    //! 矩阵维度, >= 2
    int dims;
    //! 当矩阵有多个维度时,行数及列数或者(-1, -1) 
    int rows, cols;
    //! 指向数据的指针
    uchar* data;

    //! (在locateROI and adjustROI中使用)
    const uchar* datastart;
    const uchar* dataend;
    const uchar* datalimit;

    //! 定制的分配器
    MatAllocator* allocator;
    //! 标准分配器
    static MatAllocator* getStdAllocator();
    static MatAllocator* getDefaultAllocator();
    static void setDefaultAllocator(MatAllocator* allocator);

    //! 与 UMat的交互
    UMatData* u;

    MatSize size;
    MatStep step;

protected:
    template void forEach_impl(const Functor& operation);
};

部分函数源码

//==================================================================================================

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;
        CV_TRY
        {
            u = a->allocate(dims, size, _type, 0, step.p, 0, USAGE_DEFAULT);
            CV_Assert(u != 0);
        }
        CV_CATCH_ALL
        {
            if(a != a0)
                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);
}

void Mat::create(const std::vector<int>& _sizes, int _type)
{
    create((int)_sizes.size(), _sizes.data(), _type);
}

void Mat::copySize(const Mat& m)
{
    setSize(*this, m.dims, 0, 0);
    for( int i = 0; i < dims; i++ )
    {
        size[i] = m.size[i];
        step[i] = m.step[i];
    }
}

void Mat::deallocate()
{
    if(u)
    {
        UMatData* u_ = u;
        u = NULL;
        (u_->currAllocator ? u_->currAllocator : allocator ? allocator : getDefaultAllocator())->unmap(u_);
    }
}

Mat::Mat(const Mat& m, const Range& _rowRange, const Range& _colRange)
    : flags(MAGIC_VAL), dims(0), rows(0), cols(0), data(0), datastart(0), dataend(0),
      datalimit(0), allocator(0), u(0), size(&rows)
{
    CV_Assert( m.dims >= 2 );
    if( m.dims > 2 )
    {
        AutoBuffer rs(m.dims);
        rs[0] = _rowRange;
        rs[1] = _colRange;
        for( int i = 2; i < m.dims; i++ )
            rs[i] = Range::all();
        *this = m(rs);
        return;
    }

    *this = m;
    CV_TRY
    {
        if( _rowRange != Range::all() && _rowRange != Range(0,rows) )
        {
            CV_Assert( 0 <= _rowRange.start && _rowRange.start <= _rowRange.end
                       && _rowRange.end <= m.rows );
            rows = _rowRange.size();
            data += step*_rowRange.start;
            flags |= SUBMATRIX_FLAG;
        }

        if( _colRange != Range::all() && _colRange != Range(0,cols) )
        {
            CV_Assert( 0 <= _colRange.start && _colRange.start <= _colRange.end
                       && _colRange.end <= m.cols );
            cols = _colRange.size();
            data += _colRange.start*elemSize();
            flags &= cols < m.cols ? ~CONTINUOUS_FLAG : -1;
            flags |= SUBMATRIX_FLAG;
        }
    }
    CV_CATCH_ALL
    {
        release();
        CV_RETHROW();
    }

    if( rows == 1 )
        flags |= CONTINUOUS_FLAG;

    if( rows <= 0 || cols <= 0 )
    {
        release();
        rows = cols = 0;
    }
}


Mat::Mat(const Mat& m, const Rect& roi)
    : flags(m.flags), dims(2), rows(roi.height), cols(roi.width),
    data(m.data + roi.y*m.step[0]),
    datastart(m.datastart), dataend(m.dataend), datalimit(m.datalimit),
    allocator(m.allocator), u(m.u), size(&rows)
{
    CV_Assert( m.dims <= 2 );
    flags &= roi.width < m.cols ? ~CONTINUOUS_FLAG : -1;
    flags |= roi.height == 1 ? CONTINUOUS_FLAG : 0;

    size_t esz = CV_ELEM_SIZE(flags);
    data += roi.x*esz;
    CV_Assert( 0 <= roi.x && 0 <= roi.width && roi.x + roi.width <= m.cols &&
              0 <= roi.y && 0 <= roi.height && roi.y + roi.height <= m.rows );
    if( u )
        CV_XADD(&u->refcount, 1);
    if( roi.width < m.cols || roi.height < m.rows )
        flags |= SUBMATRIX_FLAG;

    step[0] = m.step[0]; step[1] = esz;

    if( rows <= 0 || cols <= 0 )
    {
        release();
        rows = cols = 0;
    }
}


Mat::Mat(int _dims, const int* _sizes, int _type, void* _data, const size_t* _steps)
    : flags(MAGIC_VAL), dims(0), rows(0), cols(0), data(0), datastart(0), dataend(0),
      datalimit(0), allocator(0), u(0), size(&rows)
{
    flags |= CV_MAT_TYPE(_type);
    datastart = data = (uchar*)_data;
    setSize(*this, _dims, _sizes, _steps, true);
    finalizeHdr(*this);
}


Mat::Mat(const std::vector<int>& _sizes, int _type, void* _data, const size_t* _steps)
    : flags(MAGIC_VAL), dims(0), rows(0), cols(0), data(0), datastart(0), dataend(0),
      datalimit(0), allocator(0), u(0), size(&rows)
{
    flags |= CV_MAT_TYPE(_type);
    datastart = data = (uchar*)_data;
    setSize(*this, (int)_sizes.size(), _sizes.data(), _steps, true);
    finalizeHdr(*this);
}


Mat::Mat(const Mat& m, const Range* ranges)
    : flags(MAGIC_VAL), dims(0), rows(0), cols(0), data(0), datastart(0), dataend(0),
      datalimit(0), allocator(0), u(0), size(&rows)
{
    int d = m.dims;

    CV_Assert(ranges);
    for( int i = 0; i < d; i++ )
    {
        Range r = ranges[i];
        CV_Assert( r == Range::all() || (0 <= r.start && r.start < r.end && r.end <= m.size[i]) );
    }
    *this = m;
    for( int i = 0; i < d; i++ )
    {
        Range r = ranges[i];
        if( r != Range::all() && r != Range(0, size.p[i]))
        {
            size.p[i] = r.end - r.start;
            data += r.start*step.p[i];
            flags |= SUBMATRIX_FLAG;
        }
    }
    updateContinuityFlag(*this);
}

Mat::Mat(const Mat& m, const std::vector& ranges)
    : flags(MAGIC_VAL), dims(0), rows(0), cols(0), data(0), datastart(0), dataend(0),
    datalimit(0), allocator(0), u(0), size(&rows)
{
    int d = m.dims;

    CV_Assert((int)ranges.size() == d);
    for (int i = 0; i < d; i++)
    {
        Range r = ranges[i];
        CV_Assert(r == Range::all() || (0 <= r.start && r.start < r.end && r.end <= m.size[i]));
    }
    *this = m;
    for (int i = 0; i < d; i++)
    {
        Range r = ranges[i];
        if (r != Range::all() && r != Range(0, size.p[i]))
        {
            size.p[i] = r.end - r.start;
            data += r.start*step.p[i];
            flags |= SUBMATRIX_FLAG;
        }
    }
    updateContinuityFlag(*this);
}


Mat Mat::diag(int d) const
{
    CV_Assert( dims <= 2 );
    Mat m = *this;
    size_t esz = elemSize();
    int len;

    if( d >= 0 )
    {
        len = std::min(cols - d, rows);
        m.data += esz*d;
    }
    else
    {
        len = std::min(rows + d, cols);
        m.data -= step[0]*d;
    }
    CV_DbgAssert( len > 0 );

    m.size[0] = m.rows = len;
    m.size[1] = m.cols = 1;
    m.step[0] += (len > 1 ? esz : 0);

    if( m.rows > 1 )
        m.flags &= ~CONTINUOUS_FLAG;
    else
        m.flags |= CONTINUOUS_FLAG;

    if( size() != Size(1,1) )
        m.flags |= SUBMATRIX_FLAG;

    return m;
}


void Mat::pop_back(size_t nelems)
{
    CV_Assert( nelems <= (size_t)size.p[0] );

    if( isSubmatrix() )
        *this = rowRange(0, size.p[0] - (int)nelems);
    else
    {
        size.p[0] -= (int)nelems;
        dataend -= nelems*step.p[0];
        /*if( size.p[0] <= 1 )
        {
            if( dims <= 2 )
                flags |= CONTINUOUS_FLAG;
            else
                updateContinuityFlag(*this);
        }*/
    }
}


void Mat::push_back_(const void* elem)
{
    int r = size.p[0];
    if( isSubmatrix() || dataend + step.p[0] > datalimit )
        reserve( std::max(r + 1, (r*3+1)/2) );

    size_t esz = elemSize();
    memcpy(data + r*step.p[0], elem, esz);
    size.p[0] = r + 1;
    dataend += step.p[0];
    if( esz < step.p[0] )
        flags &= ~CONTINUOUS_FLAG;
}


void Mat::reserve(size_t nelems)
{
    const size_t MIN_SIZE = 64;

    CV_Assert( (int)nelems >= 0 );
    if( !isSubmatrix() && data + step.p[0]*nelems <= datalimit )
        return;

    int r = size.p[0];

    if( (size_t)r >= nelems )
        return;

    size.p[0] = std::max((int)nelems, 1);
    size_t newsize = total()*elemSize();

    if( newsize < MIN_SIZE )
        size.p[0] = (int)((MIN_SIZE + newsize - 1)*nelems/newsize);

    Mat m(dims, size.p, type());
    size.p[0] = r;
    if( r > 0 )
    {
        Mat mpart = m.rowRange(0, r);
        copyTo(mpart);
    }

    *this = m;
    size.p[0] = r;
    dataend = data + step.p[0]*r;
}


void Mat::reserveBuffer(size_t nbytes)
{
    size_t esz = 1;
    int mtype = CV_8UC1;
    if (!empty())
    {
        if (!isSubmatrix() && data + nbytes <= dataend)//Should it be datalimit?
            return;
        esz = elemSize();
        mtype = type();
    }

    size_t nelems = (nbytes - 1) / esz + 1;

#if SIZE_MAX > UINT_MAX
    CV_Assert(nelems <= size_t(INT_MAX)*size_t(INT_MAX));
    int newrows = nelems > size_t(INT_MAX) ? nelems > 0x400*size_t(INT_MAX) ? nelems > 0x100000 * size_t(INT_MAX) ? nelems > 0x40000000 * size_t(INT_MAX) ?
                  size_t(INT_MAX) : 0x40000000 : 0x100000 : 0x400 : 1;
#else
    int newrows = nelems > size_t(INT_MAX) ? 2 : 1;
#endif
    int newcols = (int)((nelems - 1) / newrows + 1);

    create(newrows, newcols, mtype);
}


void Mat::resize(size_t nelems)
{
    int saveRows = size.p[0];
    if( saveRows == (int)nelems )
        return;
    CV_Assert( (int)nelems >= 0 );

    if( isSubmatrix() || data + step.p[0]*nelems > datalimit )
        reserve(nelems);

    size.p[0] = (int)nelems;
    dataend += (size.p[0] - saveRows)*step.p[0];

    //updateContinuityFlag(*this);
}


void Mat::resize(size_t nelems, const Scalar& s)
{
    int saveRows = size.p[0];
    resize(nelems);

    if( size.p[0] > saveRows )
    {
        Mat part = rowRange(saveRows, size.p[0]);
        part = s;
    }
}

void Mat::push_back(const Mat& elems)
{
    int r = size.p[0], delta = elems.size.p[0];
    if( delta == 0 )
        return;
    if( this == &elems )
    {
        Mat tmp = elems;
        push_back(tmp);
        return;
    }
    if( !data )
    {
        *this = elems.clone();
        return;
    }

    size.p[0] = elems.size.p[0];
    bool eq = size == elems.size;
    size.p[0] = r;
    if( !eq )
        CV_Error(CV_StsUnmatchedSizes, "Pushed vector length is not equal to matrix row length");
    if( type() != elems.type() )
        CV_Error(CV_StsUnmatchedFormats, "Pushed vector type is not the same as matrix type");

    if( isSubmatrix() || dataend + step.p[0]*delta > datalimit )
        reserve( std::max(r + delta, (r*3+1)/2) );

    size.p[0] += delta;
    dataend += step.p[0]*delta;

    //updateContinuityFlag(*this);

    if( isContinuous() && elems.isContinuous() )
        memcpy(data + r*step.p[0], elems.data, elems.total()*elems.elemSize());
    else
    {
        Mat part = rowRange(r, r + delta);
        elems.copyTo(part);
    }
}


void Mat::locateROI( Size& wholeSize, Point& ofs ) const
{
    CV_Assert( dims <= 2 && step[0] > 0 );
    size_t esz = elemSize(), minstep;
    ptrdiff_t delta1 = data - datastart, delta2 = dataend - datastart;

    if( delta1 == 0 )
        ofs.x = ofs.y = 0;
    else
    {
        ofs.y = (int)(delta1/step[0]);
        ofs.x = (int)((delta1 - step[0]*ofs.y)/esz);
        CV_DbgAssert( data == datastart + ofs.y*step[0] + ofs.x*esz );
    }
    minstep = (ofs.x + cols)*esz;
    wholeSize.height = (int)((delta2 - minstep)/step[0] + 1);
    wholeSize.height = std::max(wholeSize.height, ofs.y + rows);
    wholeSize.width = (int)((delta2 - step*(wholeSize.height-1))/esz);
    wholeSize.width = std::max(wholeSize.width, ofs.x + cols);
}

Mat& Mat::adjustROI( int dtop, int dbottom, int dleft, int dright )
{
    CV_Assert( dims <= 2 && step[0] > 0 );
    Size wholeSize; Point ofs;
    size_t esz = elemSize();
    locateROI( wholeSize, ofs );
    int row1 = std::min(std::max(ofs.y - dtop, 0), wholeSize.height), row2 = std::max(0, std::min(ofs.y + rows + dbottom, wholeSize.height));
    int col1 = std::min(std::max(ofs.x - dleft, 0), wholeSize.width), col2 = std::max(0, std::min(ofs.x + cols + dright, wholeSize.width));
    if(row1 > row2)
        std::swap(row1, row2);
    if(col1 > col2)
        std::swap(col1, col2);

    data += (row1 - ofs.y)*step + (col1 - ofs.x)*esz;
    rows = row2 - row1; cols = col2 - col1;
    size.p[0] = rows; size.p[1] = cols;
    if( esz*cols == step[0] || rows == 1 )
        flags |= CONTINUOUS_FLAG;
    else
        flags &= ~CONTINUOUS_FLAG;
    return *this;
}

Mat Mat::reshape(int new_cn, int new_rows) const
{
    int cn = channels();
    Mat hdr = *this;

    if( dims > 2 )
    {
        if( new_rows == 0 && new_cn != 0 && size[dims-1]*cn % new_cn == 0 )
        {
            hdr.flags = (hdr.flags & ~CV_MAT_CN_MASK) | ((new_cn-1) << CV_CN_SHIFT);
            hdr.step[dims-1] = CV_ELEM_SIZE(hdr.flags);
            hdr.size[dims-1] = hdr.size[dims-1]*cn / new_cn;
            return hdr;
        }
        if( new_rows > 0 )
        {
            int sz[] = { new_rows, (int)(total()/new_rows) };
            return reshape(new_cn, 2, sz);
        }
    }

    CV_Assert( dims <= 2 );

    if( new_cn == 0 )
        new_cn = cn;

    int total_width = cols * cn;

    if( (new_cn > total_width || total_width % new_cn != 0) && new_rows == 0 )
        new_rows = rows * total_width / new_cn;

    if( new_rows != 0 && new_rows != rows )
    {
        int total_size = total_width * rows;
        if( !isContinuous() )
            CV_Error( CV_BadStep,
            "The matrix is not continuous, thus its number of rows can not be changed" );

        if( (unsigned)new_rows > (unsigned)total_size )
            CV_Error( CV_StsOutOfRange, "Bad new number of rows" );

        total_width = total_size / new_rows;

        if( total_width * new_rows != total_size )
            CV_Error( CV_StsBadArg, "The total number of matrix elements "
                                    "is not divisible by the new number of rows" );

        hdr.rows = new_rows;
        hdr.step[0] = total_width * elemSize1();
    }

    int new_width = total_width / new_cn;

    if( new_width * new_cn != total_width )
        CV_Error( CV_BadNumChannels,
        "The total width is not divisible by the new number of channels" );

    hdr.cols = new_width;
    hdr.flags = (hdr.flags & ~CV_MAT_CN_MASK) | ((new_cn-1) << CV_CN_SHIFT);
    hdr.step[1] = CV_ELEM_SIZE(hdr.flags);
    return hdr;
}

Mat Mat::reshape(int _cn, int _newndims, const int* _newsz) const
{
    if(_newndims == dims)
    {
        if(_newsz == 0)
            return reshape(_cn);
        if(_newndims == 2)
            return reshape(_cn, _newsz[0]);
    }

    if (isContinuous())
    {
        CV_Assert(_cn >= 0 && _newndims > 0 && _newndims <= CV_MAX_DIM && _newsz);

        if (_cn == 0)
            _cn = this->channels();
        else
            CV_Assert(_cn <= CV_CN_MAX);

        size_t total_elem1_ref = this->total() * this->channels();
        size_t total_elem1 = _cn;

        AutoBuffer<int, 4> newsz_buf( (size_t)_newndims );

        for (int i = 0; i < _newndims; i++)
        {
            CV_Assert(_newsz[i] >= 0);

            if (_newsz[i] > 0)
                newsz_buf[i] = _newsz[i];
            else if (i < dims)
                newsz_buf[i] = this->size[i];
            else
                CV_Error(CV_StsOutOfRange, "Copy dimension (which has zero size) is not present in source matrix");

            total_elem1 *= (size_t)newsz_buf[i];
        }

        if (total_elem1 != total_elem1_ref)
            CV_Error(CV_StsUnmatchedSizes, "Requested and source matrices have different count of elements");

        Mat hdr = *this;
        hdr.flags = (hdr.flags & ~CV_MAT_CN_MASK) | ((_cn-1) << CV_CN_SHIFT);
        setSize(hdr, _newndims, (int*)newsz_buf, NULL, true);

        return hdr;
    }

    CV_Error(CV_StsNotImplemented, "Reshaping of n-dimensional non-continuous matrices is not supported yet");
    // TBD
    return Mat();
}

Mat Mat::reshape(int _cn, const std::vector<int>& _newshape) const
{
    if(_newshape.empty())
    {
        CV_Assert(empty());
        return *this;
    }

    return reshape(_cn, (int)_newshape.size(), &_newshape[0]);
}

Mat Mat::diag(const Mat& d)
{
    CV_Assert( d.cols == 1 || d.rows == 1 );
    int len = d.rows + d.cols - 1;
    Mat m(len, len, d.type(), Scalar(0));
    Mat md = m.diag();
    if( d.cols == 1 )
        d.copyTo(md);
    else
        transpose(d, md);
    return m;
}

int Mat::checkVector(int _elemChannels, int _depth, bool _requireContinuous) const
{
    return data && (depth() == _depth || _depth <= 0) &&
        (isContinuous() || !_requireContinuous) &&
        ((dims == 2 && (((rows == 1 || cols == 1) && channels() == _elemChannels) ||
                        (cols == _elemChannels && channels() == 1))) ||
        (dims == 3 && channels() == 1 && size.p[2] == _elemChannels && (size.p[0] == 1 || size.p[1] == 1) &&
         (isContinuous() || step.p[1] == step.p[2]*size.p[2])))
    ? (int)(total()*channels()/_elemChannels) : -1;
}

你可能感兴趣的:(OpenCV,计算机视觉,OpenCV,源码,C++,Mat类)