欢迎来到计算机视觉的世界!如果你正准备踏上OpenCV的学习之旅,那么你一定听说过NumPy。它不仅仅是一个普通的Python库,更是OpenCV在Python中进行图像处理、数据操作的基石。可以说,不理解NumPy,就很难真正掌握OpenCV的精髓。
这篇博客带你快速入门NumPy,了解其核心概念和常用操作,为后续学习OpenCV扫清障碍。
为什么先学NumPy?
在OpenCV中,图像被表示为多维NumPy数组:
- 灰度图像:一个二维数组(高度✖宽度)
- 彩色图像:一个三维数组(高度✖宽度✖通道数),例如BGR(蓝绿红)图像的通道数为3。
NumPy提供了高效的数组操作,这对于处理包含成千上万甚至数百万像素的图像至关重要。Python原生的列表(list)在处理大规模数值运算时效率较低,而NumPy底层由C语言实现,能进行快速的向量化运算。
本教程将涵盖:
1.什么是NumPy数组?
2.如何创建NumPy数组?
3.数组的属性(形状、数据类型等)
4.数组的索引与切片
5.基本的数组运算
6.常用的NumPy函数
准备工作
首先,确保你已经安装了Python。然后,通过pip安装NumPy:
pip install numpy
在你的Python脚本或jupyter Notebook中,我们通常这样导入numPy:
import numpy as np
np是NumPy的标准别名,你会发现几乎所有的NumPy用户都这么做。
1.什么是NumPy数组(ndarray)?
NumPy的核心是ndarray对象。它是一个多维数组,用于存储同类型的元素(通常是数字),比如整数(int)或浮点数(float)。
与Python列表的区别:
类型统一:NumPy数组中的所有元素必须是相同的数据类型,这使得存储和计算更高效。
性能:NumPy数组操作通常比等效的Python列表操作快得多,尤其是在大数据集上。
功能:NumPy提供了大量针对数组优化的数学函数。
2.如何创建NumPy数组?
有多种方法可以创建NumPy数组:
a.从Python列表或元组创建
#一维数组
arrld = np.array([1,2,3,4,5])
print("一维数组:")
print(arr1d)
# 二维数组
arr2d = np.array([[1, 2, 3], [4, 5, 6]])
print("n二维数组:")
print(arr2d)
# 三维数组
arr3d = np.array([[[1,2,3], [4,5,6]], [[7,8,9], [10,11,12]]])
print("n三维数组:")
print(arr3d)
b.使用NumPy内置函数创建
- np.zeros(): 创建全零数组。
- np.ones(): 创建全一数组。
- np.arange(): 类似于 Python 的 range(),但返回一个数组。
- np.linspace(): 创建等差数列。
- np.random.rand() / np.random.randn() / np.random.randint(): 创建随机数组。
# 创建一个 2x3 的全零数组 (常用于初始化)
zeros_arr = np.zeros((2, 3))
print("n全零数组:")
print(zeros_arr)
# 创建一个 3x2 的全一数组,数据类型为整数
ones_arr = np.ones((3, 2), dtype=np.int16)
print("n全一数组 (整数类型):")
print(ones_arr)
# 创建从 0 到 9 的数组
range_arr = np.arange(10)
print("n0到9的数组:")
print(range_arr)
# 创建从 0 到 10,包含 5 个等间距元素的数组
linspace_arr = np.linspace(0, 10, 5)
print("n等差数组:")
print(linspace_arr)
# 创建一个 2x2 的,元素在 [0, 1) 之间均匀分布的随机数组
random_arr = np.random.rand(2, 2)
print("n随机数组 [0,1):")
print(random_arr)
# 创建一个 3x3 的,[0, 10) 之间的随机整数数组
random_int_arr = np.random.randint(0, 10, size=(3,3))
print("n随机整数数组 [0,10):")
print(random_int_arr)
注意 dtype 参数: 在创建数组时,可以指定数据类型。对于图像处理,np.uint8 (0-255 的无符号整数) 是非常常见的数据类型。
3.数组的属性
了解数组的属性对于操作它们至关重要:
- ndarray.ndim: 数组的轴(维度)的个数。
- ndarray.shape: 数组的维度。这是一个整数元组,表示每个维度中数组的大小。例如,一个 n 行 m 列的矩阵,它的 shape 将是 (n, m)。对于一个彩色图像,可能是 (height, width, 3)。
- ndarray.size: 数组元素的总个数。等于 shape 属性中元组元素的乘积。
- ndarray.dtype: 一个描述数组中元素类型的对象。例如 numpy.int32, numpy.float64, numpy.uint8。
- ndarray.itemsize: 数组中每个元素的字节大小。
img_array = np.random.randint(0, 256, size=(100, 200, 3), dtype=np.uint8) # 模拟一个 100x200 的彩色图像
print(f"n模拟图像数组属性:")
print(f"维度数量 (ndim): {img_array.ndim}")
print(f"形状 (shape): {img_array.shape}") # (高度, 宽度, 通道数)
print(f"元素总数 (size): {img_array.size}")
print(f"数据类型 (dtype): {img_array.dtype}")
print(f"每个元素的字节大小 (itemsize): {img_array.itemsize}")
4.数组的索引与切片
这部分与 Python 列表的索引和切片非常相似,但可以扩展到多维。
a.一维数组索引与切片
arr = np.arange(10) # [0 1 2 3 4 5 6 7 8 9]
print(f"n一维数组: {arr}")
# 获取单个元素
print(f"元素在索引 3: {arr[3]}") # 输出 3
# 切片 [start:stop:step] (不包括 stop)
print(f"从索引 2 到 5: {arr[2:6]}") # 输出 [2 3 4 5]
print(f"所有元素,步长为 2: {arr[::2]}") # 输出 [0 2 4 6 8]
b.多维数组索引与切片
这是OpenCV中最常用的操作之一,例如提取图像的某个区域(ROI)
# 创建一个 3x4 的二维数组
matrix = np.array([[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12]])
print(f"n二维数组 matrix:n{matrix}")
# 获取单个元素 (row, column)
print(f"第 1 行, 第 2 列的元素 (0-indexed): {matrix[1, 2]}") # 输出 7
# 获取某一行
print(f"第 0 行: {matrix[0, :]}") # 输出 [1 2 3 4] 或者 matrix[0]
print(f"第 0 行 (另一种写法): {matrix[0]}")
# 获取某一列
print(f"第 1 列: {matrix[:, 1]}") # 输出 [ 2 6 10]
# 子区域切片 (ROI)
# 提取 matrix 的左上角 2x2 子矩阵
roi = matrix[0:2, 0:2]
print(f"ROI (左上角 2x2):n{roi}")
# 输出:
# [[1 2]
# [5 6]]
# 对于三维数组 (如彩色图像),索引类似:arr[height_slice, width_slice, channel_slice]
# 例如,获取彩色图像的蓝色通道: blue_channel = img_array[:, :, 0] (假设 BGR 顺序)
重要: NumPy 切片返回的是原始数组的视图 (view),而不是副本。这意味着修改视图会直接影响原始数组。如果需要副本,请使用 .copy() 方法,例如 roi_copy = matrix[0:2, 0:2].copy()。
5.基本的数组运算
NumPy允许你对整个数组执行元素级运算,这非常高效。
a.标量运算:数组与单个数字(标量)的运算。
arr = np.array([[1, 2], [3, 4]])
print(f"n原始数组 arr:n{arr}")
print(f"arr + 5:n{arr + 5}")
print(f"arr * 2:n{arr * 2}")
print(f"arr / 2:n{arr / 2}")
print(f"arr ** 2 (平方):n{arr ** 2}")
b.数组间运算: 两个形状相同的数组之间的运算。
a = np.array([[1, 2], [3, 4]])
b = np.array([[5, 6], [7, 8]])
print(f"n数组 a:n{a}")
print(f"数组 b:n{b}")
print(f"a + b:n{a + b}")
print(f"a * b (元素级乘法):n{a * b}")
注意: a * b 是元素级乘法,不是矩阵乘法。矩阵乘法使用 @ 运算符或 np.dot() 函数。
print(f"a @ b (矩阵乘法):n{a @ b}")
print(f"np.dot(a, b) (矩阵乘法):n{np.dot(a, b)}")
c. 广播:
当操作不同形状的数组时,NumPy 有一套广播规则,可以自动扩展较小数组的维度,使其与较大数组兼容。这是一个强大但有时也容易混淆的特性。
一个简单的例子:
arr = np.array([[1, 2, 3], [4, 5, 6]]) # shape (2, 3)
row_vector = np.array([10, 20, 30]) # shape (3,) -> 广播为 (1, 3)
print(f"n数组 arr:n{arr}")
print(f"行向量 row_vector:n{row_vector}")
# row_vector 会被广播到 arr 的每一行进行相加
print(f"arr + row_vector (广播):n{arr + row_vector}")
# 输出:
# [[11 22 33]
# [14 25 36]]
6.常用的NumPy函数
NumPy函数提供了大量的数学函数,称为通用函数,它们可以直接作用于数组。
arr = np.array([-1, 0, 1, 2, 3, 4])
data = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print(f"n数组 data:n{data}")
# 基本统计
print(f"data 中所有元素的和: {np.sum(data)}")
print(f"data 中所有元素的平均值: {np.mean(data)}")
print(f"data 中最大值: {np.max(data)}")
print(f"data 中最小值: {np.min(data)}")
print(f"data 标准差: {np.std(data)}")
# 沿轴操作 (axis=0 表示沿列操作,axis=1 表示沿行操作)
print(f"data 每列的和 (axis=0): {np.sum(data, axis=0)}") # [12 15 18]
print(f"data 每行的最大值 (axis=1): {np.max(data, axis=1)}") # [3 6 9]
# 其他常用函数
print(f"arr 的平方根 (负数会产生 nan): {np.sqrt(np.abs(arr))}") # np.abs取绝对值避免负数开方错误
print(f"arr 的指数: {np.exp(arr)}")
print(f"数组中大于2的元素: {data[data > 2]}") # 布尔索引
# 改变数组形状
flat_arr = data.flatten() # 将多维数组转换为一维数组
print(f"data 展平后: {flat_arr}")
reshaped_arr = data.reshape((1, 9)) # 重塑为 1x9 数组
print(f"data 重塑为 1x9:n{reshaped_arr}")
总结与展望
恭喜你!你已经了解了 NumPy 的基础知识,包括:
- ndarray 对象及其与 Python 列表的区别。
- 创建数组的多种方法。
- 获取数组的关键属性,如形状、数据类型。
- 强大的索引和切片能力,特别是对于多维数组。
- 高效的元素级运算和广播机制。
- 一些常用的数学和统计函数。
这些知识是你学习 OpenCV 的关键。当你开始用 OpenCV 加载图像时,你会发现 cv2.imread() 返回的就是一个 NumPy 数组。你将使用 NumPy 的切片来选取图像区域,使用 NumPy 的运算来修改像素值,使用 NumPy 的函数来进行各种图像分析。
下一步:
- 多练习! 亲手敲代码是掌握 NumPy 最好的方法。尝试创建不同形状和数据类型的数组,并对它们进行各种操作。
- 查阅官方文档: NumPy 的[官方文档](NumPy 文档 — NumPy v2.3 手册)非常全面,是深入学习的好资源。
- 开始学习 OpenCV: 现在你已经准备好将 NumPy 的知识应用到实际的图像处理中了!
希望这篇入门教程对你有所帮助。在学习 OpenCV 的道路上,NumPy 将是你最忠实的伙伴。祝学习愉快!