学习OpenCV2——Mat之数据结构

      OpenCV中,Mat是最基础的数据处理单元,矩阵可以用Mat表示,图像可以用Mat表示,向量也可以用Mat表示,它们之间有何区别?

      理解好Mat对学好OpenCV有很大帮助。相信很多人和我一样,接触OpenCV时,被Mat弄得一头雾水。我这里写一些我对Mat的理解。


1. Mat的存储结构

      平时我们看见的图像,在计算机眼里其实是个大的数字矩阵。图像涉及到通道,而且像素值是正整数,而我们印象中的矩阵是没有这些特点的。OpenCV为了把图像和一般的矩阵统一到Mat中,特地给矩阵添加了一个矩阵头。    

OpenCV参考手册之Mat类详解



    Mat本质上是有由一个矩阵头和一个指针组成的类。矩阵头包含了矩阵的大小,存储的方法,存储的地址等,指针指向了一个包含像素值的矩阵。

    矩阵头的大小是恒定的,而矩阵本身的大小因图像的不同而不同,通常是较大的数量级。OpenCV 是图像处理库,一般图像的像素值矩阵非常大,矩阵头能让我们对图像进行一些操作时变得更加快捷和方便。


    我们来看看Mat的存储形式。Mat和Matlab里的数组格式有点像,但一般是二维向量,如果是灰度图,一般存放<uchar>类型;如果是RGB彩色图,存放<Vec3b>类型。

单通道灰度图数据存放格式:

多通道的图像中,每列并列存放通道数量的子列,如RGB三通道彩色图:

   
注意通道的顺序反转了:BGR。通常情况内存足够大的话图像的每一行是连续存放的,也就是在内存上图像的所有数据存放成一行,这中情况在访问时可以提供很大方便。可以用 isContinuous()函数来判断图像数组是否为连续的。
    比如我们读入一个100×100的灰度图像img1和一个100×100的RGB彩色图像img2。然后输出各自的矩阵,会发现img1输出了一个100×100的矩阵,而img2输出了一个300×100的矩阵。因为RGB图像每个像素点上有3个值,而灰度图像只有1个,尽管图像的size是一样的。

2. 深入Mat类

    Mat是矩阵,对矩阵的操作有很多的方法,这里我们只介绍Mat类里面包含的方法,其他的方法再单独介绍。
    Mat类非常大,大多数是各种函数和模板,还有一些内部的定义和结构体什么的。让我们来分类了解下。

2.1 矩阵的构造、初始化、释放

2.1.1 只构造矩阵头,无数据

Mat();
//构造2维矩阵头    
Mat(int rows, int cols, int type);
Mat(Size size, int type);
//构造n维矩阵头 
Mat(int ndims, const int* sizes, int type);
//创建矩阵头
void create(int rows, int cols, int type);
void create(Size size, int type);
void create(int ndims, const int* sizes, int type);
//创建一个指定行数的矩阵头    
 Mat row(int y) const;
//创建一个指定列数的矩阵头
Mat col(int x) const;
//创建一个指定行数范围的矩阵头
Mat rowRange(int startrow, int endrow) const;
Mat rowRange(const Range& r) const;
//创建一个指定列数范围的矩阵头
Mat colRange(int startcol, int endcol) const;
Mat colRange(const Range& r) const;

2.1.2 构造矩阵并初始化

//构造2维矩阵并赋值
Mat(int rows, int cols, int type, const Scalar& s);
Mat(Size size, int type, const Scalar& s);
//构造n维矩阵并赋值
Mat(int ndims, const int* sizes, int type, const Scalar& s);
//复制矩阵
Mat(const Mat& m);  //Mat m3(m2);将m2复制到m3中,m2和m3共用同一内存位置的数据,也就是说改变m2,m3会根根变,同理
//根据data构造矩阵
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中提取一个区域来构造矩阵
Mat(const Mat& m, const Range& rowRange, const Range& colRange=Range::all());
Mat(const Mat& m, const Rect& roi);
Mat(const Mat& m, const Range* ranges);

//根据特定对象来构造矩阵,其实是其他数据类型转Mat,false表示不包含数据,true表示包含数据
//将CvMat转换成Mat,默认不复制数据。
Mat(const CvMat* m, bool copyData=false);
//将CvMatND转换成Mat,默认不复制数据。
Mat(const CvMatND* m, bool copyData=false);
//将IplImage转换成Mat,默认不复制数据。
 Mat(const IplImage* img, bool copyData=false);
 //根据std::vector构造矩阵,默认不复制数据
template<typename _Tp> explicit Mat(const vector<_Tp>& vec, bool copyData=false);
//根据cv::Vec构造矩阵,默认不复制数据
template<typename _Tp, int n> explicit Mat(const Vec<_Tp, n>& vec, bool copyData=true);
//根据cv::Matx构造矩阵,默认不复制数据
template<typename _Tp, int m, int n> explicit Mat(const Matx<_Tp, m, n>& mtx, bool copyData=true);
//根据2维点构造矩阵
template<typename _Tp> explicit Mat(const Point_<_Tp>& pt, bool copyData=true);
//根据3维点构造矩阵
template<typename _Tp> explicit Mat(const Point3_<_Tp>& pt, bool copyData=true);
//根据comma initializer构造矩阵,默认不复制矩阵
template<typename _Tp> explicit Mat(const MatCommaInitializer_<_Tp>& commaInitializer);

//构造特殊矩阵
static MatExpr zeros(int rows, int cols, int type);
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);
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);
//Mat m1 = Mat::eye(5,5,CV_8UC1);
	
//复制矩阵,包括数据
Mat clone() const;
void copyTo( OutputArray m ) const;   
void copyTo( OutputArray m, InputArray mask ) const;   
void convertTo( OutputArray m, int rtype, double alpha=1, double beta=0 ) const;
void assignTo( Mat& m, int type=-1 ) const;

//从GpuMat下载数据
explicit Mat(const gpu::GpuMat& m);

2.1.3 释放矩阵及其元素

//析构函数
~Mat();   //同Mat::release()。
//释放矩阵
void release();
//释放矩阵数据
void deallocate();

2.2 改变矩阵的结构和元素

//将矩阵所有元素设置为s
Mat& operator = (const Scalar& s);
//根据mask将矩阵部分元素设置为s
Mat& setTo(InputArray value, InputArray mask=noArray());
//改变矩阵的通道数cn、维数dims和行数rows
Mat reshape(int cn, int rows=0) const;
Mat reshape(int cn, int newndims, const int* newsz) const;

2.3 返回矩阵头的信息和指向矩阵的指针

//判断矩阵在内存是否是连续存放
bool isContinuous() const;
//判断一个矩阵是否是另一个矩阵的子矩阵
bool isSubmatrix() const;
//以字节为单位返回元素大小,比如CV_8U返回1,CV_8U3返回3,CV_16U返回2,CV_64FC3返回24    
size_t elemSize() const;
//以字节形式返回元素通道的尺寸,<span style="font-family: Arial, Helvetica, sans-serif;">也就是elemSize()/channels()</span>
size_t elemSize1() const;
//返回矩阵数据类型
int type() const;
//返回矩阵数据深度,<span style="color: rgb(17, 17, 17); font-size: 13px; line-height: 16.848px; white-space: pre-wrap;">CV_8U=0, CV_8S=1, CV_16U=2, CV_16S=3, CV_32S=4, CV_32F=5, CV_64F=6</span>
int depth() const;   
//返回矩阵通道
int channels() const;
//返回step/elemSize1()
size_t step1(int i=0) const;
//判断矩阵是否为空,是返回true
bool empty() const;
//返回矩阵所有元素的和
size_t total() const;
//检查Mat某通道元素矩阵是否是向量,是就返回N
int checkVector(int elemChannels, int depth=-1, bool requireContinuous=true) const;

//返回指向Mat的行指针
uchar* ptr(int i0=0);
const uchar* ptr(int i0=0) const;
//返回指向Mat像素点的指针
uchar* ptr(int i0, int i1);
const uchar* ptr(int i0, int i1) const;
//返回指向第#0行#1列#2通道的指针
uchar* ptr(int i0, int i1, int i2);
const uchar* ptr(int i0, int i1, int i2) const;
//返回指向索引为idx的某个数(不是像素点)的指针
uchar* ptr(const int* idx);
//返回只读型的指向索引为idx的某个数(不是像素点)的指针
const uchar* ptr(const int* idx) const;

2.4 矩阵的数学运算

//返回矩阵对角线元素,d=0,对角线,d>0,对角线下移d单位的斜线上元素,d<0,对角线上移d单位的斜线上元素
Mat diag(int d=0) const;
static Mat diag(const Mat& d);
//计算矩阵的转置
MatExpr t() const;
//计算矩阵的逆,
/*DECOMP_LU:采用的是高斯消去法(Gaussian elimination),只能计算非奇异矩阵的逆,如果矩阵可逆,则函数返回非零值,否则返回0.
DECOMP_SVD:采用的是奇异值分解法(singular value decomposition)。对于奇异矩阵,计算伪逆矩阵,非奇异矩阵则计算逆矩阵,如果矩阵是非奇异的,则返回最小奇异值和最大奇异值的比例,否则返回0.
 DECOMP_CHOLESKY:采用的是Cholesky分解*/
MatExpr inv(int method=DECOMP_LU) const;
//矩阵按元素相乘或相除。比如,Mat C = A.mul(5/B); //等价于divide(A,B,C,5)
MatExpr mul(InputArray m, double scale=1) const;
//计算两向量的叉积(外积,向量积)。
Mat cross(InputArray m) const;
//计算两向量的点积(内积,数量积)。
double dot(InputArray m) const;

2.5 Mat与CvMat、CvMatND、IplImage格式转换

//Mat转CvMat、CvMatND、IplImage,不复制数据
operator CvMat() const;   
operator CvMatND() const;
operator IplImage() const;
	
//CvMat、CvMatND、IplImage转换成Mat,默认不复制数据。
Mat(const CvMat* m, bool copyData=false);
//将CvMatND转换成Mat,默认不复制数据。
Mat(const CvMatND* m, bool copyData=false);
//将IplImage转换成Mat,默认不复制数据。
Mat(const IplImage* img, bool copyData=false);

2.6 不清楚的地方

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

//! template version of the above method
template<typename _Tp> _Tp* ptr(int i0=0);
template<typename _Tp> const _Tp* ptr(int i0=0) const;

template<typename _Tp> _Tp* ptr(int i0, int i1);
template<typename _Tp> const _Tp* ptr(int i0, int i1) const;

template<typename _Tp> _Tp* ptr(int i0, int i1, int i2);
template<typename _Tp> const _Tp* ptr(int i0, int i1, int i2) const;

template<typename _Tp> _Tp* ptr(const int* idx);
template<typename _Tp> const _Tp* ptr(const int* idx) const;

template<typename _Tp, int n> _Tp* ptr(const Vec<int, n>& idx);
template<typename _Tp, int n> const _Tp* ptr(const Vec<int, n>& idx) const;

//! the same as above, with the pointer dereferencing
template<typename _Tp> _Tp& at(int i0=0);
template<typename _Tp> const _Tp& at(int i0=0) const;

template<typename _Tp> _Tp& at(int i0, int i1);
template<typename _Tp> const _Tp& at(int i0, int i1) const;

template<typename _Tp> _Tp& at(int i0, int i1, int i2);
template<typename _Tp> const _Tp& at(int i0, int i1, int i2) const;

template<typename _Tp> _Tp& at(const int* idx);
template<typename _Tp> const _Tp& at(const int* idx) const;

template<typename _Tp, int n> _Tp& at(const Vec<int, n>& idx);
template<typename _Tp, int n> const _Tp& at(const Vec<int, n>& idx) const;

//! special versions for 2D arrays (especially convenient for referencing image pixels)
template<typename _Tp> _Tp& at(Point pt);
template<typename _Tp> const _Tp& at(Point pt) const;

//! template methods for iteration over matrix elements.
// the iterators take care of skipping gaps in the end of rows (if any)
template<typename _Tp> MatIterator_<_Tp> begin();
template<typename _Tp> MatIterator_<_Tp> end();
template<typename _Tp> MatConstIterator_<_Tp> begin() const;
template<typename _Tp> MatConstIterator_<_Tp> end() const;

3. 示例

     我们来看一些实际的例子,加深对Mat的理解。先读入一张图片

                                     Mat img = imread("test.png");

      来看看内存里放了哪些东西。


        flags不知道是什么。。。

      dims = 2.   表明img是矩阵,等于1就是向量了;

      rows = 486,cols=646; 图像尺寸是646×486,也可以用img.size返回图像尺寸;

      Mat的数据都存在了data中,注意到data是指针型的,所以我们访问数据都要用到指针,也就是地址;图像在内存的地址从datastart到dataend,即0X00e3d050到0X00f22f7c,那么图像大小 = 0X00f22f7c - 0X00e3d050 = 0Xe5f2c = 941868 = 486×646×3 = rows×cols×channels;

     重点是step!step定义了矩阵的布局,img.step[0]=1938=646×3,所以step[0]是矩阵一行的大小。step[1]=3,step[1]表示每一列由3组元素组成,其实img.channels()也等于3,step[1]也可以看做通道数。知道了数据的起始地址、矩阵一行的大小和通道数,那么图像上(m,n)点的地址,就可以表示为

                      ptr = datastart + step[0]*m+step[1]*n

     对应的各通道数值的地址就分别为ptr[0]、ptr[1]、ptr[2]。


     还不懂的话可以参照下图进行理解

                       

上面是一个 3 X 4 的矩阵,假设其数据类型为 CV_8UC1,则

M.dims = 2,

M.rows = 3; 

M.cols = 4。

因为是二维矩阵,那么 step 数组只有两个值, step[0] 和 step[1] 分别代表一行的数据大小和一个元素的数据大小,则 M.step[0] == 4, M.step[1] == 1。点(1,2)的地址

ptr12 = addr(M1,2) = M.data +M.step[0]*1 + M.step[1]*2

       假设上面的矩阵数据类型是 CV_8UC3。

M.dims == 2; 

M.channels() == 3;

M.depth() == 0;

M.elemSize() == 3 (每一个元素包含3个uchar值),

M.elemSize1() == 1 (elemSize / channels),

M.step[0] == M.cols * M.elemSize() == 12, 

M.step[1] == M.channels() * M.elemSize1() == M.elemSize() == 3;

点(1,2)处各通道数值的地址分别为ptr12[0],ptr12[1],ptr12[2]

 

再来看看3维情况

                              

上面是一个 3 X 4 X 6 的矩阵,假设其数据类型为 CV_16SC4,也就是 short 类型,在内存中是存成3个4×6矩阵的。

M.dims = 3 ; 

M.channels() = 4 ; 

M.elemSize1() = sizeof(short) = 2 ;
M.elemSize() = M.elemSize1() * M.channels() = M.step[M.dims-1]= M.step[2] = 2 * 4 = 8;
M.step[0] = 4 * 6 * M.elemSize() = 192;
M.step[1] = 6 * M.elemSize() = 48;
M.step[2] = M.elemSize() = 8;
M.step1(0) = M.step[0] / M.elemSize() = 48 / 2 = 96 (第一维度(即面的元素个数) * 通道数);
M.step1(1) = M.step[1] / M.elemSize() = 12 / 2 = 24(第二维度(即行的元素个数/列宽) * 通道数);
M.step1(2) = M.step[2] / M.elemSize() = M.channels() = 4(第三维度(即元素) * 通道数);

那么点(0,1,2)的地址为

ptr = addr(M0,1,2) = M.data +M.step[0]*0 + M.step[1]*1+ M.step[2]*2

对应的各通道的地址为ptr[0]、ptr[1]、ptr[2]、ptr[3]


知道了地址,元素的值就用取值运算符“ * ”,比如上面,*ptr[0] 表示M在点(0,1,2)处第1通道的值。


      

  • 3
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值