打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
视觉SLAM——OpenCV之Mat结构详解 数据成员和构造函数 创建Mat方法 遍历Mat方法

前言

OpenCV1时代采用基于C语言接口构建函数库,使用名为IplImage的结构体在内存中存储图像,其问题在于需要用户手动管理内存,如果不手动释放内存会造成内存泄漏。
OpenCV2引入面向对象编程思想,加入了一个c 接口,使用Mat类数据结构作为主打,可以实现自动内存管理,且扩展性大大提高。

Mat概述

对于Mat类,首先要知道的是
1)不必手动为其开辟空间;
2)不必再在不需要时将空间释放。
但手动做还也是可以的:大多数OpenCV函数仍会手动地为输出数据开辟空间。当传递一个已经存在的 Mat 对象时,开辟好的矩阵空间会被重用。

Mat是一个类,由两个数据部分组成:矩阵头(包含矩阵尺寸、存储方法、存储地址等)和一个指向存储所有像素值矩阵的指针。

Mat类最重要的一点是浅拷贝和深拷贝问题。由于OpenCV处理图像时很多时候没有必要重新复制一份图像矩阵,因而采用了引用计数机制。其思路是让每个Mat对象有自己的信息头,但共享一个图像矩阵(矩阵指针指向同一地址)。赋值运算符和拷贝构造函数只复制矩阵头和矩阵指针,而不复制矩阵。

Mat A , C;A = imread(argv[1], CV_LOAD_IMAGE_COLOR); // 为矩阵开辟内存Mat B(A);                                 // 拷贝构造函数C = A;                                    // 赋值

这里A、B、C矩阵头不同,但指向了相同的图像矩阵,其中一个对象对矩阵数据进行改变也会影响其他对象。如果矩阵属于多个Mat对象,由最后一个使用它的对象进行内存清理。这通过引用计数来判断,每复制一个Mat对象,计数器加一,每释放一个计数器减一,当计数器值为0时矩阵就会被清理。

如果需要进行对象的深拷贝可以采用clone()函数或者copyTo()。

Mat A;A = imread(argv[1], CV_LOAD_IMAGE_COLOR); Mat B = A.clone();Mat C;A.copyTo(C);

Mat类的数据成员

	/*	flag的详细解释可以看 https://blog.csdn.net/yiyuehuan/article/details/43701797	0-2位 depth:每一个像素的位数,也就是每个通道的位数,即数据类型(如CV_8U)		enum { CV_8U=0, CV_8S=1, CV_16U=2, CV_16S=3, CV_32S=4, CV_32F=5, CV_64F=6 }		8U 表示 8 位无符号整数,16S 表示 16 位有符号整数,64F表示 64 位浮点数	3-11位 number of channels:代表通道数channels,最高512位	0-11位共同代表type:矩阵元素的类型,即通道数和数据类型(如CV_8UC3、CV_16UC2)	14位 continuity flag:代表Mat的内存是否连续	15位 submat flag:代表该Mat是否为某一个Mat的submatrix	16-31位 the magic signature:用来区分Mat的类型,如果Mat和SparseMat	*/	int flags;	    //矩阵的维数,一般大于2    int dims;        //矩阵的行数与列数,超过2维矩阵时(-1,-1)    int rows, cols;        //指向存放矩阵数据的内存    uchar* data;        //用来控制ROI区域,来获取一些图像的局部切片,减少计算量或者特殊需求的。    const uchar* datastart;    const uchar* dataend;    const uchar* datalimit;    	//如果需要创建一个新矩阵的内存空间,会调用MatAllocator类作为分配符进行内存的分配。    MatAllocator* allocator;        //interaction with UMat    //UMatData结构体总有一个成员refcount:记录了矩阵的数据被其他变量引用了多少次    UMatData* u;        //返回矩阵大小    MatSize size;        //矩阵元素寻址,step[i]表示第i维的总大小,单位字节    //对于2维矩阵:step[0]是矩阵中一行元素的字节数,step[1]是矩阵中一个元素的字节数    //以下公式可以得到Mat中任意元素地址    //addr(M{i,j})=M.data M.step[0]∗i M.step[1]∗j;    MatStep step;

此外其他版本还存在以下成员:
elemSize :矩阵一个元素占用的字节数,例如:type是CV_16SC3,那么elemSize = 3 * 16 / 8 = 6 bytes。
elemSize1 :矩阵元素一个通道占用的字节数,例如:type是CV_16CS3,那么elemSize1 = 16 / 8 = 2 bytes = elemSize / channels。

Mat类的构造函数

//1、默认构造函数,无参数Mat::Mat();	//2、行数为rows,列数为cols,类型为type(如CV_8UC1、CV_16UC2)Mat(int rows, int cols, int type);//3、矩阵大小为size,类型为type//注意size的构造函数是Size_(_Tp _width,_Tp _height) 先列后行Mat(Size size, int type);//4、行数为rows,列数为cols(或矩阵大小为size),类型为type,所有元素初始化为s//Scalar表示具有4个元素的数组,如Scalar(a,b,b),其原型为Scalar_<double>Mat(int rows, int cols, int type, const Scalar& s);Mat(Size size, int type, const Scalar& s);//5、矩阵维数为ndims,sizes为指定ndims维数组形状的整数数组,所有元素初始化为sMat(int ndims, const int* sizes, int type);Mat(const std::vector<int>& sizes, int type);Mat(int ndims, const int* sizes, int type, const Scalar& s);Mat(const std::vector<int>& sizes, int type, const Scalar& s);//6、拷贝构造函数,将m赋值给新创建的对象,浅拷贝Mat(const Mat& m);//7、行数为rows,列数为cols,类型为type,矩阵数据为data,直接使用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(const std::vector<int>& sizes, int type, void* data, const size_t* steps=0);//8、创建的新图像为m的一部分,范围由rowRange和colRange指定,新图像与m共用图像数据,浅拷贝Mat(const Mat& m, const Range& rowRange, const Range& colRange=Range::all());Mat(const Mat& m, const Range* ranges);Mat(const Mat& m, const std::vector<Range>& ranges);//创建的新图像为m的一部分,具体的范围由矩阵对象roi指定//Rect的成员函数有x,y,width,height,分别为左上角点的坐标好矩阵宽和高Mat(const Mat& m, const Rect& roi);

创建Mat对象

1、使用Mat()构造函数

Mat M(2,2, CV_8UC3, Scalar(0,0,255)); 

需要指定行数、列数、存储元素的数据类型以及每个矩阵点的通道数。

2、使用Mat()构造函数2

int sz[3] = {2,2,2}; Mat L(3,sz, CV_8UC(1), Scalar::all(0));

常用于创建一个超过两维的矩阵:指定维数,然后传递一个指向一个数组的指针,这个数组包含每个维度的尺寸,其余的相同

3、为已存在IplImage指针创建信息头(一般不用)

IplImage* img = cvLoadImage("greatwave.png", 1);Mat mtx(img); // convert IplImage* -> Mat

4、利用create()函数
这个创建方法不能为矩阵设初值,它只是在改变尺寸时重新为矩阵数据开辟内存

 M.create(4,4, CV_8UC(2));//4X4的图像矩阵,通道数为2,没有初值

5、MATLAB形式的初始化方式: zeros(), ones(), eyes()

 Mat E = Mat::eye(4, 4, CV_64F);//4X4的单位矩阵      Mat O = Mat::ones(2, 2, CV_32F);//2X2的全为1矩阵 Mat Z = Mat::zeros(3,3, CV_8UC1);//3X3的零矩阵

6、小矩阵使用逗号分隔式初始化

 Mat C = (Mat_<double>(3,3) << 1, 0, 0, 0, 1, 0, 0, 0, 1); //3X3的单位矩阵

7、使用clone()或copyto()为已存在对象创建新信息头

Mat RowClone = C.row(1).clone();//复制 C中的第2行[0,1,0]作为新矩阵(深拷贝)

遍历Mat对象

首先假设需要对图像将那些颜色空间缩减,如imgnew=imgold/1010img_{new} = img_{old} / 10 * 10imgnew​=imgold​/10∗10。对于较大的图像,一般不是对每个像素点每个通道的值进行如上计算,而是预先计算所有可能的值,构建查找表,然后利用查找表对其直接复制。

	//构建查找表table	int divideWith;     cin >> divideWith;    uchar table[256];     for (int i = 0; i < 256;   i)       table[i] = divideWith* (i/divideWith);

1、ptr指针单像素单通道值访问
即通过uchar* ptr =img.ptr<char>(i); 得到第i行首地址,然后采用指针运算( )或者操作符[]遍历。

Mat& ScanImage(Mat& img,const uchar* const table){	CV_Assert(img.depth() != sizeof(uchar))//只接收uchar类型矩阵	int channels = img.channel();	int nRows = img.rows * channels;//注意行数乘以通道数	int nCols = img.cols;		if(img.isContinuous())//如果存储连续则可以使用一次循环	{		nCols *= nRows;		nRows = 1; 	}	uchar* ptr;	for(int i=0; i<nRows;   i)	{		ptr = img.ptr<char>(i);		for( j=0; j<nCols;   j)		{			ptr[j] = table[ptr[j]]			//*ptr   =  table[*ptr]		}	}	return I;}

2、ptr指针单像素访问
利用cv::Vec3b *ptr =img.ptr<cv::Vec3b>(i);得到第i行第一个像素的3个通道地址。

Mat& ScanImage(Mat& img,const uchar* const table){	CV_Assert(img.depth() != sizeof(uchar))//只接收uchar类型矩阵	int channels = img.channel();	int nRows = img.rows;//每一行	int nCols = img.cols;	Vec3b *ptr;	for(int i=0; i<nRows;   i)	{		ptr = img.ptr<Vec3b>(i);		for( j=0; j<nCols;   j)		{			ptr[j][0] = table[ptr[j][0]];			ptr[j][1] = table[ptr[j][1]];			ptr[j][2] = table[ptr[j][2]];		}	}	return I;}

3、对data操作
data会从Mat中返回指向矩阵第一行第一列的指针。常用来检查图像是否被成功读入(如果指针为NULL则表示无输入)。当矩阵式连续存储时,可以通过data遍历矩阵。
也可以采用addr(M{i,j})=M.data M.step[0]∗i M.step[1]∗j;得到每个元素的地址,但是不常用。

if(I.isContinuous()){	uchar* p = img.data;	for( unsigned int i =0; i < ncol * nrows;   i)    	*p   = table[*p];}

4、迭代器iterator访问
使用迭代器操作像素,与STL库用法相似,只需要获得图像矩阵的begin和end,然后增加迭代直至从begin到end。将*操作符添加在迭代指针前,即可访问当前指向的内容。
迭代器可以采用MatIterator_以及Mat的模板子类Mat_,它重载了operator()。

MatIterator_<uchar> it = img.begin<uchar>();Mat_<uchar>::iterator it = img.begin<uchar>();
Mat& ScanImage(Mat& I,const uchar* const table){	CV_Assert(img.depth() != sizeof(uchar));	const int channels = img.channels();	switch(channels)	{		case 1:			{				Mat_<uchar>::iterator it = img.begin<uchar>();				Mat_<uchar>::iterator itend = img.end<uchar>();				for(;it != itend;   it)					*it = table[*it];				break;			}		case 3:			{				Mat_<Vec3b>::iterator it = img.begin<uchar>();				Mat_<Vec3b>::iterator itend = img.end<uchar>();				for(; it != itend ;   it)				{					(*it)[0] = table[(*it)[0]];					(*it)[1] = table[(*it)[1]];					(*it)[2] = table[(*it)[2]];				}			}	}	return img;}

5、动态地址计算(at)
该方法一般用于获取或更改图像中的某个元素(或随机元素),而不用于进行全图扫描。它的基本用途是要确定你试图访问的元素的所在行数与列数。
在debug模式下该方法会检查你的输入坐标是否有效或者超出范围,如果坐标有误则会输出一个标准的错误信息;在release模式下,它和其他的区别仅仅是对于矩阵的每个元素,都会获取一个新的行指针,然后通过该指针和[]操作获取元素。

Mat& ScanImage(Mat& img,const uchar* const table){	CV_Assert(img.depth() != sizeof(uchar));	const int channels = img.channels();	switch(channels)	{		case 1:			{				for(int i=0; i<img.rows;   i)					for(int j=0; j<img.cols;   i)						img.at<uchar>(i,j) = table[img.at<uchar>(i,j)];				break;			}		case 3:			{				for(int i=0; i<img.rows;   i)				{					for(int j=0; j<img.cols;   i)					{						img.at<vec3b>(i,j)[0] = table[img.at<vec3b>(i,j)[0]];						img.at<vec3b>(i,j)[1] = table[img.at<vec3b>(i,j)[1]];						img.at<vec3b>(i,j)[2] = table[img.at<vec3b>(i,j)[2]];					}				}				break;			}	}	return img;}

6、LUT函数
LUT(Look up table)被官方推荐用于实现批量图像元素查找和更改。对于修改像素值,OpenCV提供函数operationsOnArray:LUT()<lut>,直接实现该操作,而不需要扫描图像。

//建立mat类对象用于查表Mat LookUpTable(1,256,CV_8U);uchar* p = LookUpTable.data;for(int i=0; i<256;   i)	p[i] = table[i];//调用函数(I时输入,J是输出)LUT(I, LookUpTable, J);

一般认为,采用ptr指针访问图像像素的效率更高;而迭代器则被认为是更安全的方式,仅需要获得图像矩阵的begin和end;at方法一般不用于对图像进行遍历;而调用LUT函数可以获得最快的速度,因为OpenCV库可以通过英特尔线程架构启用多线程。其他也存在一些比较偏的遍历方法,不过这几种最为常见,效率和安全性也相对较好。

来源:http://www.icode9.com/content-4-148551.html
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
Opencv系列1.4--图像和大型数据类型
图像锐化(拉普拉斯算子):
opencv Mat 像素操作
opencv中mat,cvmat,Iplimage构造体定义以及格式互相转换
OpenCV参考手册之Mat类详解(三)
OpenCV中:CvArr、CvMat、IplImage、cv::Mat和cv::InputArray的相关总结
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服