说明
1、通过讲解cv::Mat类的深拷贝和浅拷贝来说明cv::Mat的四种复制操作
2、当cv::Mat作为函数形参时:cv::Mat temp、const cv::Mat temp、cv::Mat &temp、const cv::Mat &temp这四种形参有什么区别?函数内部对Mat类形参数据的改变是否会影响到外面的Mat类实参数据?
cv::Mat数据结构
OpenCV2,数据结构Mat主要包含两个数据部分:矩阵头和一个指向图像像素矩阵的指针。
矩阵头主要包含矩阵尺寸、存储方法、存储地址和引用次数等。它的大小是一个常数,不会随着图像的大小而改变。图像像素矩阵大小相对与矩阵头要大得多,而且大小也会随着图像的大小而改变,这样,在图像复制和传递过程中,主要的开销是由图像像素矩阵引起的,而不是矩阵头。
因此,在Mat数据类型的拷贝中,除非有必要,否则应避免图像像素矩阵的拷贝。
OpenCV使用引用计数机制。其思路是让每个 Mat 对象有自己的信息头,但共享同一个矩阵。这通过让矩阵指针指向同一地址而实现。 当进行图像复制和传递时,不复制图像像素矩阵数据,而只是复制矩阵头和指向图像像素矩阵的指针。
当Mat对象每被复制一次时,就会将引用计数加1,而每销毁一个Mat对象(共用同一个矩阵数据)时引用计数会被减1,当引用计数为0时,矩阵数据会被清理。
cv::Mat深拷贝与浅拷贝
首先明白一点:OpenCV中,存储图像的像素矩阵的内存与存储Mat类对象的内存,并不是在一个连续的内存空间中,这从Mat类的封装源码中可以看到,实际上是,Mat类对象中有一个指针变量uchar* data,该指针变量存放的是存储图像像素矩阵的内存的起始地址,所以该指针data指向图像的像素矩阵。
浅拷贝:只拷贝信息头和矩阵指针而不拷贝像素矩阵,指针指向的像素矩阵是同一个存储区域,当改变了其中一个对象的图像像素值时,其他对象的像素值也随之改变。
深拷贝:除拷贝矩阵头外,也会拷贝像素矩阵,深拷贝对应的两个对象间是完全独立的关系,一个对象像素区域的改变不会引起另一个对象像素区域的改变。
cv::Mat的四种复制操作
矩阵复制的四种形式:拷贝构造函数、重载运算符=、clone函数、copyTo函数
// m声明并初始化cv::Mat m1(m); // 拷贝构造函数,浅拷贝,仅是创建了Mat的头部分,m1与m共享数据区cv::Mat m2 = m; // 重载运算符=,浅拷贝cv::Mat m3 = m.clone(); // clone函数,深拷贝,m3与m有各自独立的数据区cv::Mat m4;m.copyTo(m4); // copyTo函数,深拷贝
测试
测试函数:
#include<opencv2\opencv.hpp>#include<iostream>using namespace std;using namespace cv;int main() {cv::Mat m;//m = cv::imread("C:/Users/Administrator/Desktop/fly.JPG");int c = 1;//c = c + 1;////cv::Mat m1(m); // 拷贝构造函数,浅拷贝,仅是创建了Mat的头部分,m1与m共享数据区cv::Mat m2 = m; // 重载运算符=,浅拷贝cv::Mat m3 = m.clone(); // clone函数,深拷贝,m3与m有各自独立的数据区cv::Mat m4;m.copyTo(m4); // copyTo函数,深拷贝return 0;}
m的像素矩阵的起始地址m1、m2、m3、m4像素矩阵的起始地址
copyTo函数与clone函数的区别
无论m3、m4之前有没有分配内存,两个函数最终都会使m3、m4的大小和图像数据矩阵与m一致。
copyTo函数与clone函数都能得到一个矩阵的深拷贝,但是copyTo比clone多一个掩膜贴图功能,这是一个比较常用的函数。
src.copyTo(src_temp, mask);// 其中mask为一个二值的掩模图像,如果在某个像素点(i, j)其值为1,则把src中对应位置的像素复制到src_temp中,其余为0的位置像素保持不变。
cv::Mat作为函数形参的四种形式
1、cv::Mat(也要分两种情况)
情况一(实参矩阵和形参矩阵的尺寸相同,都是n*n,或者都是n*m)此时,cv :: Mat传入函数的是局部变量,执行的是浅拷贝,函数对cv::Mat图像像素矩阵的操作会影响到函数体外,但是函数体内外的cv::Mat的地址不同。
void func(cv::Mat temp){ temp = cv::Mat::ones(3, 3, CV_8U); //函数内部修改了temp,函数外的Mat也会被修改 //...}
测试程序1: 实参矩阵和形参矩阵都是 3*3
#include<iostream>#include<opencv2\opencv.hpp>using namespace std;using namespace cv;void func(cv::Mat temp) {cout << "函数内的Mat对象的地址&temp=" << &temp << endl;//输出temp的地址,函数内的地址cout << "函数内的像素矩阵的地址temp.data=" << (void *)temp.data << endl;//输出temp中存放像素矩阵的地址temp.data=?cout << "函数内的像素矩阵temp=" << temp << endl;//输出像素矩阵tempcout << endl;temp = cv::Mat::ones(3, 3, CV_8U);//执行完这步后,temp中的data的值应该是变了的吧???答案是没变cout << "函数内的Mat对象的地址&temp=" << &temp << endl;cout << "函数内的像素矩阵的地址temp.data=" << (void *)temp.data << endl;cout << "函数内的像素矩阵temp=" << temp << endl;cout << endl;}int main() {cv::Mat a = (cv::Mat_<uchar>(3, 3) << 1, 2, 3, 4, 5, 6, 7, 8, 9);cout << "外面的Mat对象的地址&a=" << &a << endl;//输出a的地址,函数外的地址cout << "外面的像素矩阵的地址a.data=" << (void *)a.data << endl;//输出a中存放像素矩阵的地址a.data=?cout << "外面的像素矩阵a=" << a << endl;//输出像素矩阵acout << endl;//func(a);//cout << "外面的Mat对象的地址&a=" << &a << endl;cout << "外面的像素矩阵的地址a.data=" << (void *)a.data << endl;cout << "外面的像素矩阵a=" << a << endl;cout << endl;return 0;}
测试程序1:运行结果
从运行结果中可以看到:1、func函数调用的时候,func函数外面实参的Mat对象的地址与func函数内部形参的Mat对象的地址是不同的;2、func函数对cv::Mat图像像素矩阵的操作确实会影响到函数体外的Mat.
测试程序1:结果分析
关键就是func函数中的 temp = cv::Mat::ones(3, 3, CV_8U); 这条赋值语句。我的猜想是:因为 temp.data 所指向的像素矩阵的大小与 cv::Mat::ones(3, 3, CV_8U) 所需要的像素矩阵的大小都是一样的,都是 3*3 规格的嘛,所以在执行 temp = cv::Mat::ones(3, 3, CV_8U); 赋值语句右边的时候,并不会在其他新地方再去额外的开辟新内存空间来存储 cv::Mat::ones(3, 3, CV_8U) 像素矩阵了,因为我原来的地方已经能够容下你了呀,那就没必要再去额外找新地方来存储你。所以执行完 temp = cv::Mat::ones(3, 3, CV_8U); 这条赋值语句之后,temp.data 的值始终没变,还是原来那个 temp.data 里面的值,但是这个时候存放原来的像素矩阵的位置的矩阵值已经被改变了(变成了单位矩阵),所以当然也要影响到外面的Mat。
此时,cv :: Mat传入函数的是局部变量,执行的是浅拷贝,函数对cv::Mat图像像素矩阵的操作不会影响到函数体外,但是函数体内外的cv::Mat的地址不同。
void func(cv::Mat temp){ temp = cv::Mat::ones(3, 3, CV_8U); //函数内部修改了temp,但是函数外的Mat不会被修改,见下面的例子 //...}
测试程序2:实参矩阵是 3*3,但是在func函数内部把形参矩阵改成 4*4
#include<iostream>#include<opencv2\opencv.hpp>using namespace std;using namespace cv;void func(cv::Mat temp) {cout << "函数内的Mat对象的地址&temp=" << &temp << endl;//输出temp的地址,函数内的地址cout << "函数内的像素矩阵的地址temp.data=" << (void *)temp.data << endl;//输出temp中存放像素矩阵的地址temp.data=?cout << "函数内的像素矩阵temp=" << temp << endl;//输出像素矩阵tempcout << endl;temp = cv::Mat::ones(4, 4, CV_8U);//执行完这步后,temp中的data的值应该是变了的吧???答案是变了cout << "函数内的Mat对象的地址&temp=" << &temp << endl;cout << "函数内的像素矩阵的地址temp.data=" << (void *)temp.data << endl;cout << "函数内的像素矩阵temp=" << temp << endl;cout << endl;}int main() {cv::Mat a = (cv::Mat_<uchar>(3, 3) << 1, 2, 3, 4, 5, 6, 7, 8, 9);cout << "外面的Mat对象的地址&a=" << &a << endl;//输出a的地址,函数外的地址cout << "外面的像素矩阵的地址a.data=" << (void *)a.data << endl;//输出a中存放像素矩阵的地址a.data=?cout << "外面的像素矩阵a=" << a << endl;//输出像素矩阵acout << endl;func(a);//cout << "外面的Mat对象的地址&a=" << &a << endl;cout << "外面的像素矩阵的地址a.data=" << (void *)a.data << endl;cout << "外面的像素矩阵a=" << a << endl;cout << endl;return 0;}
测试程序2:运行结果
从运行结果中可以看到:1、func函数调用的时候,func函数外面实参的Mat对象的地址与func函数内部形参的Mat对象的地址是不同的;2、func函数对cv::Mat图像像素矩阵的操作不会影响到函数体外的Mat.
测试程序1:结果分析
关键就是func函数中的 temp = cv::Mat::ones(4, 4, CV_8U); 这条赋值语句。我的猜想是:因为 temp.data 所指向的像素矩阵的大小与 cv::Mat::ones(4, 4, CV_8U) 所需要的像素矩阵的大小是不一样的(temp.data所指向的像素矩阵是 3*3 规格,而 cv::Mat::ones(4, 4, CV_8U) 需要的是 4*4 的规格),所以在执行 temp = cv::Mat::ones(4, 4, CV_8U); 赋值语句右边的时候,会在其他新地方再去额外的开辟新内存空间来存储 cv::Mat::ones(4, 4, CV_8U) 像素矩阵,因为我原来的地方已经容不下你了呀,那就只能再去额外找新地方来存储你了。所以执行完 temp = cv::Mat::ones(4, 4, CV_8U); 这条赋值语句之后,temp.data 的值是改变了的,不再是原来的那个地址值,但是这个时候存放原来的像素矩阵的位置的矩阵值是没有被掩盖掉的,所以当然不会影响到外面的Mat。
测试程序3:注意这种情况也变了
#include<iostream>#include<opencv2\opencv.hpp>using namespace std;using namespace cv;void func(cv::Mat temp) {cout << "函数内的Mat对象的地址&temp=" << &temp << endl;//输出temp的地址,函数内的地址cout << "函数内的像素矩阵的地址temp.data=" << (void *)temp.data << endl;//输出temp中存放像素矩阵的地址temp.data=?cout << "函数内的像素矩阵temp=" << temp << endl;//输出像素矩阵tempcout << endl;temp = cv::Mat::ones(2, 6, CV_8U);//执行完这步后,temp中的data的值应该是变了的吧???答案也是变了cout << "函数内的Mat对象的地址&temp=" << &temp << endl;cout << "函数内的像素矩阵的地址temp.data=" << (void *)temp.data << endl;cout << "函数内的像素矩阵temp=" << temp << endl;cout << endl;}int main() {cv::Mat a = (cv::Mat_<uchar>(4, 3) << 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12);cout << "外面的Mat对象的地址&a=" << &a << endl;//输出a的地址,函数外的地址cout << "外面的像素矩阵的地址a.data=" << (void *)a.data << endl;//输出a中存放像素矩阵的地址a.data=?cout << "外面的像素矩阵a=" << a << endl;//输出像素矩阵acout << endl;func(a);//cout << "外面的Mat对象的地址&a=" << &a << endl;cout << "外面的像素矩阵的地址a.data=" << (void *)a.data << endl;cout << "外面的像素矩阵a=" << a << endl;cout << endl;return 0;}
测试程序3:运行结果
2、const cv::Mat
const cv :: Mat传入函数的是局部变量,执行的是浅拷贝,但是限制函数体内不允许对图像像素矩阵进行修改。
void func(const cv::Mat temp){ temp = cv::Mat::ones(4, 4, CV_8U); //错误,不能修改temp //...}
函数依然是按值传递,由于const的限制temp的矩阵头中的数据不能修改,也就是不能在函数里面修改矩阵的大小等参数,因为这些参数保存在矩阵头中。
3、cv::Mat&(也是两种情况)
cv :: Mat& 传入函数的是引用,是同一个变量,函数体内外的cv::Mat的地址相同,函数对cv::Mat图像像素矩阵的操作会影响到函数体外。在使用引用的时候,修改矩阵Input将会影响到函数外的Mat,也就是矩阵头中包含的 任何参数 修改都会影响到外部的Mat。
void func(cv::Mat& temp){temp = cv::Mat::ones(4, 4, CV_8U); //修改了Mat的矩阵头,函数外的Mat也受到影响 //...}
情况一(实参矩阵和形参矩阵的尺寸相同,都是n*n,或者都是n*m) 测试程序4:
#include<iostream>#include<opencv2\opencv.hpp>using namespace std;using namespace cv;void func(cv::Mat& temp) {int c = 0;c = c + 1; cout << "函数内的Mat对象的地址&temp=" << &temp << endl;//输出temp的地址,函数内的地址cout << "函数内的像素矩阵的地址temp.data=" << (void *)temp.data << endl;//输出temp中存放像素矩阵的地址temp.data=?cout << "函数内的像素矩阵temp=" << temp << endl;//输出像素矩阵tempcout << endl;temp = cv::Mat::ones(4, 3, CV_8U);//执行完这步后,temp中的data的值不变c = c + 1;cout << "函数内的Mat对象的地址&temp=" << &temp << endl;cout << "函数内的像素矩阵的地址temp.data=" << (void *)temp.data << endl;cout << "函数内的像素矩阵temp=" << temp << endl;cout << endl;}int main() {int b = 0;b = b + 1;cv::Mat a = (cv::Mat_<uchar>(4, 3) << 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12);cout << "外面的Mat对象的地址&a=" << &a << endl;//输出a的地址,函数外的地址cout << "外面的像素矩阵的地址a.data=" << (void *)a.data << endl;//输出a中存放像素矩阵的地址a.data=?cout << "外面的像素矩阵a=" << a << endl;//输出像素矩阵acout << endl;func(a);//cout << "外面的Mat对象的地址&a=" << &a << endl;cout << "外面的像素矩阵的地址a.data=" << (void *)a.data << endl;cout << "外面的像素矩阵a=" << a << endl;cout << endl;return 0;}
测试程序4:运行结果
可以看出:1、使用引用时,func函数外面的Mat对象的地址与func函数内的Mat对象的地址始终是一样的,因为是引用嘛;2、使用引用时,func函数外面的Mat对象的像素矩阵的地址与func函数内的Mat对象的像素矩阵的地址始终是一样的;3、使用引用时,修改func函数内部的像素矩阵的值也会影响到func函数外面的像素矩阵。
测试程序5:
#include<iostream>#include<opencv2\opencv.hpp>using namespace std;using namespace cv;void func(cv::Mat& temp) {cout << "函数内的Mat对象的地址&temp=" << &temp << endl;//输出temp的地址,函数内的地址cout << "函数内的像素矩阵的地址temp.data=" << (void *)temp.data << endl;//输出temp中存放像素矩阵的地址temp.data=?cout << "函数内的像素矩阵temp=" << temp << endl;//输出像素矩阵tempcout << endl;temp = cv::Mat::ones(3, 4, CV_8U);//执行完这步后,temp中的data的值变了cout << "函数内的Mat对象的地址&temp=" << &temp << endl;cout << "函数内的像素矩阵的地址temp.data=" << (void *)temp.data << endl;cout << "函数内的像素矩阵temp=" << temp << endl;cout << endl;}int main() {cv::Mat a = (cv::Mat_<uchar>(4, 3) << 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12);cout << "外面的Mat对象的地址&a=" << &a << endl;//输出a的地址,函数外的地址cout << "外面的像素矩阵的地址a.data=" << (void *)a.data << endl;//输出a中存放像素矩阵的地址a.data=?cout << "外面的像素矩阵a=" << a << endl;//输出像素矩阵acout << endl;func(a);//cout << "外面的Mat对象的地址&a=" << &a << endl;cout << "外面的像素矩阵的地址a.data=" << (void *)a.data << endl;cout << "外面的像素矩阵a=" << a << endl;cout << endl;return 0;}
测试程序5:运行结果
可以看出:1、使用引用时,func函数外面的Mat对象的地址与func函数内的Mat对象的地址始终是一样的,因为是引用嘛;2、此种情况,使用引用时,func函数外面的Mat对象的像素矩阵的地址和func函数内的Mat对象的像素矩阵的地址都变化了,原因就是存储之前的像素矩阵的位置已经不能再用来存储现在这个像素矩阵了(像素矩阵的内部组织结构发生了改变),只能重新开辟一个新的内存空间来存储;3、使用引用时,修改func函数内部的像素矩阵的值也会影响到func函数外面的像素矩阵。
4、const cv::Mat&
const cv :: Mat& 传入函数的是引用,是同一个变量,但是限制函数体内不允许对图像像素矩阵进行修改。
void func(const cv::Mat& temp){temp = cv::Mat::ones(4, 4, CV_8U); // //...}
在使用const引用的时候,将不能修改矩阵头参数。
5、总结
如果你在函数中修改了矩阵头的参数,比方说存储地址,那么在函数内修改Mat中的值(比如图像值),则不会影响函数外部的Mat。(因为当在函数内部操作temp使得矩阵头发生变化,就有可能引起数据地址发生变化,那么再修改矩阵值时可能就不会导致外部矩阵值发生变化了。)如果你没有修改矩阵头的参数,那么在函数中通过矩阵指针修改Mat的值,那么函数外部也会发生变化。如果希望函数体内的数据变化会影响到函数体外,最好采用引用形式。如果不希望有图像数据矩阵的影响,需要在函数体内执行深拷贝。测试程序:深拷贝
#include<iostream>#include<opencv2\opencv.hpp>using namespace std;using namespace cv;void func(cv::Mat temp) {//cout << "函数内的Mat对象的地址&temp=" << &temp << endl;//输出temp的地址,函数内的地址cout << "函数内的像素矩阵的地址temp.data=" << (void *)temp.data << endl;//输出temp中存放像素矩阵的地址temp.data=?cout << "函数内的像素矩阵temp=" << temp << endl;//输出像素矩阵tempcout << endl;//深拷贝cv::Mat temp2 = temp.clone();//cout << "函数内的Mat对象的地址&temp2=" << &temp2 << endl;//输出temp2的地址,函数内的地址cout << "函数内的像素矩阵的地址temp2.data=" << (void *)temp2.data << endl;//输出temp2中存放像素矩阵的地址temp2.data=?cout << "函数内的像素矩阵temp2=" << temp2 << endl;//输出像素矩阵temp2cout << endl;//temp2 = cv::Mat::ones(4, 3, CV_8U);//cout << "函数内的Mat对象的地址&temp2=" << &temp2 << endl;cout << "函数内的像素矩阵的地址temp2.data=" << (void *)temp2.data << endl;cout << "函数内的像素矩阵temp2=" << temp2 << endl;cout << endl;}int main() {cv::Mat a = (cv::Mat_<uchar>(4, 3) << 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12);cout << "外面的Mat对象的地址&a=" << &a << endl;//输出a的地址,函数外的地址cout << "外面的像素矩阵的地址a.data=" << (void *)a.data << endl;//输出a中存放像素矩阵的地址a.data=?cout << "外面的像素矩阵a=" << a << endl;//输出像素矩阵acout << endl;func(a);//cout << "外面的Mat对象的地址&a=" << &a << endl;cout << "外面的像素矩阵的地址a.data=" << (void *)a.data << endl;cout << "外面的像素矩阵a=" << a << endl;cout << endl;return 0;}
运行结果:
6、疑问:
如果修改了矩阵头中的矩阵的行或列,那么存储地址一定会改变???答案是不论是往小的改,还是往大的改,存储地址都不会改变。其实也难说,往大的改,要是从改值非常非常的大,那么从该地址起,后面剩余的地址空间也存储不了那么大的像素矩阵,这个时候地址会不会变化?
测试程序1:行或列,往小的改
#include<iostream>#include<opencv2\opencv.hpp>using namespace std;using namespace cv;int main() {int b = 0;b = b + 1;cv::Mat a = (cv::Mat_<uchar>(4, 3) << 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12);cout << "行:" << a.rows << "列:" << a.cols << endl;cout << "外面的Mat对象的地址&a=" << &a << endl;//输出a的地址,函数外的地址cout << "外面的像素矩阵的地址a.data=" << (void *)a.data << endl;//输出a中存放像素矩阵的地址a.data=?cout << "外面的像素矩阵a=" << a << endl;//输出像素矩阵acout << endl;//a.rows = 3;//往小的改//cout << "行:" << a.rows << "列:" << a.cols << endl;cout << "外面的Mat对象的地址&a=" << &a << endl;cout << "外面的像素矩阵的地址a.data=" << (void *)a.data << endl;cout << "外面的像素矩阵a=" << a << endl;cout << endl;return 0;}
运行结果:
测试程序2:行或列,往大的改
#include<iostream>#include<opencv2\opencv.hpp>using namespace std;using namespace cv;int main() {int b = 0;b = b + 1;cv::Mat a = (cv::Mat_<uchar>(4, 3) << 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12);cout << "行:" << a.rows << "列:" << a.cols << endl;cout << "外面的Mat对象的地址&a=" << &a << endl;//输出a的地址,函数外的地址cout << "外面的像素矩阵的地址a.data=" << (void *)a.data << endl;//输出a中存放像素矩阵的地址a.data=?cout << "外面的像素矩阵a=" << a << endl;//输出像素矩阵acout << endl;//a.rows = 10;//往大的改//cout << "行:" << a.rows << "列:" << a.cols << endl;cout << "外面的Mat对象的地址&a=" << &a << endl;cout << "外面的像素矩阵的地址a.data=" << (void *)a.data << endl;cout << "外面的像素矩阵a=" << a << endl;cout << endl;return 0;}
运行结果: