OpenCV(八):NumPy
OpenCV (Open Source Computer Vision Library) 和 NumPy (Numerical Python) 是 Python 中进行图像处理和计算机视觉任务时不可或缺的两个库。OpenCV-Python 的核心实现依赖于 NumPy,所有 OpenCV 的图像数据结构都以 NumPy 数组(numpy.ndarray
)的形式表示。
图像表示与数据结构:NumPy 数组
在 OpenCV 中,图像被视为多维的 NumPy 数组。这是理解一切操作的基础。
图像的本质:多维数组
- 灰度图: 一个二维数组(矩阵),形状通常为 (H,W),其中 H 是高(行数),W 是宽(列数)。每个元素代表一个像素的强度值(通常在 0 到 255 之间)。
- 彩色图 (BGR/RGB): 一个三维数组,形状通常为 (H,W,C),其中 H 是高,W 是宽,C 是通道数(通常 C=3,分别对应蓝、绿、红或红、绿、蓝)。
- 注意: OpenCV 默认的颜色顺序是 BGR(蓝-绿-红),而不是常见的 RGB。
NumPy 数组的属性
使用 NumPy 数组的属性,可以方便地查看图像的基本信息:
.shape
: 返回图像的维度信息(高、宽、通道数)。- 例如:彩色图
img.shape
可能返回(480, 640, 3)
。 - 灰度图
img.shape
可能返回(480, 640)
。
- 例如:彩色图
.dtype
: 返回数组中元素的类型。图像像素最常用的类型是uint8
(8 位无符号整数,范围 0−255)。.size
: 返回数组中元素的总个数(高 × 宽 × 通道数)。
像素访问与操作
NumPy 的切片和索引机制使得像素级别的操作变得非常高效和直观。
- 访问单个像素(彩色图):
img[y, x]
返回一个包含 B,G,R 三个通道值的数组。- 例如:
pixel = img[100, 150]
得到[B, G, R]
。
- 例如:
- 访问单个通道:
img[:, :, 0]
提取蓝色通道。 - 区域操作 (ROI, Region of Interest): 使用切片来选取图像的特定矩形区域。
- 例如:
roi = img[y1:y2, x1:x2]
。
- 例如:
- 修改像素值: 可以直接赋值修改像素或区域。
- 例如:将左上角 50×50 区域设为黑色(0):
img[0:50, 0:50] = [0, 0, 0]
。
- 例如:将左上角 50×50 区域设为黑色(0):
NumPy 在图像操作中的优势
将图像存储为 NumPy 数组带来了巨大的性能和功能优势。
向量化操作(Vectorization)
NumPy 的核心优势在于向量化操作,它允许对整个数组(或数组的子集)进行元素级别的运算,而无需编写显式的 Python 循环。这大大提高了运算速度,因为底层操作是通过高度优化的 C/C++/Fortran 代码执行的。
- 亮度调整: 增加图像亮度 50(饱和操作,需要注意数据溢出)。
new_img = cv2.add(img, 50)
或使用 NumPy 的 Clip 确保值在 0−255 之间:new_img = np.clip(img.astype(np.int32) + 50, 0, 255).astype(np.uint8)
。
- 通道分离与合并: OpenCV 的
cv2.split()
和cv2.merge()
可以完成,但 NumPy 也能高效实现。b, g, r = cv2.split(img)
img_merged = cv2.merge((b, g, r))
图像创建与初始化
NumPy 提供了便捷的方式来创建各种初始化图像(画布):
- 全黑图像:
black_img = np.zeros((H, W, 3), dtype=np.uint8)
- 全白图像:
white_img = np.ones((H, W, 3), dtype=np.uint8) * 255
- 随机噪点图:
random_img = np.random.randint(0, 256, size=(H, W, 3), dtype=np.uint8)
数据类型转换(dtype
)
图像处理中经常需要进行数据类型转换,例如,为了进行精确的浮点数运算(如傅里叶变换或归一化),或为了处理溢出问题。
- 归一化到 0.0−1.0:
float_img = img.astype(np.float32) / 255.0
- 反归一化回 0−255:
uint8_img = (float_img * 255).astype(np.uint8)
OpenCV 函数对 NumPy 的无缝支持
OpenCV-Python 的设计哲学是所有接受图像作为输入的函数,都期望接收一个 NumPy 数组;所有返回图像的函数,都会返回一个 NumPy 数组。
图像 I/O
- 读取图像:
cv2.imread()
返回一个 NumPy 数组。 - 显示图像:
cv2.imshow()
接受一个 NumPy 数组作为参数。 - 写入图像:
cv2.imwrite()
接受一个 NumPy 数组作为参数。
几何变换
像平移、旋转、缩放等操作,其变换矩阵本身就是 NumPy 数组,而 OpenCV 函数内部会对输入的图像(NumPy 数组)高效地进行矩阵运算。
-
平移: 需要定义 2×3 的平移矩阵 M,该矩阵就是一个 NumPy 数组。
M = np.float32([[1, 0, tx], [0, 1, ty]]) shifted = cv2.warpAffine(img, M, (W, H))
-
旋转:
cv2.getRotationMatrix2D()
返回的也是一个 NumPy 数组。
卷积与滤波
像高斯模糊、Sobel 梯度等操作,其核心是卷积运算。OpenCV 的 cv2.filter2D()
函数可以接受一个 NumPy 数组作为自定义卷积核(Kernel)。
# 定义一个 3x3 的均值模糊核
kernel = np.ones((3, 3), np.float32) / 9
dst = cv2.filter2D(img, -1, kernel)
与其他库的集成
NumPy 的通用性使得 OpenCV 能够轻松地与其他流行的 Python 科学计算和数据可视化库集成。
-
Matplotlib: 最常用的可视化工具。要正确显示 OpenCV 读取的彩色图,通常需要进行颜色通道转换,因为 Matplotlib 期望的是 RGB 顺序。
import matplotlib.pyplot as plt # OpenCV to RGB img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) plt.imshow(img_rgb) plt.show()
-
Scikit-image, SciPy: 这些库中的许多图像处理算法可以直接接收和返回 NumPy 数组,实现功能上的扩展。
使用示例
import numpy as np
import time# --- 一、 数组的创建 (Creating Arrays) ---print("--- 1. 数组的创建 ---")# 1.1 从 Python 列表创建 (最常用)
a = np.array([1, 2, 3])
b = np.array([[1, 2, 3], [4, 5, 6]], dtype=np.float64) # 指定数据类型
print("a (一维数组):\n", a)
print("b (二维数组):\n", b)
print("b 的形状 (shape):", b.shape)
print("b 的数据类型 (dtype):", b.dtype)
print("-" * 20)# 1.2 使用内置函数创建全零/全一/空数组
c = np.zeros((2, 3), dtype=np.int32) # 2行3列的全零矩阵
d = np.ones((1, 4)) # 1行4列的全一矩阵 (默认 float)
e = np.empty((2, 2)) # 创建一个元素值随机的数组 (通常用于占位)
print("c (全零):\n", c)
print("d (全一):\n", d)
print("-" * 20)# 1.3 创建序列数组
f = np.arange(10) # 0 到 9 的整数序列
g = np.arange(2, 10, 2) # 2 到 10 (不包含) 步长为 2
h = np.linspace(0, 1, 5) # 在 0 和 1 之间平均创建 5 个点
print("f (arange 0-9):", f)
print("h (linspace 0-1, 5点):", h)
print("-" * 20)# 1.4 随机数组
i = np.random.rand(2, 3) # 2x3 均匀分布的随机数 (0-1)
j = np.random.randint(0, 10, size=(2, 2)) # 2x2 随机整数 (0 到 9)
print("i (随机浮点数):\n", i)
print("-" * 20)# --- 二、 数组的索引与切片 (Indexing and Slicing) ---print("--- 2. 数组的索引与切片 ---")# 2.1 一维数组
arr1d = np.arange(10) # [0 1 2 3 4 5 6 7 8 9]
print("arr1d:", arr1d)
print("arr1d[2]:", arr1d[2]) # 元素索引
print("arr1d[2:5]:", arr1d[2:5]) # 切片 [2, 3, 4]
print("arr1d[:5]:", arr1d[:5]) # 从开始到索引 5 之前
print("arr1d[5:]:", arr1d[5:]) # 从索引 5 到末尾
print("arr1d[::-1]:", arr1d[::-1]) # 逆序
print("-" * 20)# 2.2 二维数组 (图像/矩阵操作的核心)
arr2d = np.array([[1, 2, 3],[4, 5, 6],[7, 8, 9]])
print("arr2d:\n", arr2d)
print("单个元素 arr2d[1, 2]:", arr2d[1, 2]) # 索引:第1行,第2列 (从0开始) -> 6
print("切片 arr2d[0:2, 1:3]:\n", arr2d[0:2, 1:3]) # 第0, 1行 和 第1, 2列
# 结果: [[2, 3], [5, 6]]
print("仅获取第1行 arr2d[1, :]:", arr2d[1, :]) # 获取整行
print("仅获取第2列 arr2d[:, 2]:", arr2d[:, 2]) # 获取整列 -> [3, 6, 9]
print("-" * 20)# 2.3 布尔索引 (非常强大)
mask = arr2d > 5 # 创建布尔数组 (True/False)
print("布尔掩码:\n", mask)
print("arr2d[mask]:", arr2d[mask]) # 只保留 True 对应的元素 -> [6 7 8 9]
arr2d[arr2d < 3] = 0 # 将所有小于 3 的元素设置为 0
print("修改后的 arr2d:\n", arr2d)
print("-" * 20)# --- 三、 数组的基本操作 (Basic Operations) ---print("--- 3. 数组的基本操作 ---")m1 = np.array([1, 2, 3])
m2 = np.array([4, 5, 6])# 3.1 算术运算 (逐元素操作/Element-wise)
print("加法 m1 + m2:", m1 + m2) # [5, 7, 9]
print("乘法 m1 * m2:", m1 * m2) # [4, 10, 18]
print("标量运算 m1 * 2:", m1 * 2) # [2, 4, 6]
print("-" * 20)# 3.2 矩阵乘法 (使用 @ 或 np.dot())
matA = np.array([[1, 2], [3, 4]])
matB = np.array([[5, 6], [7, 8]])
matC = matA @ matB # 矩阵乘法
print("矩阵 A:\n", matA)
print("矩阵 B:\n", matB)
print("矩阵乘法 A @ B:\n", matC)
print("-" * 20)# 3.3 聚合函数 (Aggregation)
arr_agg = np.array([[10, 20], [30, 40]])
print("arr_agg:\n", arr_agg)
print("总和:", arr_agg.sum()) # 100
print("最小值:", arr_agg.min()) # 10
print("按列求和 (axis=0):", arr_agg.sum(axis=0)) # [40, 60]
print("按行求和 (axis=1):", arr_agg.sum(axis=1)) # [30, 70]
print("-" * 20)# --- 四、 数组形状操作 (Shape Manipulation) ---print("--- 4. 数组形状操作 ---")# 4.1 reshape (改变数组形状,不改变数据)
arr_r = np.arange(12)
arr_2x6 = arr_r.reshape(2, 6)
arr_3x4 = arr_r.reshape(3, 4)
arr_4x_1 = arr_r.reshape(4, -1) # -1 自动计算维度
print("原始数组:\n", arr_r)
print("重塑为 3x4:\n", arr_3x4)
print("-" * 20)# 4.2 flatten (将数组展平为一维)
arr_flat = arr_3x4.flatten()
print("展平后:", arr_flat)
print("-" * 20)# 4.3 堆叠 (Stacking)
s1 = np.array([1, 2, 3])
s2 = np.array([4, 5, 6])
v_stack = np.vstack((s1, s2)) # 垂直堆叠 (行增加)
h_stack = np.hstack((s1, s2)) # 水平堆叠 (列增加)
print("垂直堆叠 (vstack):\n", v_stack)
print("水平堆叠 (hstack):", h_stack)
print("-" * 20)# --- 五、 NumPy 与 Python 列表的性能对比 (简要) ---print("--- 5. 性能对比 (NumPy 优势) ---")
array_size = 1000000# 5.1 Python 列表加法
list_a = list(range(array_size))
list_b = list(range(array_size))t0 = time.time()
list_result = [list_a[i] + list_b[i] for i in range(array_size)]
t1 = time.time()
print(f"Python 列表加法耗时: {(t1 - t0):.4f} 秒")# 5.2 NumPy 数组加法 (向量化操作)
np_a = np.array(list_a)
np_b = np.array(list_b)t2 = time.time()
np_result = np_a + np_b
t3 = time.time()
print(f"NumPy 数组加法耗时: {(t3 - t2):.4f} 秒")
# NumPy 通常快数十到数百倍
总结
NumPy 不仅仅是 OpenCV 的一个依赖库,它是 OpenCV-Python 图像处理功能的基石。
特性 | NumPy 作用 | 核心意义 |
---|---|---|
数据表示 | 将图像表示为多维数组 (ndarray ) | 统一、高效的数据结构 |
性能 | 实现底层 C 优化的向量化操作 | 显著提升图像处理速度 |
操作便捷性 | 提供强大的索引、切片和广播机制 | 简化复杂的像素级和区域操作 |
互操作性 | 作为科学计算领域的通用数组格式 | 便于与 Matplotlib、SciPy 等库集成 |