Mat–深度解析
Mat作为图像处理操作的基础对象,不掀开它的神秘面纱,实在无法愉快的看源码,所以在此做一个学习总结~
先贴出opencv的官方文档地址,这里比较详细、概括性的介绍了opencv相关的结构和知识,其中包括了Mat结构,对于初学者,我感觉很值得一看~
想了解一个对象,就要拿出面试官八卦你的精神:
- 你叫什么?-----------------(Mat)
- 你是干什么的?-----------(Mat的功用)
- 你有什么优势?-----------(Mat比Iplimage改进的地方)
- 你能接手哪些业务面?–(Mat的作用对象)
- 你家里有几口人?--------(Mat的成员变量)
- 分别都有谁?--------------(Mat的成员变量的作用)
- 你会干什么?--------------(Mat的成员函数)
- 你是怎么干的?-----------(Mat的成员函数实现过程)
这一套逼问结束,估计该了解的也都了解了。。
那就开启这一次了解之旅吧~
1.Mat 是什么,干什么用的?
根据源码对Mat的解释:Mat是一个 n-dimensional dense numerical sigle-channel or multi-channel array,就是说Mat是一个n维多通道或单通道的数组。
具体是做什么用呢?看图说话:
在计算机的世界,图像经过取样、离散化处理后,最终只剩下描述图像的像素值的数字矩阵和其他描述信息,为了将这些信息存储下来便于后续处理,opencv中提供了Mat帮助我们充当数据的载体。
2.Mat作用的对象有哪些?
Mat只可以存图像吗? 不看不知道,一看吓一跳,Mat可以存的对象有很多:(complex-valued vectors and matrices, grayscale or color images, voxel volumes, vector fileds, point clouds, tensors,histograms) 其实就是(复数向量和矩阵、灰度或彩色图像、体素、矢量场、张量和直方图)
我想,Mat的产生,除了为搞图像的人提供载体,也为搞数学、物理的人提供了载体,不过,某有关系,我们了解行业相关就好了。。
3.Mat 有什么优势?
1.不需要手动分配和释放内存了,因为类的概念,提供了内存管理,相比IplImage,Mat可以更好的调节内存管理。
2.Mat包含了两部分数据:矩阵头 和 指向像素矩阵的值。提供了浅拷贝和深拷贝,引入了引用计数,方便数据管理并提高了管理效率。
4.Mat 存储方法
我们以灰度和彩色图像为例讲述其内存存储顺序,opencv官方文档中,给出了如下描述:
从图中,我们可以看到像素的排列顺序,但是在内存中可能Row1直接就接在了Row0后,也许整个图像存储完,是在一块连续内存上,因此Mat给出了一个cv::Mat::isContinuous()函数来询问矩阵是否在内存上顺序存储。
对于不同格式的图像存储,其主要依赖于我们选择的颜色空间和数据类型,在opencv中,常见的颜色系统包括:
- RGB:彩色空间
- HSV\HLS:色调、饱和度、强度
- YUV\YCrCb:亮度、明度、饱和度
- Lab:感知统一的颜色空间
所以具体的存储形式和颜色空间的排列会统一起来,具体opencv中其他色彩空间的排列方式,可自行探测哦~
5.Mat有多少成员变量
每个成员变量就像家庭成员,都有自己的职责和分工,了解它们的功用会帮我们在编写算法时提供便利。。
成员变量 | 变量含义 及 作用 |
---|---|
flags | |
dims | 矩阵维度(>=2) |
rows/cols | 矩阵行数和列数,在图像中为图像的高和宽 |
data | 指向矩阵数据的起始的位置,不是指向矩阵头 |
size | 一个用来存放矩阵各维度数据的参数 |
step | 一个用来快速定位元素地址的元素 |
u | 和UMat相互影响的变量 |
datastart | 指向ROI区域起始点的指针 |
dataend | 指向ROI区域终点的指针 |
datalimit | 包含了ROI区域的全局区域的终点指针 |
因为受到网友darkragon-001的启发,画图来表示一下各个变量的含义,化抽象为具象哈~
看图的时候,有1个个注意点:
1.将整幅画面看为三维的矩阵,是用来对比二维矩阵下step的不同。其他时候只需要看最上层的二维矩阵即可并认为Mat是一个二维矩阵即可。
知识点:
-
1.step,如果Mat为三维矩阵,那么step[0]为MNelemSize,即MN这一面所占内存大小,如果Mat为二维图像(以浅灰色那一面为例),step[0]为MelemSize,,即一行数据所占内存大小,相应的step[1]为一个数据所占内存大小,这块可以去了解我的另外一篇博文,有详细讲~
-
2.size,size作为一个opencv自定义的结构体,也是用来帮助定位矩阵的尺寸分布,可以用size[i]调用的方法查找矩阵第i维的维度,如果Mat是二维的,也可以用size()->width,size()->height的方法去查找矩阵的长和宽。
-
3.绿色区域为ROI区域,即存在某个图像M1为绿色部分且为M2的一部分,当然当M1==M2时,就不是一个ROI图像了。这种ROI的设置,对于实现图像算法,比如滤波,卷积时边缘处理,非常的方便。
6.Mat有多少成员变量,都有什么作用?
为了方便阅读,同样以表格的形式表现出来~
成员函数 | 函数作用 及 功能 |
---|---|
1.构造函数 | 构造一个Mat对象 |
Mat() | |
Mat(int rows, int cols, int type) | |
Mat(int _rows, int _cols, int _type, const Scalar& _s) | |
Mat(Size size, int type) | |
Mat(int rows, int cols, int type, const Scalar& s) | |
Mat(Size size, int type, const Scalar& s | 以上构造2维矩阵 |
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) | 根据data构造2维矩阵 |
Mat(int ndims, const int* sizes, int type) | |
Mat(const std::vector& sizes, int type) | |
Mat(int ndims, const int* sizes, int type, const Scalar& s) | |
Mat(const std::vector& sizes, int type, const Scalar& s | 以上构造n维矩阵 |
Mat(int ndims, const int* sizes, int type, void* data, const size_t* steps=0) | |
Mat(const std::vector& sizes, int type, void* data, const size_t* steps=0) | 根据data构造n维矩阵 |
Mat(const Mat& m) | 根据m构造 |
Mat(const Mat& m, const Range& rowRange, const Range& colRange=Range::all()) | 根据m和行列的范围构造 |
Mat(const Mat& m, const Rect& roi) | 根据m和roi进行构造 |
Mat(const Mat& m, const std::vector& ranges) | |
Mat(const Mat& m, const std::vector& ranges) | 以上根据m构造出来的图像被修改时也会改变m中的值,这种关联关系需谨慎对待 |
2.运算符操作 | Mat之间+=-这类符号操作 |
Mat& operator = (const Mat& m) | 与m共用一套data |
Mat& operator = (const MatExpr& expr) | 自己可以是已存在的,内存也会被管理 |
UMat getUMat(int accessFlags, UMatUsageFlags usageFlags = USAGE_DEFAULT) const | 从Mat中追溯UMat |
3.行、列操作 | 得到Mat中成员变量或信息的函数 |
Mat::row(int y) const | 返回第y行的矩阵,data共用,创建新的函数头 |
Mat::col(int x) const | 返回第x列的矩阵,data共用,创建新的函数头 |
Mat::rowRange(int startrow, int endrow) const | 返回startrow-endrow全部行矩阵 |
Mat::rowRange(const Range& r) const | 返回r范围内全部行的矩阵 |
Mat::colRange(int startcol, int endcol) const | 返回startcol-endcol全部列的矩阵 |
Mat::colRange(const Range& r) const | 返回r范围内的全部列的矩阵 |
因为取行、列都属于内存不变,所以进行列之间拷贝时,需要copyto操作 | |
4.提取对角线矩阵 | 对矩阵取反z型对角线 |
Mat diag(int d=0) const | .diag(0)取对角线上的矩阵 |
static Mat diag(const Mat& d) | 根据主对角线矩阵创建正方形矩阵 |
5.克隆函数 | |
Mat clone() const CV_NODISCARD | 从矩阵头到数据全部进行拷贝 |
void copyTo( OutputArray m ) const | 拷贝给已有矩阵m,若m尺寸类型不合适,会重新分配内存 |
void copyTo( OutputArray m, InputArray mask ) const | mask与this尺寸相同,mask中非0元素可进行拷贝 |
void convertTo( OutputArray m, int rtype, double alpha=1, double beta=0 ) const | 将this转换类型输出给m,m为单独的Mat |
void assignTo( Mat& m, int type=-1 ) const | 内部封装了convertTo |
Mat& operator = (const Scalar& s) | 将数组中元素转换为s的值,类型不变 |
Mat& setTo(InputArray value, InputArray mask=noArray()) | 更高级的变量,能根据mask转换数组中的value |
6.改变2维矩阵shape或通道数,在不复制data的前提下 | 不增不减元素个数,不拷贝数据 |
Mat reshape(int cn, int rows=0) const | 重新排列新的通道数,新的行数 |
Mat reshape(int cn, int newndims, const int* newsz) const | |
Mat reshape(int cn, const std::vector& newshape) const | 以上是对矩阵的reshape |
7.矩阵转置 | |
MatExpr t() const | 返回了一个临时转置变量可进一步参与复杂矩阵表达 |
8.矩阵反转 | |
MatExpr inv(int method=DECOMP_LU) const | 返回一个临时反转矩阵 |
9.矩阵间元素相乘或相除,等于* | |
MatExpr mul(InputArray m, double scale=1) const | 矩阵乘法 |
10.矩阵叉乘 | |
Mat cross(InputArray m) const | 必须是两个3元素浮点型vectors相乘 |
11.矩阵点乘 | |
double dot(InputArray m) const | 对点乘的两个矩阵视为1维矩阵进行点乘 |
12.根据需求返回特定类型矩阵 | |
static MatExpr zeros(int rows, int cols, int type) | 返回全0矩阵 |
static MatExpr zeros(Size size, int type) | |
static MatExpr zeros(int ndims, const int* sz, int type) | |
static MatExpr ones(int rows, int cols, int type) | 返回全1矩阵 |
static MatExpr ones(Size size, int type) | |
static MatExpr ones(int ndims, const int* sz, int type) | |
static MatExpr eye(int rows, int cols, int type) | 返回对角矩阵 |
static MatExpr eye(Size size, int type) | |
13.构造新的数组data | |
void create(int rows, int cols, int type) | |
void create(Size size, int type) | |
void create(int ndims, const int* sizes, int type) | |
void create(const std::vector& sizes, int type) | |
14.增加引用计数 | |
void addref() | 增加当前矩阵的引用计数,是一个隐式调用的原子操作,是安全的,所以我们是不需要关心这个的使用的 |
15.预留空间 | |
void reserve(size_t sz) | 一个类似STL vector的预留行操作 |
void reserveBuffer(size_t sz) | 预留bytes操作,如果重新申请空间了,之前存在的内容会丢失 |
void resize(size_t sz) | 改变矩阵行的数量,如果reallocated了,min(sz,rows) |
void resize(size_t sz, const Scalar& s) | 改变矩阵行数,新增元素用s初始化 |
16.增加、移除元素 | |
void push_back(const _Tp& elem) | |
void push_back(const Mat_<_Tp>& elem) | |
void push_back(const std::vector<_Tp>& elem) | 给matrix最底层增加元素,元素类型和列数要与matrix保持一致 |
void push_back(const Mat& m) | 给matrix增加line |
void pop_back(size_t nelems=1) | 移除matrix元素,从底层开始 |
17.ROI区域操作 | |
void locateROI( Size& wholeSize, Point& ofs ) const | 定位当前矩阵的头在父矩阵中的位置,和父矩阵的大小 |
Mat& adjustROI( int dtop, int dbottom, int dleft, int dright ) | 修改当前矩阵,在父矩阵上下左右四个方向上的变化 |
18.矩阵提取,只产生新的矩阵头 | |
Mat operator()( Range rowRange, Range colRange ) const | ()操作,行范围列范围 |
Mat operator()( const Rect& roi ) const | 根据矩阵范围提取 |
Mat operator()( const Range* ranges ) const | |
Mat operator()(const std::vector& ranges) const | |
19.判断性函数 | |
bool isContinuous() const | 判断数组是否在内存上没有间断,连续一体的 |
bool isSubmatrix() const | 判断矩阵是否为子矩阵 |
bool empty() const | data是否为NULL |
20.取值操作 | |
size_t elemSize() const | 当type为16UC3时,elemSize=16bit/8*3=6bytes |
size_t elemSize1() const | elemSize/channels |
int type() const | 元素类型 |
int depth() const | CV_16S,这种 |
int channels() const | 通道数 |
size_t step1(int i=0) const | step/eleSize1() |
size_t total() const | 数组元素个数 |
size_t total(int startDim, int endDim=INT_MAX) const | 某个子数组startDim-endDim维度中的元素个数 |
int checkVector(int elemChannels, int depth=-1, bool requireContinuous=true) const | 检测是否为一个2d或3d矩阵 |
除了以上的函数,Mat中还包含了at,ptr,begin,end,操作符,更多的是对图像中元素的取或写操作~
总结:
1.通过对Mat成员函数的调查,发现其成员函数数量众多,功能比较全面,但万变不离其宗,是"增删查改“的扩充与完善。
2.通过阅读Mat的成员函数,我想大家都会对Mat所处理的内存也会有一定的了解了,而我们写一个算法,其实最终就是对这块内存的一顿操作,更好的了解内存结构可以帮助我们更好的实现算法~
3.我想通过对Mat的发问及了解,Mat中一些变量和函数也不在变得神秘,对于读源码也有很大的帮助,今天就先记录到这里吧~