cv::Mat 数据结构和相关深拷贝与浅拷贝
cv::Mat 是 OpenCV 中用于存储图像、矩阵等多维数据的核心数据结构。它的设计核心目标是实现高效的内存管理和便捷的数据操作。
📚 cv::Mat 的数据结构
cv::Mat 的设计巧妙地将其分为两个部分,这也是理解其拷贝行为的关键:
-
矩阵头 (Matrix Header)
这是一个轻量级的结构体,包含了描述数据的元信息,例如:
- 尺寸信息 :行数 (
rows)、列数 (cols)。 - 类型信息 :数据类型 (
type),如CV_8UC3表示8位无符号整数、3通道。 - 数据指针 :一个指向实际像素数据的指针 (
data)。 - 引用计数器 :一个指向引用计数器 (
refcount) 的指针,用于追踪有多少个Mat对象共享同一块数据。
- 尺寸信息 :行数 (
-
数据区 (Data Region)
这是存储实际像素值的连续内存块。对于一幅图像来说,这部分占用的内存通常远大于矩阵头。
这种"头"与"数据"分离的设计,使得 cv::Mat 可以通过引用计数 机制来高效地管理内存。当多个 Mat 对象共享同一块数据时,只有最后一个对象被销毁时,数据内存才会被真正释放。
🧐 深拷贝与浅拷贝的区别
cv::Mat 的拷贝操作分为浅拷贝和深拷贝,它们的根本区别在于是否复制了数据区。
浅拷贝 (Shallow Copy)
- 行为 :仅复制矩阵头 ,新的
Mat对象和原始对象共享同一块数据区内存。 - 影响 :修改其中任何一个
Mat对象的像素数据,都会影响到所有共享该数据区的对象。 - 性能:非常高效,因为它只复制了一个小的结构体,避免了大量数据的复制。
- 实现方式 :
- 使用赋值运算符
= - 使用拷贝构造函数
Mat B(A) - 创建感兴趣区域 (ROI),如
Mat roi = img(Rect(...))
- 使用赋值运算符
深拷贝 (Deep Copy)
- 行为 :不仅复制矩阵头 ,还会为新的
Mat对象重新分配一块独立的内存来存储数据副本。 - 影响:新对象和原始对象完全独立,修改其中一个不会影响另一个。
- 性能:相对较慢,因为需要分配新内存并复制所有像素数据。
- 实现方式 :
- 使用
clone()方法 - 使用
copyTo()方法
- 使用
📊 核心区别对比
| 特性 | 浅拷贝 (Shallow Copy) | 深拷贝 (Deep Copy) |
|---|---|---|
| 内存共享 | 是,共享数据区 | 否,拥有独立数据区 |
| 修改影响 | 相互影响 | 互不影响 |
| 性能开销 | 低(仅复制矩阵头) | 高(复制全部数据) |
| 实现方法 | =, 拷贝构造函数, ROI |
clone(), copyTo() |
💻 代码示例
cpp
#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
// 创建一个 2x2 的单通道矩阵
cv::Mat mat1 = (cv::Mat_<int>(2, 2) << 1, 2, 3, 4);
// --- 浅拷贝 ---
cv::Mat mat2 = mat1; // 赋值操作,浅拷贝
mat2.at<int>(0, 0) = 99; // 修改 mat2
// 此时 mat1 也被修改了,输出: [99, 2; 3, 4]
std::cout << "mat1 after shallow copy: " << mat1 << std::endl;
// --- 深拷贝 ---
cv::Mat mat3 = mat1.clone(); // 克隆操作,深拷贝
mat3.at<int>(0, 0) = 100; // 修改 mat3
// 此时 mat1 不受影响,输出: [99, 2; 3, 4]
std::cout << "mat1 after deep copy: " << mat1 << std::endl;
return 0;
}
⚠️ 特别注意:ROI 的浅拷贝
从原图中提取一个感兴趣区域(ROI)也是一种浅拷贝操作。这意味着对 ROI 的修改会直接反映到原始图像上。
cpp
cv::Mat image = cv::imread("image.jpg");
// 提取左上角 100x100 的区域,这是浅拷贝
cv::Mat roi = image(cv::Rect(0, 0, 100, 100));
// 将 ROI 区域设置为黑色,原图对应区域也会变黑!
roi.setTo(cv::Scalar(0, 0, 0));
如果需要得到一个独立的 ROI 副本,必须在提取后调用 clone():
cpp
// 创建一个独立的 ROI 副本
cv::Mat roi_clone = image(cv::Rect(0, 0, 100, 100)).clone();
🤔 clone() 与 copyTo() 的细微差别
虽然两者都用于深拷贝,但在使用上略有不同:
clone(): 总是创建一个新的Mat对象并分配内存,返回这个新对象。它更直接,适用于需要绝对独立副本的场景。copyTo(): 将一个Mat的数据复制到另一个已存在 的Mat对象中。如果目标Mat的大小或类型不匹配,copyTo()会先为其重新分配内存。它更适合在目标内存已预分配或需要复用的场景,并且支持使用掩码(mask)进行选择性复制。
###未完待续