1--我们知道Mat是一个图像容器类,这个数据结构由两部分组成: 1--矩阵头--即class Mat类所实例化的类对象所开辟的空间里面存储的数据---就是这个矩阵的信息,当我们以 Mat object;这样声明类对象的时候,也仅仅是创建了一个Mat的信息头,并没有创建矩阵体,也就是说,我们并 没有给将要存储的图像开辟相应的空间 2--矩阵头--包含: 1--矩阵的尺寸----比如---class Mat这个类中的----数据成员rows,cols---就可以指定图像的尺寸 2--存储方法------对应---各种Mat的构造函数 3--存储地址 4--和一个指向----存储所有像素值的矩阵的----指针 2--因此,当在程序中,传递图像并创建拷贝时,大的开销是由矩阵造成的,而不是信息头。 3--OpenCv是一个图像处理库,囊括了大量的图像处理函数,为了解决问题,通常要使用库中的多个函数,因此,在函数中传 递图像是家常便饭的事情.同时,不要忘了我们正在讨论的是计算量很大的图形处理算法,因此,除非万不得已,我们不应该 拷贝大的图像,因为这会降低程序的速度 4--为了搞定这个问题,OpenCv使用---引用计数机制,其思路就是让每个Mat对象有自己的信息头,但共享一个矩阵。通过让矩阵 指针指向同一地址而实现。而拷贝构造函数则只拷贝: 1--信息头 2--矩阵指针 而不拷贝矩阵. /********************************************************************************************* 程序功能: Mat基本图像容器Mat对象信息头,矩阵体的创建,深复制,浅复制详解 编写环境: OpenCv2.4.8+VS2010 地点时间: 陕西师范大学 2016.4.25 作者信息: 九月 **********************************************************************************************/ /********************************【头文件.命名空间包含部分】***********************************/ #include<opencv2/highgui/highgui.hpp> #include<opencv2/core/core.hpp> #include<iostream> using namespace cv; using namespace std; #define MAT_INFO_HEADER_SIZE "Mat图像容器类信息头所占内存空间的大小----->" #define WINDOW_SRC_NAME "【原始图像】" #define WINDOW_DST_NAME "【复制构造函数图像】" #define WINDOW_ASSIGN_NAME "【赋值图像】" #define WINDOW_ROI_NAME "【ROI原图像部分数据】" #define WINDOW_RANGE_NAME "【Range指定的图像数据部分】" /*****************************************【main()函数】**************************************/ int main(int argc,char** argv) { /**** * 在这里需要说明的一点是,C++中类对象的定义和java中对象的定义是有巨大区别的: *【1】C++中,当定义一个类的对象时,就为其分配了类的存储空间---说的通俗一点,就是说C++中,当用 * 一个类去定义一个类对象时,其实就是将类---进行了实例化,产生了一个类的----具体实例 *【2】而java中,类对象的定义和类对对象的实例化是分开进行的: * 【1】Person lili---类对象的定义---开辟了一个四个字节的空间----lili这个类对象其实相当于 * C++中的一个对象指针,此时并没有产生类的---具体实例 * 【2】Java中只能手动的,用new关键字去实例化一个类的对象,如下所示: * lili=new Person(); ****/ //【1】只创建了信息头部分 Mat src,assign; //【2】我们在这里测试一下,Mat信息头所占内存空间的大小 cout<<MAT_INFO_HEADER_SIZE<<sizeof(src)<<"字节"<<endl; //【3】在这里,为矩阵开辟了内存空间---这相当于---矩阵体 src=imread("D:\\scenery.png",CV_LOAD_IMAGE_COLOR); //【4】显示图片 imshow(WINDOW_SRC_NAME,src); //【5】使用拷贝构造函数,只复制矩阵的信息头-----典型的浅复制 Mat dst(src); assign=src; //【6】显示图片 imshow(WINDOW_DST_NAME,dst); imshow(WINDOW_ASSIGN_NAME,assign); /**** *【1】通过上述代码的结果可知.所有的Mat对象最终都指向了一个也是唯一的一个----数据矩阵。虽然它们的 * 信息头不同,但是通过任何一个对象对图像矩阵所做的改变也会影响其它对象 *【2】实际上,不同的Mat类对象,只是访问相同数据的不同途径而已 ****/ /**** *【1】这里介绍一个比较厉害的功能:你可以创建只引用---图像矩阵部分数据---的信息头。比如想要创建一个 * 感兴趣的区域(ROI),你只需要创建包含边界信息的信息头 *【2】实例如下所示: * Mat dstROI(src,Rect(0,0,100,100)) * Mat dstROI_1(Range:all(),Range(1,3)) ****/ //【7】使用一个矩形,定义ROI区域 Mat dstROI(src,Rect(0,0,200,200)); //【8】创建窗口+显示图像 namedWindow(WINDOW_ROI_NAME,CV_WINDOW_AUTOSIZE); imshow(WINDOW_ROI_NAME,dstROI); //【9】用行rows和列cols截取原图指定区域的图像 //【10】指定的src图像的区域包括图像的所有行和从第0列到第199列 //【11】Mat Mat::operator()( Range _rowRange, Range _colRange ) const----为src对象的子数组创建 //新的信息头,底下的相当于src.colRange() Mat dstRange=src(Range::all(),Range(0,200)); namedWindow(WINDOW_RANGE_NAME,CV_WINDOW_AUTOSIZE); imshow(WINDOW_RANGE_NAME,dstRange); /** *【1】现在你也许会问,如果矩阵属于多个Mat对象,那么当不在需要它时,谁来负责清理呢?答案是:最后一个 * 使用它的对象。通过引用计数机制来实现.无论什么时候,有人拷贝一个Mat对象的信息头,都会增加矩阵的 * 引用次数;反之,当一个头被释放后,这个计数减一;当计数为零时,矩阵会被清理。 *【2】在这块我们通过Mat src这样的方法创建的类对象都是----类的静态对象,在程序运行的过程中,这样的对象 * 占用的空间的分配和释放的时间点是固定的 ***/ /** *【1】但是,某些时候,你仍会想拷贝矩阵本身(不只是信息头和矩阵指针),这时你可以用---深复制---函数: * Mat clone() const; * void copyTo( OutputArray m ) const; * void copyTo( OutputArray m, InputArray mask ) const; **/ Mat dstDeep1=src.clone(); Mat dstDeep2; src.copyTo(src); //【12】经过深复制后,这时,我们再改变,就再也不会影响原图像了 //【13】比如说,我们现在将图像中的所有元素都置为白色 //【14】存取彩色图像的像素 for(int i=0;i<dstDeep1.rows;i++) { for(int j=0;j<dstDeep1.cols;j++) { dstDeep1.at<Vec3b>(i,j)[0]=255;//蓝色通道 dstDeep1.at<Vec3b>(i,j)[1]=255;//红色通道 dstDeep1.at<Vec3b>(i,j)[2]=255;//绿色通道 } } imshow("【原始图像】",src); imshow("【经过深复制处理过后的图像】",dstDeep1); waitKey(0); } 现在,总结一下,我们需要记住的是: 1--OpenCv函数中输出图像的内存分配是自动完成的(如果不是特别指定的话) 2--使用OpenCv的C++接口时,不需要考虑内存释放问题 3--复制运算符和拷贝构造函数只拷贝---信息头 4--使用函数clone()或者copyTo()来拷贝一副图像的矩阵
时间: 2024-10-29 00:53:46