第 3 章

学习 OpenCV 需要的 Numpy 知识

本章共 6 节,介绍 ndarray、资料型态、阵列建立、切片、axis、vstack 与 hstack。
Python 的列表和元组可以表示一维或多维资料,但是在大量数值运算时,执行速度与系统资源会成为问题。 Numpy 是 Numerical Python 的缩写,常用于科学运算、影像处理与人工智能。OpenCV 读取的灰阶图、彩色图和视频画面,都可以看成 Numpy 的多维阵列;每一个数值就是影像中的像素资料。因此,在进入 OpenCV 影像处理前,需要先熟悉 Numpy 阵列的基本操作。
3-1

阵列 ndarray

Numpy 所建立的阵列资料型态称为 ndarray,也就是 n-dimension array,表示 n 维阵列。 例如一维阵列、二维阵列、三维阵列,都属于 ndarray 的范畴。OpenCV 的影像资料在 Python 中也以 ndarray 表示。

ndarray 与 Python 一般列表不同,主要特性如下:

因为阵列大小固定且元素型态一致,Numpy 可以用更有效率的方式储存与运算资料。在处理影像时,这一点非常重要。

3-2

Numpy 的资料型态

Numpy 支援多种资料型态。OpenCV 影像最常见的是 uint8,也就是 8 位元无符号整数,可表示 0 到 255 的像素值。

资料型态说明
bool布尔型态,值为 TrueFalse
int整数型态,依平台而定。
intc对应 C 语言的 int
intp用于索引的整数型态。
int88 位元有符号整数。
int1616 位元有符号整数。
int3232 位元有符号整数。
int6464 位元有符号整数。
uint88 位元无符号整数,范围 0 到 255。
uint1616 位元无符号整数。
uint3232 位元无符号整数。
uint6464 位元无符号整数。
float浮点数型态。
float16半精度浮点数。
float32单精度浮点数。
float64双精度浮点数。
complex复数型态。
complex64由两个 32 位元浮点数表示的复数。
complex128由两个 64 位元浮点数表示的复数。
3-3

建立一维或多维阵列

3-3-1 认识 ndarray 的属性

建立 Numpy 阵列后,常用下列属性查看阵列的资料型态、维度、形状与元素数量。

属性说明
dtype阵列元素的资料型态。
itemsize阵列中每个元素占用的位元组数。
ndim阵列的维度。
shape阵列的形状。
size阵列的元素总数。

3-3-2 使用 array() 建立一维阵列

array() 可以将列表或元组转换成 Numpy 阵列。

语法
numpy.array(object, dtype=None, copy=True, order='K', subok=False, ndmin=0)
参数说明
object要转换为阵列的对象。
dtype指定阵列元素的资料型态,默认由资料自动判断。
copy是否复制对象,默认值为 True
order储存顺序,可为 KACF
subok是否允许传回子类别,默认值为 False
ndmin指定阵列至少要有的维度。
实例 1:建立一维阵列
import numpy as np x = np.array([1, 2, 3]) print(type(x)) print(x)
执行结果
<class 'numpy.ndarray'>
[1 2 3]

建立阵列后,可以使用索引读取阵列元素。索引从 0 开始。

实例 2:读取一维阵列元素
import numpy as np x = np.array([1, 2, 3]) print(x[0]) print(x[1]) print(x[2])
执行结果
1
2
3
实例 3:修改一维阵列元素
import numpy as np x = np.array([1, 2, 3]) x[1] = 10 print(x)
执行结果
[ 1 10  3]
实例 4:查看 ndarray 属性
import numpy as np x = np.array([1, 2, 3]) print(x.dtype) print(x.itemsize) print(x.ndim) print(x.shape) print(x.size)
执行结果
int32
4
1
(3,)
3

上例中,dtype 表示元素资料型态为 int32,每个元素占用 4 个位元组;ndim 为 1,表示这是一维阵列;shape(3,),表示阵列有 3 个元素;size 也是 3。

实例 5:指定 int8 资料型态
import numpy as np x = np.array([2, 4, 6], dtype=np.int8) print(x.dtype) print(x.itemsize)
执行结果
int8
1
实例 6:建立浮点数阵列
import numpy as np x = np.array([1.1, 2.3, 3.6]) print(x.dtype) print(x) print(repr(x))
执行结果
float64
[1.1 2.3 3.6]
array([1.1, 2.3, 3.6])

3-3-3 使用 array() 函数建立多维阵列

如果传入巢状列表,或指定 ndmin,可以建立二维或多维阵列。

ch3_1.py:建立二维与三维阵列
import numpy as np x1 = np.array([1, 2, 3], ndmin=2) x2 = np.array([1, 2, 3], ndmin=3) print(x1) print(x2)
ch3_1.py 执行结果
ch3_1.py 执行结果
ch3_2.py:使用巢状列表建立二维阵列
import numpy as np x = np.array([[1, 2, 3], [4, 5, 6]]) print(x)
执行结果
[[1 2 3]
 [4 5 6]]

二维阵列可以使用 x[列][行]x[列, 行] 访问元素。例如 x[0][2]x[0, 2] 都表示第 0 列第 2 行的元素。

ch3_3.py:认识二维阵列索引
import numpy as np x = np.array([[1, 2, 3], [4, 5, 6]]) print(x[0][2]) print(x[1][2]) print(x[0, 2]) print(x[1, 2])
执行结果
3
6
3
6

3-3-4 使用 zeros() 建立内容是 0 的多维阵列

语法
np.zeros(shape, dtype=float)

shape 指定阵列形状,dtype 指定资料型态。若未指定 dtype,默认使用浮点数。

ch3_4.py:建立全 0 阵列
import numpy as np x1 = np.zeros(3) x2 = np.zeros((2, 3), dtype=np.uint8) print(x1) print(x2)
执行结果
[0. 0. 0.]
[[0 0 0]
 [0 0 0]]

在 OpenCV 中,像素值为 0 的影像会呈现黑色,因此 zeros() 常用于建立黑色影像或初始化空白影像资料。

3-3-5 使用 ones() 建立内容是 1 的多维阵列

语法
np.ones(shape, dtype=None)
ch3_5.py:建立全 1 阵列
import numpy as np x1 = np.ones(3) x2 = np.ones((2, 3), dtype=np.uint8) print(x1) print(x2)
执行结果
[1. 1. 1.]
[[1 1 1]
 [1 1 1]]

若要建立白色影像,可将 ones() 建立的阵列乘以 255。

ch3_5_1.py:建立全白资料
import numpy as np x = np.ones((2, 3), dtype=np.uint8) * 255 print(x)
执行结果
[[255 255 255]
 [255 255 255]]

3-3-6 使用 empty() 建立未初始化阵列

语法
np.empty(shape, dtype=float)

empty() 会建立指定形状的阵列,但不会将元素初始化为固定值,因此内容可能是内存中原有的任意数值。

ch3_6.py:建立未初始化阵列
import numpy as np x1 = np.empty(3) x2 = np.empty((2, 3), dtype=np.uint8) print(x1) print(x2)
执行结果
[6.23042070e-307 4.67296746e-307 1.69121096e-306]
[[  0   0   0]
 [  0   0   0]]

上述结果会因电脑和执行环境不同而变化,重点是 empty() 不会先填入 0 或 1。

3-3-7 使用 random.randint() 建立随机整数阵列

语法
np.random.randint(low, high=None, size=None, dtype=int)
参数说明
low随机数下限。
high随机数上限,产生的值不包含此数。
size输出阵列形状。
dtype输出资料型态。
ch3_7.py:建立随机整数资料
import numpy as np x1 = np.random.randint(5) x2 = np.random.randint(1, 5, size=10) x3 = np.random.randint(0, 10, size=(3, 5)) print(x1) print(x2) print(x3)
执行结果范例
3
[1 4 1 2 4 2 1 3 3 4]
[[8 9 7 8 1]
 [5 0 6 9 4]
 [2 7 1 5 6]]

3-3-8 使用 arange() 建立连续整数阵列

语法
np.arange(start, stop, step)

start 是起始值,stop 是停止值,step 是间隔。若只给一个参数,则代表从 0 开始到该值前一项为止。

ch3_7_1.py:使用 arange()
import numpy as np x = np.arange(16) print(x)
执行结果
[ 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15]

3-3-9 使用 reshape() 改变阵列形状

语法
np.reshape(a, newshape)

reshape() 可以重新安排阵列形状,但是元素总数必须相同。如果新形状中的某一维写成 -1,Numpy 会自动计算该维度。

ch3_7_2.py:将一维阵列改为 2 x 8
import numpy as np x = np.arange(16) x = np.reshape(x, (2, 8)) print(x)
执行结果
[[ 0  1  2  3  4  5  6  7]
 [ 8  9 10 11 12 13 14 15]]
ch3_7_3.py:使用 -1 自动计算第二维
import numpy as np x = np.arange(16) x = np.reshape(x, (4, -1)) print(x)
执行结果
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]
 [12 13 14 15]]
ch3_7_4.py:使用 -1 自动计算第一维
import numpy as np x = np.arange(16) x = np.reshape(x, (-1, 8)) print(x)
执行结果
[[ 0  1  2  3  4  5  6  7]
 [ 8  9 10 11 12 13 14 15]]
3-4

一维阵列的运算与切片

3-4-1 一维阵列四则运算

Numpy 阵列可以直接进行加、减、乘、除等运算。若与单一数值运算,阵列中的每个元素都会参与运算;若两个阵列形状相同,则对应位置的元素逐一运算。

运算子说明
+加法
-减法
*乘法
/除法
//整数除法
%余数
**次方
例 1:阵列与数值相加
import numpy as np x = np.array([1, 2, 3]) print(x + 5)
执行结果
[6 7 8]
例 2:两个阵列相加
import numpy as np x = np.array([1, 2, 3]) y = np.array([10, 20, 30]) print(x + y)
执行结果
[11 22 33]
例 3:两个阵列相乘
import numpy as np x = np.array([1, 2, 3]) y = np.array([10, 20, 30]) print(x * y)
执行结果
[10 40 90]
例 4:除法与整数除法
import numpy as np x = np.array([1, 2, 3]) y = np.array([10, 20, 30]) print(x / y) print(x // y)
执行结果
[0.1 0.1 0.1]
[0 0 0]

3-4-2 一维阵列的关系运算子运算

阵列也可以使用关系运算子,执行结果会是布尔阵列。

运算子说明
>大于
>=大于或等于
<小于
<=小于或等于
==等于
!=不等于
关系运算范例
import numpy as np x = np.array([1, 2, 3]) y = np.array([10, 20, 30]) z = x > y print(z) z = x < y print(z)
执行结果
[False False False]
[ True  True  True]

3-4-3 阵列切片

切片可以读取阵列中的部分资料,语法如下。

语法
array[start:end:step]
参数说明
start起始索引。
end结束索引,不包含此位置的元素。
step间隔,若省略则为 1。

常见写法包括 arr[start:end]arr[:n]arr[:-n]arr[n:]arr[-n:]arr[:]

ch3_8.py:一维阵列切片
import numpy as np x = np.arange(10) print("x[2:] =", x[2:]) print("x[:2] =", x[:2]) print("x[0:3] =", x[0:3]) print("x[1:4] =", x[1:4]) print("x[0:9:2] =", x[0:9:2]) print("x[-1] =", x[-1]) print("x[::2] =", x[::2]) print("x[2::3] =", x[2::3]) print("x[:] =", x[:]) print("x[::-1] =", x[::-1]) print("x[-3:-7:-1] =", x[-3:-7:-1])
执行结果
x[2:] = [2 3 4 5 6 7 8 9]
x[:2] = [0 1]
x[0:3] = [0 1 2]
x[1:4] = [1 2 3]
x[0:9:2] = [0 2 4 6 8]
x[-1] = 9
x[::2] = [0 2 4 6 8]
x[2::3] = [2 5 8]
x[:] = [0 1 2 3 4 5 6 7 8 9]
x[::-1] = [9 8 7 6 5 4 3 2 1 0]
x[-3:-7:-1] = [7 6 5 4]

3-4-4 使用参数 copy=True 复制数据

使用 np.array() 建立阵列时,若参数 copy=True,会复制原始资料。复制后的阵列修改时,不会影响原阵列。

语法
x2 = np.array(x1, copy=True)
ch3_9.py:使用 copy=True 复制阵列
import numpy as np x1 = np.array([1, 2, 3]) x2 = np.array(x1, copy=True) print("x1 =", x1) print("x2 =", x2) print("----------------") x2[0] = 9 print("x1 =", x1) print("x2 =", x2)
执行结果
x1 = [1 2 3]
x2 = [1 2 3]
----------------
x1 = [1 2 3]
x2 = [9 2 3]

3-4-5 使用 copy() 函数复制阵列

copy() 也可以复制阵列,常用于影像处理。先复制原图,再对复制图执行处理,可以保留原始影像。

语法
x2 = x1.copy()
ch3_10.py:使用 copy() 复制阵列
import numpy as np x1 = np.array([1, 2, 3]) x2 = x1.copy() print("x1 =", x1) print("x2 =", x2) print("----------------") x2[0] = 9 print("x1 =", x1) print("x2 =", x2)
执行结果
x1 = [1 2 3]
x2 = [1 2 3]
----------------
x1 = [1 2 3]
x2 = [9 2 3]
3-5

多维阵列的索引与切片

3-5-1 认识 axis 的定义

在多维阵列中,axis 表示轴的方向。二维阵列可以看成列与行,axis=0 表示垂直方向,axis=1 表示水平方向。

ch3_11.py:二维阵列与 axis
import numpy as np x = np.array([[0, 1, 2, 3, 4], [5, 6, 7, 8, 9], [10, 11, 12, 13, 14]]) print(x)
执行结果
[[ 0  1  2  3  4]
 [ 5  6  7  8  9]
 [10 11 12 13 14]]

上例中,外层阵列有 3 个子阵列,所以 axis=0 的长度为 3;每个子阵列内有 5 个元素,所以 axis=1 的长度为 5。

ch3_12.py:三维阵列与 axis
import numpy as np x = np.array([[[0, 1, 2, 3, 4], [5, 6, 7, 8, 9], [10, 11, 12, 13, 14]], [[0, 1, 2, 3, 4], [5, 6, 7, 8, 9], [10, 11, 12, 13, 14]]]) print(x) print(x.shape)
执行结果
[[[ 0  1  2  3  4]
  [ 5  6  7  8  9]
  [10 11 12 13 14]]

 [[ 0  1  2  3  4]
  [ 5  6  7  8  9]
  [10 11 12 13 14]]]
(2, 3, 5)

三维阵列有三个轴,上例的 shape(2, 3, 5),分别对应 axis=0axis=1axis=2

3-5-2 多维阵列索引

多维阵列可以连续使用中括号索引,也可以使用逗号分隔各维索引。

ch3_13.py:二维阵列索引
import numpy as np x4 = np.array([[0, 1, 2, 3, 4], [5, 6, 7, 8, 9], [10, 11, 12, 13, 14]]) print(x4[2][1]) print(x4[1][3])
执行结果
11
8
ch3_13_1.py:二维阵列逗号索引
import numpy as np x4 = np.array([[0, 1, 2, 3, 4], [5, 6, 7, 8, 9], [10, 11, 12, 13, 14]]) print(x4[2, 1]) print(x4[1, 3])
执行结果
11
8
ch3_14.py:三维阵列索引
import numpy as np x5 = np.array([[[0, 1, 2, 3, 4], [5, 6, 7, 8, 9], [10, 11, 12, 13, 14]], [[0, 1, 2, 3, 4], [5, 6, 7, 8, 9], [10, 11, 12, 13, 14]]]) print(x5[0][2][1]) print(x5[0][1][3]) print(x5[1][0][1]) print(x5[1][1][4])
执行结果
11
8
1
9
ch3_14_1.py:三维阵列逗号索引
import numpy as np x5 = np.array([[[0, 1, 2, 3, 4], [5, 6, 7, 8, 9], [10, 11, 12, 13, 14]], [[0, 1, 2, 3, 4], [5, 6, 7, 8, 9], [10, 11, 12, 13, 14]]]) print(x5[0, 2, 1]) print(x5[0, 1, 3]) print(x5[1, 0, 1]) print(x5[1, 1, 4])
执行结果
11
8
1
9

3-5-3 多维阵列切片

多维阵列切片时,可以对每一个维度分别指定切片范围。不同维度的切片条件用逗号分隔。

ch3_15.py:二维阵列切片
import numpy as np x = np.array([[0, 1, 2, 3, 4], [5, 6, 7, 8, 9], [10, 11, 12, 13, 14]]) print("x[:2, :] =") print(x[:2, :]) print("x[2, :4] =") print(x[2, :4]) print("x[:2, :1] =") print(x[:2, :1]) print("x[:, 4:] =") print(x[:, 4:]) print("x[:, 4] =") print(x[:, 4])
执行结果
x[:2, :] =
[[0 1 2 3 4]
 [5 6 7 8 9]]
x[2, :4] =
[10 11 12 13]
x[:2, :1] =
[[0]
 [5]]
x[:, 4:] =
[[ 4]
 [ 9]
 [14]]
x[:, 4] =
[ 4  9 14]

x[:, 4:] 取出的仍是二维阵列;x[:, 4] 取出的是一维阵列。是否保留维度会影响后续影像处理时的资料形状。

ch3_16.py:连续中括号切片的差异
import numpy as np x = np.array([[0, 1, 2, 3, 4], [5, 6, 7, 8, 9], [10, 11, 12, 13, 14]]) print("x[:2, 4] =") print(x[:2, 4]) print("x[:2][4] =") print(x[:2][4])
执行结果
x[:2, 4] =
[4 9]
x[:2][4] =
IndexError: index 4 is out of bounds for axis 0 with size 2

x[:2][4] 会先执行 x[:2],此时结果只有 2 列,再取第 4 列就会发生索引错误。

3-6

阵列水平与垂直合并

3-6-1 使用 vstack() 垂直合并阵列

vstack() 可以将多个阵列依垂直方向合并。在影像处理中,可用于将影像上下拼接。

语法
x = np.vstack(tup)
ch3_17.py:垂直合并阵列
import numpy as np x1 = np.array([[1, 2, 3], [4, 5, 6]]) x2 = np.array([[7, 8, 9], [10, 11, 12]]) x = np.vstack((x1, x2)) print(x)
ch3_17.py 执行结果
ch3_17.py 执行结果

3-6-2 使用 hstack() 水平合并阵列

hstack() 可以将多个阵列依水平方向合并。在影像处理中,可用于将影像左右拼接。

语法
x = np.hstack(tup)
ch3_18.py:水平合并阵列
import numpy as np x1 = np.array([[1, 2, 3], [4, 5, 6]]) x2 = np.array([[7, 8, 9], [10, 11, 12]]) x = np.hstack((x1, x2)) print(x)
ch3_18.py 执行结果
ch3_18.py 执行结果
习题

1. 使用 huang.jpg 影像,将影像水平合并,最后输出合并结果。

import cv2 import numpy as np img = cv2.imread("huang.jpg") dst = np.hstack((img, img)) cv2.imshow("Dst", dst) cv2.waitKey(0) cv2.destroyAllWindows()
习题 1 参考结果
习题 1 参考结果

2. 使用 flower1.jpg 影像,将影像合并成 4 张排列的结果。

import cv2 import numpy as np img = cv2.imread("flower1.jpg") row = np.hstack((img, img)) dst = np.vstack((row, row)) cv2.imshow("Dst", dst) cv2.waitKey(0) cv2.destroyAllWindows()
习题 2 参考结果
习题 2 参考结果