今天在对着http://cs231n.github.io/python-numpy-tutorial练习matplotlib显示图片的时候,
img_tinted = img * [1, 0.95, 0.9]
这条语句就出错了,ValueError: operands could not be broadcast together with shapes (200,200,4) (3,)
这是因为我的图片和例子的图片shape不一样,违反了ufunc的广播机制
广播机制如下:
当我们使用ufunc函数对两个数组进行计算时,ufunc函数会对这两个数组的对应元素进行计算,因此它要求这两个数组有相同的大小(shape相同)。如果两个数组的shape不同的话,会进行如下的广播(broadcasting)处理:
- 让所有输入数组都向其中shape最长的数组看齐,shape中不足的部分都通过在前面加1补齐
- 输出数组的shape是输入数组shape的各个轴上的最大值
- 如果输入数组的某个轴和输出数组的对应轴的长度相同或者其长度为1时,这个数组能够用来计算,否则出错
- 当输入数组的某个轴的长度为1时,沿着此轴运算时都用此轴上的第一组值
import numpy as np import matplotlib.pyplot as plt from scipy.misc import imread, imresize img = imread(u'images/猫扑.png') print(img.dtype) print(img.shape) #print(img) img_tinted = img * [1, 0.95, 0.9, 0.8] plt.subplot(1,2,1) plt.imshow(img) plt.subplot(1,2,2) plt.imshow(np.uint8(img_tinted)) plt.show()
上述4条规则理解起来可能比较费劲,让我们来看一个广播的实际例子。
先创建一个二维数组a,其shape为(6,1):
>>> a = np.arange(0, 60, 10).reshape(-1, 1) >>> a array([[ 0], [10], [20], [30], [40], [50]]) >>> a.shape (6, 1)
再创建一维数组b,其shape为(5,):
>>> b = np.arange(0, 5) >>> b array([0, 1, 2, 3, 4]) >>> b.shape (5,)
计算a和b的和,得到一个加法表,它相当于计算a,b中所有元素组的和,得到一个shape为(6,5)的数组:
>>> c = a + b >>> c array([[ 0, 1, 2, 3, 4], [10, 11, 12, 13, 14], [20, 21, 22, 23, 24], [30, 31, 32, 33, 34], [40, 41, 42, 43, 44], [50, 51, 52, 53, 54]]) >>> c.shape (6, 5)
由于a和b的shape长度(也就是ndim属性)不同,根据规则1,需要让b的shape向a对齐,于是将b的shape前面加1,补齐为(1,5)。相当于做了如下计算:
>>> b.shape=1,5 >>> b array([[0, 1, 2, 3, 4]])
这样加法运算的两个输入数组的shape分别为(6,1)和(1,5),根据规则2,输出数组的各个轴的长度为输入数组各个轴上的长度的最大值,可知输出数组的shape为(6,5)。
由于b的第0轴上的长度为1,而a的第0轴上的长度为6,因此为了让它们在第0轴上能够相加,需要将b在第0轴上的长度扩展为6,这相当于:
>>> b = b.repeat(6,axis=0) >>> b array([[0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4]])
由于a的第1轴的长度为1,而b的第一轴长度为5,因此为了让它们在第1轴上能够相加,需要将a在第1轴上的长度扩展为5,这相当于:
>>> a = a.repeat(5, axis=1) >>> a array([[ 0, 0, 0, 0, 0], [10, 10, 10, 10, 10], [20, 20, 20, 20, 20], [30, 30, 30, 30, 30], [40, 40, 40, 40, 40], [50, 50, 50, 50, 50]])
经过上述处理之后,a和b就可以按对应元素进行相加运算了。
当然,numpy在执行a+b运算时,其内部并不会真正将长度为1的轴用repeat函数进行扩展,如果这样做的话就太浪费空间了