OpenCV Mat矩阵复制(深复制和浅复制)详解
复制矩阵分为深复制(也叫完全拷贝)和浅复制。深复制就是不但复制矩阵头,还复制矩阵数据区。而浅复制只复制矩阵头,不复制数据区。
深复制的成员函数有 clone() 和 copyTo()。浅复制常用方式有赋值运算符和拷贝构造函数,它们仅复制矩阵信息头。
值得注意的是,无论是深复制还是浅复制,矩阵头肯定是新申请内存空间并进行复制的。此外,深复制一定有复制(全部或部分矩阵)数据区这个操作,而浅复制则没有这个操作。深复制有开辟新数据区内存和不开辟新数据区内存两种情况,后者在原有的数据区内存中完成复制,比如 copyTo() 函数在某些条件下就是如此。
函数 clone() 声明如下:
示例如下代码:
copyTo() 也是深复制,但是否申请新的内存空间取决于 dst 矩阵头中的大小信息是否与 src 一致,若一致,则只深复制且不申请新的空间;否则先申请空间,再进行复制。
函数 copyTo() 声明如下:
需要注意的是,copyTo() 这个函数会检测 mask 中相应位置是否为 0,如果不为 0,就会把输入 Mat 相应位置的值直接复制到输出 OutputArray 的相应位置上;如果为 0,输出 OutputArray 的相应位置就置为 0。mask 的作用相当于一个“与”的操作。
OutputArray 是 InputArray 的派生类,而 InputArray 是一个接口类,这个接口类可以是 Mat、Mat_<T>、Mat_<T, m, n>、vector<T>、vector<vector<T>>、vector<Mat>。这也就意味着如果看见函数的参数类型是 InputArray,那么把上述几种类型作为参数都是可以的。示例如下代码:
【实例 1】两种方式实现深复制矩阵。
1) 打开 Qt Creator,新建一个控制台工程,工程名是 test。
2) 在 IDE 中打开 main.cpp,输入如下代码:
3) 保存工程并运行,结果如下图所示:
【实例 2】实验 mask 中为 0 和不为 0 的情况。
1) 打开 Qt Creator,新建一个控制台工程,工程名是 test。
2) 在 IDE 中打开 main.cpp,输入如下代码:
3) 保存工程并运行,结果如下图所示:
果然 mask 矩阵某位置的灰度值为 0,目标矩阵的相应位置的灰度值也为 0。
【实例 3】利用 copyTo 实现图 1 贴在图 2 上。
1) 打开 Qt Creator,新建一个控制台工程,工程名是 test。
2) 在 IDE 中打开 main.cpp,输入如下代码:
注意 imread 的第二个参数,如果取值为 0,就将图像转换为灰度图像。有了 mask 后,就可以开始利用 copyTo 复制了。注意 mask 必须和调用者 Mat 矩阵(这里是 Image2)大小相同。
3) 保存工程并运行,结果如下图所示。
深复制的成员函数有 clone() 和 copyTo()。浅复制常用方式有赋值运算符和拷贝构造函数,它们仅复制矩阵信息头。
值得注意的是,无论是深复制还是浅复制,矩阵头肯定是新申请内存空间并进行复制的。此外,深复制一定有复制(全部或部分矩阵)数据区这个操作,而浅复制则没有这个操作。深复制有开辟新数据区内存和不开辟新数据区内存两种情况,后者在原有的数据区内存中完成复制,比如 copyTo() 函数在某些条件下就是如此。
Mat类深复制
Mat 类提供了成员函数 clone() 和 copyTo() 用来实现完全复制矩阵,其中 clone() 是完全的深复制,在内存中申请新的空间(包括矩阵头和数据区)。函数 clone() 声明如下:
Mat clone();该函数为新矩阵申请新的内存空间(包括矩阵头空间和数据区空间),并将原矩阵的全部数据(包括矩阵头和数据区)复制到新矩阵,函数返回新矩阵的 Mat 对象。
示例如下代码:
Mat A = Mat::ones(4,5,CV_32F); Mat B = A.clone(); // clone 是完全的深复制,在内存中申请新的空间,B与A完全独立
copyTo() 也是深复制,但是否申请新的内存空间取决于 dst 矩阵头中的大小信息是否与 src 一致,若一致,则只深复制且不申请新的空间;否则先申请空间,再进行复制。
函数 copyTo() 声明如下:
void copyTo (OutputArray m); void cv::Mat::copyTo(OutputArray m, InputArray mask );其中参数 m 表示目标数组。对于带 mask 参数这种使用方式,mask 作为一个掩码矩阵,其大小必须和调用者对象的 Mat 矩阵大小相同,类型必须是 CV_8U,通道数可以为一或多通道,但只看单通道,所以用 CV_8U1 即可。
需要注意的是,copyTo() 这个函数会检测 mask 中相应位置是否为 0,如果不为 0,就会把输入 Mat 相应位置的值直接复制到输出 OutputArray 的相应位置上;如果为 0,输出 OutputArray 的相应位置就置为 0。mask 的作用相当于一个“与”的操作。
OutputArray 是 InputArray 的派生类,而 InputArray 是一个接口类,这个接口类可以是 Mat、Mat_<T>、Mat_<T, m, n>、vector<T>、vector<vector<T>>、vector<Mat>。这也就意味着如果看见函数的参数类型是 InputArray,那么把上述几种类型作为参数都是可以的。示例如下代码:
Mat A = Mat::ones(4, 5, CV_32F); // A的大小(通过size()函数获得)是[5,4],列数(宽度)是5,行数是4 Mat C; // 此时C的大小是[0,0] A.copyTo(C); // C的大小是[0,0],若大小不合适,则需要申请新的内存空间,并完成复制,等同于clone() Mat D = A.col(1); // 赋值后,D的大小是[1,4],宽度是1,高度是4,相当于列数是1,行数是4 A.col(0).copyTo(D); // 此处A.col(0)和D一样大,因此不会申请空间,而是直接进行复制,相当于把A的第1列赋值给第2列。注意,别看成A.copyTo(D) Mat mat1 = Mat::ones(1, 5, CV_32F); // [1,1,1,1,1] Mat mat3 = Mat::zeros(1, 5, CV_32F); // [0,0,0,0,0] // 因为mat3和mat1大小一样,所以mat1未被重新分配内存,通过mat1可以改变mat3的内容 mat3.copyTo(mat1);
【实例 1】两种方式实现深复制矩阵。
1) 打开 Qt Creator,新建一个控制台工程,工程名是 test。
2) 在 IDE 中打开 main.cpp,输入如下代码:
#include <iostream> #include "opencv2/imgcodecs.hpp" #include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> using namespace cv; // 所有 OpenCV 类都在命名空间 cv 下 using namespace std; Mat colorReduce_2(Mat img, int div) { Mat image; image = img.clone(); // img.copyTo(image); // 效果同 clone 一样 int nl = image.rows; // 得到行数 int nc = image.cols * image.channels(); // 得到实际列数 for (int j = 0; j < nl; j++) { uchar* data = image.ptr<uchar>(j); for (int i = 0; i < nc; i++) data[i] = data[i] - data[i] % div + div / 2; } return image; } int main() { Mat img = imread("520.jpg"); Mat img2 = colorReduce_2(img, 64); imshow("reduce520", img2); imshow("520", img); waitKey(0); }520.jpg 是工程目录下的一幅图片。在上述代码中,我们自定义了函数 colorReduce_2,该函数用于减少每个像素值,因为使用了 clone(copyTo 效果也一样),所以并不会影响原图。
3) 保存工程并运行,结果如下图所示:

下图是原图,上图是加工后的图片,图片没有任何变化。
【实例 2】实验 mask 中为 0 和不为 0 的情况。
1) 打开 Qt Creator,新建一个控制台工程,工程名是 test。
2) 在 IDE 中打开 main.cpp,输入如下代码:
#include <iostream> #include "opencv2/imgcodecs.hpp" #include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> using namespace cv; // 所有 OpenCV 类都在命名空间 cv 下 using namespace std; int main() { Mat image, mask; Rect r1(10, 10, 60, 100); // 生成一个矩形 Mat img2; image = imread("520.jpg"); mask = Mat::zeros(image.size(), CV_8UC1); // 掩码矩阵灰度值为0,单通道矩阵,全黑的图 mask(r1).setTo(255); // 将掩码矩阵中 r1 范围内的灰度值设定为 255,即白图 image.copyTo(img2, mask); // 将掩码图像与 image 图像进行“与操作”,然后赋值给 img2 图像 imshow("src", image); imshow("dst", img2); imshow("mask", mask); waitKey(); return 0; }在上述代码中,我们定义了一个掩码矩阵 mask,它只在 r1 矩阵范围内的灰度值是 255,其他都是 0,那么 copyTo 的操作结果就是将 image 对应位置上的像素值复制到 img2 上,也就是 r1 范围内的 image 部分被复制到 img2 的 r1 范围内,而 img2 其他区域都为 0,即黑色。
3) 保存工程并运行,结果如下图所示:

果然 mask 矩阵某位置的灰度值为 0,目标矩阵的相应位置的灰度值也为 0。
【实例 3】利用 copyTo 实现图 1 贴在图 2 上。
1) 打开 Qt Creator,新建一个控制台工程,工程名是 test。
2) 在 IDE 中打开 main.cpp,输入如下代码:
#include <iostream> #include "opencv2/imgcodecs.hpp" #include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> using namespace cv; // 所有 OpenCV 类都在命名空间 cv 下 using namespace std; int main() { Mat Image1 = imread("520.jpg"); Mat Image2 = imread("2.jpg"); imshow("pic1", Image1); imshow("pic2", Image2); // 在 Image1 上创建一个感兴趣的区域 Mat imagedst = Image1(Rect(0, 0, Image2.cols, Image2.rows)); Mat mask = imread("2.jpg", 0); // 加载图2,转为灰度图后存于 mask imshow("mask", mask); // 显示 mask 图 // 进行复制 Image2.copyTo(imagedst, mask); // mask 必须和调用者 Mat 矩阵(这里是 dstImage)大小相同 imshow("result", Image1); // 显示效果图 waitKey(0); return 0; }我们的目标是把 image2 复制到 image1 的某块区域内(这里是 imagedst),这个区域大小和 image2 相同。首先在 image1 上创建一个感兴趣的区域,并存于 imagedst 中,然后加载图 2,将其转为灰度图像后存于掩码矩阵 mask 中。
注意 imread 的第二个参数,如果取值为 0,就将图像转换为灰度图像。有了 mask 后,就可以开始利用 copyTo 复制了。注意 mask 必须和调用者 Mat 矩阵(这里是 Image2)大小相同。
3) 保存工程并运行,结果如下图所示。

Mat类浅复制
浅复制常见的方式有:使用赋值运算符和拷贝构造函数。使用赋值运算符和拷贝构造函数都是复制信息头,比如以下程序:Mat src1 = imread("1.jpg"); Mat src2=src1; // src2 和 src1 共用一个矩阵,所以当改变二者中的任意一个时,另一个会随之改变 Mat dst(src1); // 使用拷贝构造函数,只复制矩阵的信息头-----典型的浅复制