OpenCV中,Mat是最基础的数据处理单元,矩阵可以用Mat表示,图像可以用Mat表示,向量也可以用Mat表示,它们之间有何区别?
理解好Mat对学好OpenCV有很大帮助。相信很多人和我一样,接触OpenCV时,被Mat弄得一头雾水。我这里写一些我对Mat的理解。
平时我们看见的图像,在计算机眼里其实是个大的数字矩阵。图像涉及到通道,而且像素值是正整数,而我们印象中的矩阵是没有这些特点的。OpenCV为了把图像和一般的矩阵统一到Mat中,特地给矩阵添加了一个矩阵头。
Mat本质上是有由一个矩阵头和一个指针组成的类。矩阵头包含了矩阵的大小,存储的方法,存储的地址等,指针指向了一个包含像素值的矩阵。
矩阵头的大小是恒定的,而矩阵本身的大小因图像的不同而不同,通常是较大的数量级。OpenCV 是图像处理库,一般图像的像素值矩阵非常大,矩阵头能让我们对图像进行一些操作时变得更加快捷和方便。
我们来看看Mat的存储形式。Mat和Matlab里的数组格式有点像,但一般是二维向量,如果是灰度图,一般存放
单通道灰度图数据存放格式:
多通道的图像中,每列并列存放通道数量的子列,如RGB三通道彩色图:
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维矩阵并赋值
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 explicit Mat(const vector<_Tp>& vec, bool copyData=false);
//根据cv::Vec构造矩阵,默认不复制数据
template explicit Mat(const Vec<_Tp, n>& vec, bool copyData=true);
//根据cv::Matx构造矩阵,默认不复制数据
template explicit Mat(const Matx<_Tp, m, n>& mtx, bool copyData=true);
//根据2维点构造矩阵
template explicit Mat(const Point_<_Tp>& pt, bool copyData=true);
//根据3维点构造矩阵
template explicit Mat(const Point3_<_Tp>& pt, bool copyData=true);
//根据comma initializer构造矩阵,默认不复制矩阵
template 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);
//析构函数
~Mat(); //同Mat::release()。
//释放矩阵
void release();
//释放矩阵数据
void deallocate();
//将矩阵所有元素设置为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;
//判断矩阵在内存是否是连续存放
bool isContinuous() const;
//判断一个矩阵是否是另一个矩阵的子矩阵
bool isSubmatrix() const;
//以字节为单位返回元素大小,比如CV_8U返回1,CV_8U3返回3,CV_16U返回2,CV_64FC3返回24
size_t elemSize() const;
//以字节形式返回元素通道的尺寸,也就是elemSize()/channels()
size_t elemSize1() const;
//返回矩阵数据类型
int type() const;
//返回矩阵数据深度,CV_8U=0, CV_8S=1, CV_16U=2, CV_16S=3, CV_32S=4, CV_32F=5, CV_64F=6
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;
//返回矩阵对角线元素,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;
//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);
template uchar* ptr(const Vec& idx);
template const uchar* ptr(const Vec& idx) const;
//! template version of the above method
template _Tp* ptr(int i0=0);
template const _Tp* ptr(int i0=0) const;
template _Tp* ptr(int i0, int i1);
template const _Tp* ptr(int i0, int i1) const;
template _Tp* ptr(int i0, int i1, int i2);
template const _Tp* ptr(int i0, int i1, int i2) const;
template _Tp* ptr(const int* idx);
template const _Tp* ptr(const int* idx) const;
template _Tp* ptr(const Vec& idx);
template const _Tp* ptr(const Vec& idx) const;
//! the same as above, with the pointer dereferencing
template _Tp& at(int i0=0);
template const _Tp& at(int i0=0) const;
template _Tp& at(int i0, int i1);
template const _Tp& at(int i0, int i1) const;
template _Tp& at(int i0, int i1, int i2);
template const _Tp& at(int i0, int i1, int i2) const;
template _Tp& at(const int* idx);
template const _Tp& at(const int* idx) const;
template _Tp& at(const Vec& idx);
template const _Tp& at(const Vec& idx) const;
//! special versions for 2D arrays (especially convenient for referencing image pixels)
template _Tp& at(Point pt);
template 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 MatIterator_<_Tp> begin();
template MatIterator_<_Tp> end();
template MatConstIterator_<_Tp> begin() const;
template MatConstIterator_<_Tp> end() const;
我们来看一些实际的例子,加深对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通道的值。