参考:《计算机视觉开发实践》朱文伟 李建英 著
前言
在 OpenCV 中:
import cv2 as cv
img = cv.imread("test.jpg")
- img 的类型是 numpy.ndarray(数组),包含每个像素点的数据。
- 熟悉 NumPy 是操作图像数据的基础。它极大简化了向量/矩阵运算,是数据分析、机器学习和科学计算的基石。
又要开始掏出那本线性代数。
4.1 NumPy 概述
NumPy 是一个高性能的数学库,核心能力:
- 强大的 N 维数组对象 ndarray
- 广播(Broadcasting)
- 与 C/C++/Fortran 互操作
- 线性代数、傅里叶变换、随机数
NumPy 常与 SciPy(科学计算)和 Matplotlib(可视化)搭配使用,常见于替代 MATLAB 的技术计算场景。NumPy 是开源项目。
为什么使用 NumPy?
- Python 的 list 保存的是“对象指针”,如 [1,2,3,4] 需要 4 个指针和 4 个整数对象,运算开销大。
- array 模块的 array 能直接保存数值,但不支持多维数组,也缺乏数值计算相关函数。
- NumPy 提供:
- ndarray(同类型、多维数组,高效存储与运算)
- 通用函数(ufunc,向量化计算)
4.2 ndarray 对象
4.2.1 简介
ndarray 是同类型元素的多维数组,内部包含:
- 指向数据(内存或内存映射)的指针
- 数据类型 dtype(描述固定大小元素)
- 形状 shape(各维度大小的元组)
- 跨度 stride(到同维度下一个元素需跨过的字节数)

4.2.2 创建 ndarray
函数原型:
numpy.array(object, dtype=None, copy=True, order=None, subok=False, ndmin=0)
参数要点:
- object:数组或嵌套序列
- dtype:元素数据类型
- copy:是否复制
- order:内存布局,”C” 行优先,”F” 列优先,”A” 自动
- subok:是否保持子类
- ndmin:最小维度
4.3 NumPy 的数据类型
4.3.1 类型概览
- 布尔:bool(True/False)
- 整数:int8、int16、int32、int64
- 无符号整数:uint8、uint16、uint32、uint64
- 浮点:float16、float32、float64
- 复数:complex64、complex128
- 字符串:str(字节串 S 或 Unicode U)
- 对象:object
4.3.2 numpy.dtype()
函数原型:
numpy.dtype(object, align=False, copy=False)
示例:
import numpy as np
dt = np.dtype(np.int32) # int32
print(dt)
dt = np.dtype('i4') # int32 的别名
print(dt)
dt = np.dtype([('age', np.int8)])
print(dt) # [('age', 'i1')]
dt = np.dtype([('age', np.int8), ('name', 'S20')])
print(dt) # 结构化 dtype
dt = np.dtype([('age', np.int8), ('name', 'S20')], align=True)
print(dt)
4.4 数组属性
4.4.1 轴与维度(axis)
在 NumPy 中,每一条“线性方向”称为一个轴(axis)。
二维数组示例:
import numpy as np
a = np.array([
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
])
# shape = (3, 3)
print(np.sum(a, axis=0))
# 列方向相加
[ (1+4+7), (2+5+8), (3+6+9) ] -> [12 15 18]
print(np.sum(a, axis=1))
# 行方向相加
[ (1+2+3), (4+5+6), (7+8+9) ] -> [ 6 15 24]
可视化理解:
```
axis=1 →
┌────────────┐
│ 1 2 3 │ axis=0
│ 4 5 6 │ ↓
│ 7 8 9 │
└────────────┘
```
三维数组示例:
a = np.array([
[
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
],
[
[9, 8, 7],
[6, 5, 4],
[3, 2, 1]
]
])
# shape = (2, 3, 3)
axis=0 ↓ (矩阵层的方向)
┌──────────┐ ┌──────────┐
│ 1 2 3 │ │ 9 8 7 │
│ 4 5 6 │ │ 6 5 4 │
│ 7 8 9 │ │ 3 2 1 │
└──────────┘ └──────────┘
axis=1 ↓ axis=1 ↓
行方向 行方向
axis=2 → axis=2 →
列方向 列方向
np.sum(a, axis=0)
对应位置相加:
[ [
[ (1+9), (2+8), (3+7) ], [10, 10, 10],
[ (4+6), (5+5), (6+4) ] → [10, 10, 10],
[ (7+3), (8+2), (9+1) ] [10, 10, 10]
] ]
np.sum(a, axis=1)
对每个2D矩阵的行进行相加:
[ [
[ (1+4+7), (2+5+8), (3+6+9) ], → [12, 15, 18],
[ (9+6+3), (8+5+2), (7+4+1) ] [18, 15, 12]
]
np.sum(a, axis=2)
对每个2D矩阵的列进行相加:
[ [
[ (1+2+3), (4+5+6), (7+8+9) ], → [6, 15, 24],
[ (9+8+7), (6+5+4), (3+2+1) ] [24, 15, 6]
] ]
总结:axis 的编号,就是告诉 numpy “在哪一维上压缩”。数越小,维度越外层(0 最外层,2 最内层)。
4.4.2 ndarray 常用属性
import numpy as np
a = np.arange(24)
b = a.reshape(2, 4, 3)
print(a.ndim, b.ndim) # 维度:1, 3
print(a.shape, b.shape) # 形状:(24,), (2, 4, 3)
print(a.size, b.size) # 元素总数:24, 24
print(a.dtype, b.dtype) # 数据类型:int32, int32(随平台而异)
print(a.itemsize) # 每个元素占字节数,例如 4
# OpenCV 图像就是 ndarray
import cv2
img = cv2.imread("test.jpg")
print(type(img)) # <class 'numpy.ndarray'>
print(img.ndim) # 3(H, W, C)
print(img.shape) # (高度, 宽度, 通道数)
print(img.size) # 像素总数 * 通道数
print(img.dtype) # 常见为 uint8
4.5 新建数组
常用方式:
import numpy as np
# 1) 由列表/元组创建
a = np.array([1, 2, 3, 4, 5])
b = np.array([[1, 2, 3], [4, 5, 6]])
# 2) 等差序列
ar = np.arange(0, 10, 2) # [0 2 4 6 8]
# 3) 等间隔序列
ls = np.linspace(0, 1, 5) # [0. 0.25 0.5 0.75 1. ]
# 4) 全零 / 全一
z = np.zeros((3, 4))
o = np.ones((2, 3))
# 5) 单位矩阵
I = np.eye(3)
# 6) 随机数组([0,1))
r = np.random.rand(2, 3)
# 7) empty(未初始化,值未定义)
e = np.empty((2, 2))
参数说明:
- order 有 “C” 和 “F” 两种(行优先/列优先),影响内存布局和部分运算性能。
4.5 数组切片和索引
NumPy 支持类似 Python 列表的切片/索引,并扩展到多维。
一维示例:
import numpy as np
a = np.array([10, 20, 30, 40, 50])
print(a[0]) # 10(第一个元素)
print(a[1:4]) # [20 30 40](左闭右开)
a[2] = 99
print(a) # [10 20 99 40 50]
二维示例:
b = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print(b[0, 1]) # 2(第 1 行第 2 列)
print(b[1:3, 0:2]) # [[4 5]
# [7 8]]
b[2, 2] = 99
print(b)
切片语义:
- [2]:返回单个元素
- [2:]:从索引 2 开始到末尾
- [2:7]:索引 [2, 7) 的切片(不含 7)
4.6 迭代数组
4.6.1 nditer
numpy.nditer(op, flags=None, op_flags=None, order='K', casting='safe', itershape=None, buffersize=0)
- op:要迭代的数组/数组序列
- flags:迭代器工作方式
- op_flags:每个操作数读写权限
- order:迭代顺序
- casting:类型转换规则
- itershape:迭代器形状
- buffersize:缓冲区大小
4.6.2 基本迭代
import numpy as np
a = np.array([[1, 2, 3], [4, 5, 6]])
for x in np.nditer(a):
print(x, end=' ')
# 1 2 3 4 5 6
4.6.3 修改元素
a = np.array([[1, 2, 3], [4, 5, 6]])
for x in np.nditer(a, op_flags=['readwrite']):
x[...] = x * 2
print(a)
# [[ 2 4 6]
# [ 8 10 12]]
4.6.4 多数组迭代
a = np.array([[1, 2, 3], [4, 5, 6]])
b = np.array([[10, 20, 30], [40, 50, 60]])
for x, y in np.nditer([a, b]):
print(f'a: {x}, b: {y}')
# a: 1, b: 10
# a: 2, b: 20
# a: 3, b: 30
# a: 4, b: 40
# a: 5, b: 50
# a: 6, b: 60
4.7 数组形状操作
4.7.1 重塑 reshape
import numpy as np
a = np.arange(12)
b = a.reshape((3, 4))
print(b)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
- order:’C’ 按行优先,’F’ 按列优先,’A’ 遵循原数组顺序
4.7.2 扁平化 flatten
a = np.array([[1, 2, 3], [4, 5, 6]])
b = a.flatten() # [1 2 3 4 5 6]
4.7.3 转置 transpose
a = np.array([[1, 2, 3], [4, 5, 6]])
b = np.transpose(a)
print(b)
# [[1 4]
# [2 5]
# [3 6]]
小结
- OpenCV 读入的图像就是一个 dtype 通常为 uint8 的三维 ndarray。
- 熟悉 dtype、shape、ndim、stride 能帮助你写出更高效的图像算法。
- 掌握切片、广播、迭代与形状变换,可以用极少代码完成复杂的数据处理。