文章目录
*
–
+ 一. Mat数据类型介绍
+ 二. Mat的常用操作
+
* ① 创建Mat对象,常用的Mat构造函数
* ② Mat的行与列相关的操作
* ③ 拷贝和转换
* ④ Mat类常用的成员属性
* ⑤ 图像的基本信息
* ⑥ 按照类型生成图像矩阵
一. Mat数据类型介绍
-
首先
Mat
数据你不需要手动管理它的内存,如果你传递了一个已经存在的Mat
对象,它已经为矩阵分配了所需的内存空间,它将被重用. -
Mat
包含两个数据部分的类:矩阵头(包含诸如矩阵大小,用于存储的方法,存储的矩阵地址等信息)和指向包含像素值矩阵(根据选择的存储方法而有不同的维度)的指针.矩阵头大小是个常量,不同大小的图像的矩阵大小各不相同,通常矩阵大小要比图像大小大几个数量级. -
opencv是一个图像处理库,其中包含大量图像处理函数.为了解决计算难题,多数情况下选用库中的多个函数来实现计算功能,常见的做法是将图像传递给函数.而图像处理算法的计算量往往非常大,所以要通过避免不必要的图像复制来进一步提升程序的运行速度.为了解决上述问题,Opencv采用了一种引用计数系统.
具体做法是,每个Mat对象有其各自的头,两个Mat对象可以通过将矩阵指针指向同一块地址来共享一个矩阵,复制操作只复制Mat头和指向矩阵的指针,而不是复制数据本身
#include "MyOpencv.h"
string imagePath = IMAGE_PATH + "\\lena.jpg";
int main()
{
Mat A, C;
cout << "A.size = " << sizeof(A) << " C.size = " << sizeof(C) << endl;
A = imread(imagePath);
cout << "A.size = " << sizeof(A) << " A.data = " <<
A.cols * A.rows * A.channels() * A.elemSize() << endl;
Mat B(A);
C = A;
return 0;
}
解析:
上述所有的对象均指向同一个数据矩阵,对矩阵的任何变动均会影响所有的对象.在实际的示例中,不同的对象只是对同一数据的不同方式的访问,尽管如此,不同 MAT
对象的头各不相同.问题来了,如果像素矩阵可以属于多个 MAT
对象,那么当它不需要再次被使用时,由谁来负责清空?答案是:通过引用计数机制来实现,由最后一个使用它的对象来清空.每次拷贝MAT对象头时,计数器便会加1;当对MAT对象头进行清空时,此计数会减一.当计数器值为零的时候,矩阵会被释放.当需要对矩阵本身进行复制的时候,Opencv提供了如下两个方法
Mat F = A.clone();
Mat G;
A.copyTo(G);
修改F或者G不会影响A所指向的矩阵,需要记住以下几点:
- Opencv函数,输出图像分配时是自动的(除非另行规定)
- 无需考虑OPencv中的C++的接口的内存管理
- 赋值操作符和拷贝构造函数仅仅复制MAT对象头和那个data指针
- 图像的基本矩阵可以利用cv::Mat::clone() 和cv::Mat::copyTo()两个函数进行复制
二. Mat的常用操作
① 创建Mat对象,常用的Mat构造函数
基本: 尺寸+类型
Mat(int rows,int cols,int type);
Mat(Size size,int type);
Mat(int ndims,const int * sizes,int type);
Mat(cosnt std::vector<int>& sizes,int type);
首先解释下type变量的含义
在构造哈数中,通常可以看到需要输入一个整型的变量type,这个参数通过宏定义在相关头文件中.用来指明Mat对象的存储的数据类型(主要是data部分)
CV_[位数][带符号与否][类型前缀]C[通道数]
CV_8UC1 -> 表示8位单通道无符号char类型数组,
CV_32FC2表示一个2通道的32位浮点型数组.
depth:深度
CV_8U:
bool或者ucharCV_8S:
schar或者charCV_16U:
ushortCV_16S:
shortCV_32S:
int 或者unsigned intCV_32F:
floatCV_64F:
double
Mat有一个type()函数可以返回该Mat的类型.类型表示了矩阵中元素的类型以及矩阵通道个数,它是一系列的预定义的常量,其命名规则为CV_(位数) + (数据类型) + (通道数)
实例:
#include"MyOpencv.h"
#include
int main()
{
Mat m1 = Mat(3, 3, CV_8UC1);
Size size(3, 4);
Mat m2 = Mat(size, CV_8UC1);
int sizes[] = { 2,3 };
Mat m3 = Mat(2, sizes, CV_8UC1);
vector<int> v;
v.push_back(2);
v.push_back(3);
v.push_back(3);
Mat m4 = Mat(v, CV_8UC3);
return 0;
}
尺寸 + 颜色
Mat(int rows,int cols,int type,const Scalar& s);
Mat(Size size,int type,const Scalar& s);
Mat(int ndims,const int* sizes,int type,const Scalar& s);
Mat(const std::vector<int>& sizes,int type,cosnt Scalar& s);
测试代码:
#include "MyOpencv.h"
#include
int main()
{
Mat m1 = Mat(3, 3, CV_8UC1, cv::Scalar(0));
Mat m2 = Mat(4, 4, CV_8UC3, cv::Scalar(0,0, 255));
Mat m3 = Mat(cv::Size(3, 4), CV_8UC3, cv::Scalar(1, 2, 3));
int sizes[] = { 2,3 };
Mat m4 = Mat(2, sizes, CV_8UC1, cv::Scalar(1));
vector<int> v;
v.push_back(4);
v.push_back(3);
Mat m5 = Mat(v, CV_8UC1, cv::Scalar(8));
cout << "m1 = \n" << m1 << endl;
cout << "m2 = \n" << m2 << endl;
cout << "m3 = \n" << m3 << endl;
cout << "m4 = \n" << m4 << endl;
cout << "m5 = \n" << m5 << endl;
system("pause");
return 0;
}
结果:
基本+数据
第三种类型就是基本类型中添加数据
data
,也就是尺寸+类型+数据,其中表示数据的是:void* data
Mat(int rows,int cols,int type,void *data,size_t step=AUTO_SETP);
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(cosnt std::vector<int>& sizes,int type,void* data,const size_t* steps = 0);
参数说明:
void* data:
指向实现准备好的用户的数据的指针.矩阵构造器将会取数据和步长参数,而不会分配矩阵数据.取而代之的是,仅仅初始化矩阵的头去指向具体的数据,这意味着数据不会被拷贝.这个操作将会非常的高效,用来处理外部的数据,值得注意的是,这些外部的数据不会被自动释放step:
矩阵每行占据的字节数.这个值必须包含每行补齐的数据.如果不提供这个参数,就假设没有补齐,实际的步长就等于cols*elemSize();
这种带void*data的函数在使用时要注意:
- 这个函数在执行的时候,只是初始化了mat的头,不拷贝数据,也就是使得mat->data指向了这个外部数据的data
- 外部数据data需要另外释放,opencv的内存管理不会对这一部分内存进行释放
- 如果传入的data是一个局部对象的data,后期在传出时必须要进行深拷贝可以传出正确的数据
- 如果传入的数据是用户data,需要自己手动释放掉,如果是opencv的Mat的data会进行自动内存管理.
举例:
#include "MyOpencv.h"
#include
int main()
{
Mat m1 = Mat(3, 4, CV_8UC1, cv::Scalar(2));
Mat m2 = Mat(4, 3, CV_8UC1, (void *)m1.data);
Mat m3 = Mat(cv::Size(2, 3), CV_8UC2, (void *)m1.data);
int sizes[] = { 2,2 };
Mat m4 = Mat(2, sizes, CV_8UC3, (void *)m1.data);
vector<int> v;
v.push_back(4);
v.push_back(1);
Mat m5 = Mat(v, CV_8UC3, (void *)m1.data);
cout << "m1 = \n" << endl;
cout << m1 << endl;
cout << "m2 = \n" << endl;
cout << m2 << endl;
cout << "m3 = \n" << endl;
cout << m3 << endl;
cout << "m4 = \n" << endl;
cout << m4 << endl;
cout << "m5 = \n" << endl;
cout << m5 << endl;
return 0;
}
结果:
Mat + 其他
Mat(const Mat& m);
Mat(const Mat& m,const Range& rowRange,const Range& colRange=Range::all());
Mat(const Mat& m,const Rect& rio);
Mat(const Mat& m,const Range& ranges);
Mat(const Mat& m,const std::vector<Range>& ranges);
参数解释:
Range:
用来指明一个序列的连续的子序列.拥有两个公共成员:
start
和end
.可以使用这两个成员来表示子序列的范围,左开右闭.可以使用Range.all()表示所有.
Rect类型:
创建一个矩形区域,可以用来提取感兴趣区域.前两位为一个坐标,后两位表示偏移量.
注意:
这些构造函数,没有复制数据,构造指向m数据,如果有引用计数器,则递增.当你修改矩阵的首,通过这样的构造函数,对应的m的数据也会被修改,如果你想要不修改,请使用Mat::clone()出一个副本.
#include "MyOpencv.h"
int main()
{
Mat m1 = Mat(10, 10, CV_8UC1,cv::Scalar(6));
Mat m2 = Mat(m1);
Mat m3 = Mat(m1, cv::Range(0, 3), cv::Range::all());
Mat m4 = Mat(m1, cv::Range::all(), cv::Range(0, 3));
Mat m5 = Mat(m1, cv::Rect(4, 4, 2, 2));
cout << "m1 = m2 = \n" << endl;
cout << m2 << endl;
cout << "m3 = \n" << endl;
cout << m1 << endl;
cout << "m4 = \n" << endl;
cout << m4 << endl;
cout << "m5 = \n" << endl;
cout << m5 << endl;
return 0;
}
结果:
② Mat的行与列相关的操作
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;
示例:
#include "MyOpencv.h"
int main()
{
Mat m1 = Mat(5, 5, CV_8UC1, cv::Scalar(1));
Mat m2 = m1.row(0);
Mat m3 = m1.col(1);
Mat m4 = m1.rowRange(0, 2);
Mat m5 = m1.rowRange(cv::Range(0, 2));
Mat m6 = m1.colRange(1, 3);
Mat m7 = m1.colRange(cv::Range(1, 3));
cout << "m1 = \n" << endl;
cout << m1 << endl;
cout << "m2 = \n" << endl;
cout << m2 << endl;
cout << "m3 = \n" << endl;
cout << m3 << endl;
cout << "m4 = \n" << endl;
cout << m4 << endl;
cout << "m5 = \n" << endl;
cout << m5 << endl;
cout << "m6 = \n" << endl;
cout << m6 << endl;
cout << "m7 = \n" << endl;
cout << m7 << endl;
system("pause");
return 0;
}
结果:
③ 拷贝和转换
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;
Mat& setTo(inputArray value,InputArray mask=noArray());
#include "MyOpencv.h"
int main()
{
Mat m1 = Mat::ones(1, 4, CV_32F);
Mat m2 = m1;
Mat m3 = Mat::zeros(1, 4, CV_32F);
m3.copyTo(m1);
cout << m1 << endl;
cout << m2 << endl;
Mat m4 = Mat::ones(1, 5, CV_32F);
m4.copyTo(m1);
cout << m1 << endl;
cout << m2 << endl;
return 0;
}
结果:
#include "MyOpencv.h"
int main()
{
Mat m1 = Mat(3, 3, CV_8UC1, cv::Scalar(2));
m1.at<uchar>(0, 0) = 0;
m1.at<uchar>(2, 2) = 0;
Mat m2 = Mat(3, 3, CV_8UC1, cv::Scalar(1));
m1.copyTo(m2, m1);
cout << "m2 = " << endl;
cout << m2 << endl;
return 0;
}
结果:
m:
目标矩阵.如果m在运算前没有合适的尺寸或者类型,将被重新分配-
rtype:
目标矩阵的类型.因为目标矩阵的通道数与源矩阵一样,所以rtype
也可以看做是目标矩阵的位深度.如果rtype
为负值(一般为-1),目标矩阵(输出矩阵)将使用和源矩阵(输入矩阵)相同的类型. -
alpha:
尺度变换因子(缩放) beta:
附加到尺度变换后的值上的偏移量(可选),即将输入数组元素按比例缩放后添加的值dst(i) = src(i) * scale + (beta,beta,...);
如果没有规定,则默认是0.
说明:
- 如果scale = 1,beta = 0,则不进行比例缩放,也即目标矩阵和源矩阵没有区别
- 如果输入数组与输出数组的类型相同,则函数可以被用于缩放和平移操作
- 如果输入数组和输出数组的类型不同,则用于做类型上的转换
converTo()
在进行转换的时候,输出的通道数与输入的通道数相同,即使你填入的转换类型通道数不同,输出的通道数还是与输入的通道数相同converTo()
支持就地(in-place)操作
#include "MyOpencv.h"
string imagePath = IMAGE_PATH + "\\lena.jpg";
int main()
{
Mat imgColor = imread(imagePath, IMREAD_COLOR);
Mat imgGray = imread(imagePath, IMREAD_GRAYSCALE);
cout << "Original: " << endl;
cout << "ImageColor.type() = " << imgColor.type() << endl;
cout << "ImageGray.type() = " << imgGray.type() << endl;
cout << "--------------------------------------------------" << endl;
Mat imgColorC3;
Mat imgGrayC3;
imgColor.convertTo(imgColorC3, CV_16UC3);
imgGray.convertTo(imgGrayC3, CV_16UC3);
cout << "convertTo CV_16UC3: " << endl;
cout << "ImageColorC3.type() = " << imgColorC3.type() << endl;
cout << "ImageGrayC3.type() = " << imgGrayC3.type() << endl;
cout << "--------------------------------------------------" << endl;
Mat imgColorC1;
Mat imgGrayC1;
imgColor.convertTo(imgColorC1, CV_16UC1);
imgGray.convertTo(imgGrayC1, CV_16UC1);
cout << "convertTo CV_16UC1:" << endl;
cout << "ImageColorC1.type() = " << imgColorC1.type() << endl;
cout << "ImageColorC2.type() = " << imgGrayC1.type() << endl;
cout << "--------------------------------------------------" << endl;
imgColor.convertTo(imgColor, CV_16UC3);
imgGray.convertTo(imgGray, CV_16UC3);
cout << "In place: " << endl;
cout << "imageColor.type() = " << imgColor.type() << endl;
cout << "imageGray.type() = " << imgGray.type() << endl;
return 0;
}
④ Mat类常用的成员属性
int flags;
int dims;
int rows,cols;
uchar* data;
flags详解
-
0-2位代表depth即数据类型(如CV_8U),Opencv的数据类型一共有7种,所以3位即可表示.
-
3-11位代表通道数channels,因为Opencv的最大通道数为512,只需要9位即可表示全部
- 0-11位共同代表type即通道数和数据类型(如 CV_8UC3)
- 12-13位未使用
- 14位代表Mat的内存是否连续,一般由creat创建的mat均是连续的,如果是连续的,将加快数据的访问
-
15位代表改Mat是否为某一个Mat的submatrix,一般通过ROI以及row(),col(),rowRange,colRange()得到的mat均为submatrix.
-
16-31代表magic signature,暂时理解为用来区分Mat的类型,如Mat和SparseMat
#include "MyOpencv.h"
int main()
{
Mat m = Mat(3, 4, CV_8UC3,cv::Scalar(1,2,3));
cout << "m.flags = " << m.flags << endl;
cout << "m.dims = " << m.dims << endl;
cout << "m.rows = " << m.rows << " m.cols = " << m.cols << endl;
cout << "m = " << endl;
cout << m << endl;
m.rows = 2;
m.cols = 2;
cout << "2行2列: " << endl;
cout << m << endl;
m.flags = 0;
cout << "flags = 0之后: m = " << endl;
cout << m << endl;
return 0;
}
结果:
⑤ 图像的基本信息
int type() const;
int depth() const;
int channels() const;
bool empty() const
解析:
类型返回的是一个int类型,定位为宏数据,其对应的值表示的意思如下:
比如0 -> CV_8UC1, 1-> CV_8S_C1,依次类推
⑥ 按照类型生成图像矩阵
这些函数可以生成全是1,全是0,对角矩阵或者按照类型生成矩阵,主要有:
static MatExpr zeros(int rows,int cols,int type);
Mat A;
A = Mat::zeros(3,3,CV_32F);
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);
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<int>& sizes,int type);
实例解析:
#include"MyOpencv.h"
int main()
{
Mat m1 = Mat::zeros(2, 2, CV_8UC1);
Mat m2 = Mat::zeros(cv::Size(3, 2), CV_8UC3);
int sizes[] = { 3,4 };
Mat m3 = Mat::zeros(2, sizes, CV_8UC1);
Mat m4 = Mat::ones(2, 3, CV_8UC1);
Mat m5 = Mat::eye(3, 3, CV_8UC1);
cout << "m1 = " << endl;
cout << m1 << endl;
cout << "m2 = " << endl;
cout << m2 << endl;
cout << "m3 = " << endl;
cout << m3 << endl;
cout << "m4 = " << endl;
cout << m4 << endl;
cout << "m5 = " << endl;
cout << m5 << endl;
return 0;
}
结果:
关于create函数的说明
1)这个是Mat的关键方法之一,大多数新型的Opencv函数和方法对每个输出数组的创建都会调动此方法.
2)这个方法采用的算法如下:
如果当前的数组和形状匹配新的,立即返回.否则释放之前的数据的引用,初始化新的头,为数据分配新的数据,并且引用计数器设置为1.这种设计,使得用户不用显示的指定输出数组的大小和类型,使得变成很方便
Original: https://blog.csdn.net/Fioman_GYM/article/details/124421118
Author: Fioman_Hammer
Title: Opencv_04 图像的数据类型Mat详解
原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/701548/
转载文章受原作者版权保护。转载请注明原作者出处!