OpenCV(十二):Mat
数据结构概述
在 C++ 中,cv::Mat
是 OpenCV 的核心数据结构,用于存储图像和矩阵。它由两部分组成:
- 矩阵头(Matrix Header):包含矩阵的尺寸(行、列)、数据类型、存储地址(指向数据的指针)等元信息。
- 数据块(Data Block):实际存储像素值或矩阵元素的内存块。
在 Python 中,cv2.imread()
等函数返回的图像对象是一个 numpy.ndarray
。NumPy 数组同样包含元数据(Metadata)(如形状 shape
、数据类型 dtype
等)和实际的数据缓冲区(Data Buffer)。
无论是 cv::Mat
还是 numpy.ndarray
,它们的深拷贝和浅拷贝主要区别就在于是否创建新的数据块。
浅拷贝(Shallow Copy)
浅拷贝创建了一个新的对象,但它只复制了原始对象的顶层结构,即元数据/头部信息。对于复杂对象(如包含其他对象的对象),浅拷贝只会复制其中包含的对象的引用(指针),而不会复制实际的数据内容。
Python 中 NumPy 的浅拷贝机制:
在 NumPy/OpenCV 中,主要的浅拷贝方式包括:
-
简单的变量赋值(Assignment):
import cv2 import numpy as npimg_original = cv2.imread("image.jpg") img_shallow_assign = img_original
- 行为:这并不是真正的拷贝,而是引用传递。
img_shallow_assign
和img_original
指向内存中的同一个对象(同一个ndarray
),共享头部和数据块。 - 后果:修改
img_shallow_assign
会直接影响img_original
,反之亦然。
- 行为:这并不是真正的拷贝,而是引用传递。
-
视图/切片(View/Slice):
# 浅拷贝示例:切片操作 img_shallow_slice = img_original[100:200, 100:200]
- 行为:切片操作通常会创建一个新的
ndarray
对象(新的头部),但其数据指针仍指向原始数组的数据块。这个新对象被称为原始数组的视图(View)。 - 后果:虽然是不同的变量名和头部,但它们共享底层数据。修改
img_shallow_slice
的像素值会影响img_original
中对应区域的像素值。
- 行为:切片操作通常会创建一个新的
-
ndarray.view()
方法:# 浅拷贝示例:view() 方法 img_shallow_view = img_original.view()
- 行为:明确地创建一个新的
ndarray
头部,但与原始数组共享数据缓冲区。 - 后果:修改
img_shallow_view
会影响img_original
。
- 行为:明确地创建一个新的
浅拷贝总结
特点 | 描述 |
---|---|
头部 | 新对象有自己的头部信息(形状、数据类型等)。 (赋值除外,赋值连头部都共享) |
数据 | 共享原始对象的底层数据块。 |
独立性 | 不独立。修改其中任何一个对象的数据,另一个对象的数据也会随之改变。 |
速度/内存 | 快,内存占用少,因为没有复制数据。 |
适用场景 | 当你希望在不改变内存中实际数据的前提下,以不同的方式(如不同的数据类型、形状)来查看或操作同一块数据时。 |
深拷贝(Deep Copy)
深拷贝创建了一个完全独立的新对象。它不仅复制了原始对象的顶层结构(头部),还会递归地复制原始对象中的所有数据块。这意味着,深拷贝的结果对象拥有全新的、独立的内存数据。
Python 中 NumPy 的深拷贝机制
在 OpenCV/NumPy 中,实现深拷贝的主要方法是:
-
ndarray.copy()
方法:# 深拷贝示例:.copy() 方法 img_deep_copy = img_original.copy()
- 行为:创建了一个新的
ndarray
头部,并为图像数据分配了全新的内存空间,然后将原始数据内容复制到新内存中。 - 后果:
img_deep_copy
是一个完全独立的副本。修改img_deep_copy
的像素值不会影响img_original
,反之亦然。
- 行为:创建了一个新的
-
cv2.clone() (C++ 中常用,Python 对应 copy()):
虽然在 C++ 中有 Mat::clone() 方法,但在 Python 的 NumPy 环境下,ndarray.copy() 是最常用的深拷贝方法,效果等同于 C++ 中的 clone()。
-
cv2.copyTo() (功能等价于 copy()):
在 C++ 中 Mat::copyTo() 也是常用的深拷贝方法,在 Python 中也可以用于深拷贝,但不如 img.copy() 直接和常用。
-
copy.deepcopy()
函数:import copy # 深拷贝示例:copy 模块 img_deep_copy_module = copy.deepcopy(img_original)
- 行为:
copy
模块是 Python 内置的,deepcopy()
适用于任何复杂的 Python 对象,它会进行递归复制。对于 NumPy 数组,其效果与ndarray.copy()
相同,但通常**ndarray.copy()
效率更高**,因为它是 NumPy 库内部优化的 C 语言实现。
- 行为:
深拷贝总结
特点 | 描述 |
---|---|
头部 | 新对象有自己的头部信息(形状、数据类型等)。 |
数据 | 拥有独立分配的新数据块。 |
独立性 | 完全独立。修改一个对象不会影响另一个对象。 |
速度/内存 | 慢,内存占用大,因为它需要分配新内存并复制所有数据。 |
适用场景 | 当你需要在保留原始数据的同时,对副本进行修改或处理,且不希望相互影响时(例如图像滤波、目标检测后的标注绘制等)。 |
示例
import cv2
import numpy as np# 1. 准备原始图像 (假设我们有一张 100x100 的三通道 BGR 图像)
# 实际操作中,请替换为 cv2.imread("your_image.jpg")
img_original = np.zeros((100, 100, 3), dtype=np.uint8)
# 将左上角像素设为白色 (255, 255, 255)
img_original[0, 0] = [255, 255, 255]
print(f"原始图像 [0, 0] 像素: {img_original[0, 0]}")# ----------------- 浅拷贝 (视图/切片) -----------------
# 切片操作创建一个视图,共享数据
img_shallow_slice = img_original[50:80, 50:80] # 修改浅拷贝(切片)的左上角像素 (即原始图像的 [50, 50] 像素)
img_shallow_slice[0, 0] = [0, 0, 255] # 改为蓝色print("\n--- 浅拷贝操作 ---")
print(f"浅拷贝 [0, 0] 像素 (修改后): {img_shallow_slice[0, 0]}")
# 检查原始图像中对应的像素
print(f"原始图像 [50, 50] 像素 (被影响): {img_original[50, 50]}") # 结果是 [0, 0, 255]# ----------------- 深拷贝 (copy()) -----------------
# 使用 .copy() 方法创建深拷贝
img_deep_copy = img_original.copy()# 将深拷贝的 [0, 0] 像素修改为绿色 (0, 255, 0)
img_deep_copy[0, 0] = [0, 255, 0] print("\n--- 深拷贝操作 ---")
print(f"深拷贝 [0, 0] 像素 (修改后): {img_deep_copy[0, 0]}")
# 检查原始图像中对应的像素
print(f"原始图像 [0, 0] 像素 (未被影响): {img_original[0, 0]}") # 结果是 [255, 255, 255]# ----------------- 赋值 (最浅的拷贝/引用) -----------------
img_assign = img_original
img_assign[1, 1] = [255, 0, 0] # 修改为红色print("\n--- 赋值操作 ---")
print(f"赋值 [1, 1] 像素: {img_assign[1, 1]}")
print(f"原始图像 [1, 1] 像素 (被影响): {img_original[1, 1]}") # 结果是 [255, 0, 0]# --- 结论 ---
# 1. 浅拷贝/切片:修改 img_shallow_slice[0, 0] 影响了 img_original[50, 50]。
# 2. 深拷贝:修改 img_deep_copy[0, 0] 没有影响 img_original[0, 0]。
# 3. 赋值:修改 img_assign[1, 1] 影响了 img_original[1, 1]。
总结
在 OpenCV 的 Python 实践中,选择正确的拷贝方式是高效和安全编程的关键:
- 何时使用深拷贝 (
img.copy()
)?- 当你需要对图像进行修改(如绘图、阈值处理、颜色转换、滤波等),但又希望保留原始图像不变时。
- 这是最安全的选择,确保操作的隔离性。
- 何时使用浅拷贝(切片
img[y:y+h, x:x+w]
)?- 当你需要提取图像的某个区域进行操作,并且希望修改能够反映回原图时。
- 当你需要仅查看图像的某个区域,而不想复制数据以节省内存和时间时。
- 当你需要创建一个临时变量,仅用于引用原图,并且知道不会进行修改操作时。
- 避免使用简单赋值
- 对于 NumPy/OpenCV 数组,简单赋值
=
只是创建了一个新的引用,它甚至都不是一个新对象(它共享头部和数据)。这极易导致意外的副作用,通常只有当你确定两个变量必须共享所有状态时才使用。
- 对于 NumPy/OpenCV 数组,简单赋值