Mat

  cv::Mat是OpenCV用来记录大型数组的主要类型,可以视为是OpenCV所有C++实现的核心,OpenCV所有主要函数都是cv::Mat类的成员,或是将cv::Mat作为参数,或是返回一个cv::Mat类型。
  cv::Mat类用于表示任意维度的稠密数组。“稠密”表示该数组的所有部分都有一个值存储,即使这个值是0;对于大多数图像来说,都是以稠密数组的形式存储的;与之稠密数组对应的是稀疏数组,用于存储只有非0的数值。注意:在数组比较稠密的时候,稀疏数组反而会浪费大量内存。

cv::Mat类N维稠密数组

  cv::Mat类可以作为任意维度的数组使用,其数据可以看做是以按照栅格扫描顺序存储的n维数组。这意味着在一维数组中,元素是按照顺序排列的;而在一个二维数组中,数据按行组织,每一行也按顺序排列,对于三维数组来说,所有的通道都被行填充,每一个通道同样按顺序排列。
  所有的矩阵都含有一个表示它所包含数组类型的元素flag,一个表示其维度的元素dims,分别表示行和列的数目的元素rows和cols(在dims大于2的时候无效),一个指示数据真正存储位置的data指针,以及一个表示该内存区域有多少个引用的refcount元素,类似cv::Ptr<>的引用计数器。这个成员cv::Mat像智能指针一样管理内存区域。
  包含在cv::Mat中的数据不要求必须是简单基元。cv::Mat中的元素可以是一个简单的item,也可以是多个item。在包含多个数字的时候,它就被称为“多通道数组”。事实上,一个n维数组和一个(n-1)维多维数组有着非常接近的结构。在大多数情况下,将数组看做是带有数值向量的数组是很有用的。
  在OpenCV中,数组的行在内存中可能不是绝对按顺序存储的,由于数据对齐的原因,每一行之间可能存在一个很小的间隔,数据对齐是为了提高内存的存取效率。n维单通道数组和(n-1)维多通道数组的区别在于,这种填充始终出现于每一行的末尾(比如说,每个元素中的通道总是按顺序存储的)。

创建一个数组

  可以通过实例化变量cv::Mat来创建一个数组,通过这种方式创建的数组没有大小和数据类型,之后再调用成员函数create()来申请一个内存区域。
  一个create的变体是通过指定行数和列数以及数据类型来配置二维数组的规模。数组的类型(type)决定了它含有什么样的元素。一个有效的数据类型需要同时指明数据的类型和通道数。所有这些数据类型都在库的头文件中声明,包括CV_{8U,16S,16U,32S,32F,64F}C{1,2,3}的多种组合;
  使用cv::Mat类的构造函数来初始化数组的所有元素;

cv::Mat m;
m.create(3,10,CV_32FC3);
m.setTo(cv::Scalar(1.0f,1.0f,1.0f);
cv::Mat m(3,10,CV_32FC3,cv::Scalar(1.0f,1.0f,1.0f));

  对象cv::Mat是数据实体的头,原则上来说,它和数据实体是完全不同的两个东西;与C++的智能指针相似,cv::Mat指向的矩阵实体,是内部的数据指针所指向的数据实体,Mat和Mat之间的数据拷贝,都与指向的数据实体的引用计数相关,数组的更新都极其方便;

构造函数(非复制构造函数)

  指定初始值cv::Scalar,所有的数据都会被初始化为这个值;或者指定一个已存在的数据头指针,这就相当是在现有存在数据的头Mat内不会由数据复制的行为,成员data会被设置为data参数所指向的区域。

构造函数说明
cv::Mat默认构造函数
cv::Mat(int rows, int cols, int type);指定类型的二维数组
cv::Mat(int rows, int cols, int type, const Scalar &s);指定类型的二维数组,并指定初始化值
cv::Mat(int rows, int cols, int type, void* data,size_t step = AUTO_STEP);指定类型的二维数组,并指定预先存储的数据
cv::Mat(cv::Size sz, int type);指定类型的二维数组(大小由sz指定)
cv::Mat(cv::Size sz, int type, const Scalar &s);指定类型的二维数组,并指定初始化值(大小由sz指定)
cv::Mat(cv::Size sz, int type, void* data, size_t step = AUTO_STEP);指定类型的二维数组,并指定预先存储的数据(大小由sz指定)
cv::Mat(int ndims, const int* sizes, int type)指定类型的多维数组
cv::Mat(int ndims, const int* sizes, int type, const Scalar &s)指定类型的多维数组,并指定初始化值
cv::Mat(int ndims, const int* sizes, int type, void* data, size_t step = AUTO_STEP)指定类型的多维数组,并指定预先存储的数据

复制构造函数

  复制构造函数时从一个数组创建另一个数组,可以从一个已经存在的数组的子区域创建一个数组,或者从一些矩阵表达中生成一个新的矩阵;

构造函数描述
cv:Mat(const Mat &mat);复制构造函数
cv::Mat(const Mat &mat, const cv::Range &rows, const cv::Range &roi);只从指定的行列中复制数据的复制构造函数
cv::Mat(const Mat &mat, const cv::Rect* roi);只从感兴趣的区域中复制数据的复制构造函数
cv::Mat(const Mat &mat, const cv::Range* ranges);服务于n维数组的,从泛化的感兴趣区域中复制数据的复制构造函数
cv::Mat(const cv::MatExpr& expr);从其他矩阵的线性代数表述中生成新矩阵的复制构造函数

  使用两个方向上的cv::Range框选原始图像(二维图像)上的的复制构造函数示意图:
在这里插入图片描述
  使用一个Rect矩形从原始图像(二维图像)上的的复制构造函数示意图:
在这里插入图片描述

模板构造函数

  通过模板类cv::Vec<>或cv::Matx<>来创建对应维度和类型的cv::Mat,或者使用一个STL vector<>来创建一个相同类型的数组。

构造函数描述
cv::Mat( const cv::Vec<T,n> &vec, bool copyData = true);构造一个如同cv::Vec所指定的数据类型为T、大小为n的一维数组
cv::Mat( const cv::Matx<T,m,n> &vec, bool copyData = true);构造一个如同cv::Matx所指定的数据类型为T、大小为m*n的二维数组
cv::Mat( const std::vector &vec, bool copyData = true);构造STL的vector所指定的数据类型为T、大小为vector元素数的一维数组

构造并初始化

  cv::Mat中提供静态方法创建一个数组,并在初始时将全部矩阵数组中的值设置为指定初始值。

函数描述
cv::Mat::zeros(rows,cols,type);构造一个大小为rows*cols、数据类型为type指定类型的、值全为0的矩阵
cv::Mat::ones(rows,cols,type);构造一个大小为rows*cols、数据类型为type指定类型的、值全为1的矩阵
cv::Mat::eye(rows,cols,type);构造一个大小为rows*cols、数据类型为type指定类型的单位矩阵

独立获取数组元素

  访问一个元素的两种主要方法是通过位置或迭代器访问。
  直接访问是通过模板函数**at<>()**来实现的,对不同维度的数组有不同的参数要求。

m.at<double>(row_index,col_index);

  使用方法:
  单通道数组操作使用:

cv::Mat m = cv::Mat::eye(10,10,32FC1);
printf("Element(3,3) is %f\n",m.at<float>(3,3));

  多通道数组的操作使用:

cv::Mat m = cv::Mat::eye(10,10,32FC2);
printf("Element(3,3) is (%f,%f)\n",m.at<cv::Vec2f>(3,3)[0],m.at<cv::Vec2f>(3,3)[1]);

  at<>访问器函数的变体

示例描述
M.at<int>(i);整型数组M中的元素i
M.at<float>(i,j);浮点型数组中的元素(i,j)
M.at<int>(pt);整型矩阵M中处于(pt.x,pt.y)的元素
M.at<float>(i,j,k);三维浮点型矩阵M中处于(i,j,k)位置的元素
M.at<uchar>(idx);无符号字符数组M中位于idx[]所索引的n维位置的元素

  使用ptr<>()访问数组某一行;在OpenCV中数组中的数据是按行连续组织的,因此不可以通过这种方式访问一个指定的列。

mtx.ptr<Vec3f>(3);

  给定一个类型为float三通道的矩阵mtx,结构体mtx.ptr<Vec3f>(3)将会返回mtx的第三行指向第一个元素第一个(浮点)通道的指针,这通常是访问数组最快的一种方式。
  注意:使用at<>和利用指针访问的差距取决于编译器的优化程度。使用at<>进行存储性能更接近于好的优化器所能够达到的效果(尽管稍微慢一些),但是如果优化器被关闭了,其性能相较于没有优化器优化会有一个数量级的提升。而通过迭代器的访问几乎总是比这两种方法都要慢,然而在几乎所有情况下,使用内置的OpenCV函数都比你写的所有通过循环来控制直接访问的方法快,所以在任何情况下,都要避免通过循环来大量访问矩阵内部结构;

参考资料

1.《学习OpenCV 3》

GitHub 加速计划 / opencv31 / opencv
77.38 K
55.71 K
下载
OpenCV: 开源计算机视觉库
最近提交(Master分支:2 个月前 )
c3747a68 Added Universal Windows Package build to CI. 11 天前
9b635da5 - 11 天前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐