OpenCV学习笔记(五):Mat结构
在之前的OpenCV学习笔记(一)用到的几种显示图像的方法中其中一种就是Mat。Mat结构在OpenCV 2.0后才得到广泛应用,相对于OpenCV1.0时代中的IplImage,它有个好处就是不用再手动释放图像内存。而用IplImage格式存储图像的时候就必须在推出前将图像内存release掉,即添加语句cvReleaseImage(&iplImg);,否则会造成内存泄漏。除了这个好处外,它的操作也更加简单,比如用imshow显示图像,imread读取图像等等,跟Matlab有点接近。
Mat是一个类,它由两个数据部分组成:矩阵头和一个指向存储所有像素值的矩阵的指针。其中矩阵头包含了矩阵的尺寸、存储方法、储存地址等信息,由此可以看出矩阵头所占的内存很小,而具体储存所有像素值的矩阵则非常大。举个例子,比如一个班级,矩阵头就相当于储存了班级里有多少人、男女比多少、平均身高、平均体重等信息,而矩阵就储存了班级中所有同学的所有基本信息,每一个同学就相当于是一个像素点,而每个像素点的颜色信息、深度信息等一串数据就相当于是每个同学的身高、体重、性别等一系列信息组成的一串数据。
那么问题来了,假如说我有一张图A,我想要把A图复制给B图,理论上我是不是应该重新开一片内存,用来储存B图的信息?但是仔细想一想,我A图和B图一毛一样,如果我再开一片内存存储B图,那岂不是和存储A图的内存重复啦?是不是会造成内存的浪费。所以OpenCV中的图像存储结构Mat就很好地解决了这个问题。它使用了引用计数机制,该机制的思路就是让每个Mat对象有自己的信息头,但是共享同一个矩阵。也就是让A图和B图的矩阵指针指向同一地址,共用一片内存。复制图像的时候只是复制了矩阵头的信息和矩阵指针,并不是复制了整个矩阵。
这么一来如果不是很熟悉的话使用起来就会有点别扭了。因为通过复制后得到的图像虽然名字不一样,一个叫A,一个叫B,信息头不同,但是通过任何一个对象所做的改变也会影响到其他对象。这跟人们平常想的很不一样。通俗地讲,我现在从图A那边copy出了B,那么假如我在B图上画一笔,那么我在A图上也会出现同样的一笔。如果这发生在现实生活中,有木有很神奇!具体看以下这段代码:
- #include<opencv2/opencv.hpp>
- using namespace cv;
- int main()
- {
- Mat A, C;
- A = imread("1.jpg",1);
- //两种copy方式
- Mat B(A);
- C = A;
- imshow("Picture B", B);
- imshow("Picture C", C);
- //图像模糊一下,总要增加点区别的咯,不然怎么看得出变化
- blur(C, C, Size(7, 7), Point(-1, -1));
- imshow("Picture A", A);
- imshow("Picture B second", B);
- waitKey(0);
- return 0;
- }</span>
运行后的结果如下:
看图的时候请别忘了看上面的窗口名,所以这个时候我们发现,这三张图A,B,C其实已经变成了同一张了,因为我们对C模糊处理后,发现A图和B图同样也变得模糊了。
比如说你想要在图像中的感兴趣区域的话就会变得很方便,比如说插入一句
- Mat D = A(Rect(10, 10, 200, 200));
- imshow("Picture D", D);</span>
那么有人会觉得这样子不好,有时候我只是想改动B图,A图想让它保留原来的样子,不想让它随着B图变化而变化。那么就需要使用到clone或者copyTo函数,如果使用了这两个函数,它就不再只是复制矩阵头了,它会复制整个矩阵本身,也就是重新开辟了一块内存在储存图像矩阵的所有信息。示例如下:
- Mat A;
- A = imread("1.jpg",1);
- Mat B = A.clone();
- blur(B, B, Size(10, 10), Point(-1, -1)); //模糊
- Mat C;
- A.copyTo(C);
- cvtColor(C, C, CV_BGR2GRAY); //灰度化
- imshow("Picture A", A);
- imshow("Picture B", B);
- imshow("Picture C", C);
- waitKey(0);</span>
显示的结果为:
从结果中反映:A图并没有跟着B图变模糊,也没有跟着C图变成灰度图,所以说此时A、B、C三张图是独立存在的。
接下去学习的是显式创建Mat对象的七种方法:
方法一:使用Mat()构造函数
- Mat M(200, 200, CV_32FC3, Scalar(255, 0, 0));</span>
第一个参数是行数;第二是参数表示列数;第三个参数表示制定元素的数据类型以及通道数,定义为CV_[位数][带符号与否][类型前缀]C[通道数],所有的情况如下图所示:
第四个参数Scalar是个short型向量,可以用来初始化矩阵,也可以用来表示颜色信息。比如说我现在imshow一下输出的是一张200X200的蓝色图像。注:RGB在opencv中储存的顺序是BGR。
方法二:在C\C++中通过构造函数进行初始化。这种构造方法可以构造多维度的矩阵。
- int sz[3] = { 2, 2, 2 };
- Mat M(3, sz, CV_8UC3, Scalar::all(0));</span>
如上就构造了一个三维的矩阵。第一个参数表示维度数,第二个参数是一个指向数组的指针,该数组包含了每个维度的尺寸。第三个参数和第四个参数跟第一种方法一致。
方法三:为已存在的IplImage指针创建信息头。即实现从IplImage类型到Mat类型的转变。
值得注意的是:以前opencv2.X中IplImage转换到Mat的方法是
- IplImage* srcimg = cvLoadImage("1.jpg", 1);
- Mat M(srcimg);
- //方法二:Mat M = srcimg;</span>
- IplImage* srcimg = cvLoadImage("1.jpg", 1);
- Mat M = cvarrToMat(srcimg);</span>
顺便说一下从Mat类型转换到IplImage类型的方法:
- Mat M = imread("1.jpg", 1);
- IplImage img = M; //opencv2.X,只copy了信息头
- IplImage *img = cvCloneImage(&(IplImage)M); //opencv3.X,真正的复制(包括所有数据)</span>
方法四:利用create函数
- Mat M;
- M.create(4, 4, CV_8UC(2));
- cout << "M=" << endl << " " << M << endl << endl;</span>
但是这种方法不能给矩阵赋初值,只有在改变尺寸时重新为矩阵数据开辟内存而已。
方法五:采用Matlab形式
- Mat A = Mat::eye(4, 4, CV_8UC1); //对角矩阵
- Mat B = Mat::ones(3, 3, CV_32FC3); //单位矩阵
- Mat C = Mat::zeros(3, 3, CV_64FC2); //零矩阵</span>
- Mat A = (Mat_<double>(3, 3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);</span>
参考文献:
1. 毛星云等著《OpenCV3编程入门》;
2. http://www.cnblogs.com/wangguchangqing/p/4016179.html
3. http://www.cnblogs.com/zcftech/archive/2013/04/10/3013027.html
4. http://www.cnblogs.com/edver/p/5187190.html
5. http://wenku.baidu.com/link?url=A4OyKRnJEMRKiWDZAvrCIT8FfVtRLqftsuM4WzOFxs-eC9txuzkwuxfQtZJvWX6Tn9EiUFOfAKTq5zxxjo2nmSTmj3P4rimmYbOONcWB-dO
在之前的OpenCV学习笔记(一)用到的几种显示图像的方法中其中一种就是Mat。Mat结构在OpenCV 2.0后才得到广泛应用,相对于OpenCV1.0时代中的IplImage,它有个好处就是不用再手动释放图像内存。而用IplImage格式存储图像的时候就必须在推出前将图像内存release掉,即添加语句cvReleaseImage(&iplImg);,否则会造成内存泄漏。除了这个好处外,它的操作也更加简单,比如用imshow显示图像,imread读取图像等等,跟Matlab有点接近。
Mat是一个类,它由两个数据部分组成:矩阵头和一个指向存储所有像素值的矩阵的指针。其中矩阵头包含了矩阵的尺寸、存储方法、储存地址等信息,由此可以看出矩阵头所占的内存很小,而具体储存所有像素值的矩阵则非常大。举个例子,比如一个班级,矩阵头就相当于储存了班级里有多少人、男女比多少、平均身高、平均体重等信息,而矩阵就储存了班级中所有同学的所有基本信息,每一个同学就相当于是一个像素点,而每个像素点的颜色信息、深度信息等一串数据就相当于是每个同学的身高、体重、性别等一系列信息组成的一串数据。
那么问题来了,假如说我有一张图A,我想要把A图复制给B图,理论上我是不是应该重新开一片内存,用来储存B图的信息?但是仔细想一想,我A图和B图一毛一样,如果我再开一片内存存储B图,那岂不是和存储A图的内存重复啦?是不是会造成内存的浪费。所以OpenCV中的图像存储结构Mat就很好地解决了这个问题。它使用了引用计数机制,该机制的思路就是让每个Mat对象有自己的信息头,但是共享同一个矩阵。也就是让A图和B图的矩阵指针指向同一地址,共用一片内存。复制图像的时候只是复制了矩阵头的信息和矩阵指针,并不是复制了整个矩阵。
这么一来如果不是很熟悉的话使用起来就会有点别扭了。因为通过复制后得到的图像虽然名字不一样,一个叫A,一个叫B,信息头不同,但是通过任何一个对象所做的改变也会影响到其他对象。这跟人们平常想的很不一样。通俗地讲,我现在从图A那边copy出了B,那么假如我在B图上画一笔,那么我在A图上也会出现同样的一笔。如果这发生在现实生活中,有木有很神奇!具体看以下这段代码:
- #include<opencv2/opencv.hpp>
- using namespace cv;
- int main()
- {
- Mat A, C;
- A = imread("1.jpg",1);
- //两种copy方式
- Mat B(A);
- C = A;
- imshow("Picture B", B);
- imshow("Picture C", C);
- //图像模糊一下,总要增加点区别的咯,不然怎么看得出变化
- blur(C, C, Size(7, 7), Point(-1, -1));
- imshow("Picture A", A);
- imshow("Picture B second", B);
- waitKey(0);
- return 0;
- }</span>
运行后的结果如下:
看图的时候请别忘了看上面的窗口名,所以这个时候我们发现,这三张图A,B,C其实已经变成了同一张了,因为我们对C模糊处理后,发现A图和B图同样也变得模糊了。
比如说你想要在图像中的感兴趣区域的话就会变得很方便,比如说插入一句
- Mat D = A(Rect(10, 10, 200, 200));
- imshow("Picture D", D);</span>
那么有人会觉得这样子不好,有时候我只是想改动B图,A图想让它保留原来的样子,不想让它随着B图变化而变化。那么就需要使用到clone或者copyTo函数,如果使用了这两个函数,它就不再只是复制矩阵头了,它会复制整个矩阵本身,也就是重新开辟了一块内存在储存图像矩阵的所有信息。示例如下:
- Mat A;
- A = imread("1.jpg",1);
- Mat B = A.clone();
- blur(B, B, Size(10, 10), Point(-1, -1)); //模糊
- Mat C;
- A.copyTo(C);
- cvtColor(C, C, CV_BGR2GRAY); //灰度化
- imshow("Picture A", A);
- imshow("Picture B", B);
- imshow("Picture C", C);
- waitKey(0);</span>
显示的结果为:
从结果中反映:A图并没有跟着B图变模糊,也没有跟着C图变成灰度图,所以说此时A、B、C三张图是独立存在的。
接下去学习的是显式创建Mat对象的七种方法:
方法一:使用Mat()构造函数
- Mat M(200, 200, CV_32FC3, Scalar(255, 0, 0));</span>
第一个参数是行数;第二是参数表示列数;第三个参数表示制定元素的数据类型以及通道数,定义为CV_[位数][带符号与否][类型前缀]C[通道数],所有的情况如下图所示:
第四个参数Scalar是个short型向量,可以用来初始化矩阵,也可以用来表示颜色信息。比如说我现在imshow一下输出的是一张200X200的蓝色图像。注:RGB在opencv中储存的顺序是BGR。
方法二:在C\C++中通过构造函数进行初始化。这种构造方法可以构造多维度的矩阵。
- int sz[3] = { 2, 2, 2 };
- Mat M(3, sz, CV_8UC3, Scalar::all(0));</span>
如上就构造了一个三维的矩阵。第一个参数表示维度数,第二个参数是一个指向数组的指针,该数组包含了每个维度的尺寸。第三个参数和第四个参数跟第一种方法一致。
方法三:为已存在的IplImage指针创建信息头。即实现从IplImage类型到Mat类型的转变。
值得注意的是:以前opencv2.X中IplImage转换到Mat的方法是
- IplImage* srcimg = cvLoadImage("1.jpg", 1);
- Mat M(srcimg);
- //方法二:Mat M = srcimg;</span>
- IplImage* srcimg = cvLoadImage("1.jpg", 1);
- Mat M = cvarrToMat(srcimg);</span>
顺便说一下从Mat类型转换到IplImage类型的方法:
- Mat M = imread("1.jpg", 1);
- IplImage img = M; //opencv2.X,只copy了信息头
- IplImage *img = cvCloneImage(&(IplImage)M); //opencv3.X,真正的复制(包括所有数据)</span>
方法四:利用create函数
- Mat M;
- M.create(4, 4, CV_8UC(2));
- cout << "M=" << endl << " " << M << endl << endl;</span>
但是这种方法不能给矩阵赋初值,只有在改变尺寸时重新为矩阵数据开辟内存而已。
方法五:采用Matlab形式
- Mat A = Mat::eye(4, 4, CV_8UC1); //对角矩阵
- Mat B = Mat::ones(3, 3, CV_32FC3); //单位矩阵
- Mat C = Mat::zeros(3, 3, CV_64FC2); //零矩阵</span>
- Mat A = (Mat_<double>(3, 3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);</span>
参考文献:
1. 毛星云等著《OpenCV3编程入门》;
2. http://www.cnblogs.com/wangguchangqing/p/4016179.html
3. http://www.cnblogs.com/zcftech/archive/2013/04/10/3013027.html
4. http://www.cnblogs.com/edver/p/5187190.html
5. http://wenku.baidu.com/link?url=A4OyKRnJEMRKiWDZAvrCIT8FfVtRLqftsuM4WzOFxs-eC9txuzkwuxfQtZJvWX6Tn9EiUFOfAKTq5zxxjo2nmSTmj3P4rimmYbOONcWB-dO