Python 性能分析,大型数据算法的核心要素之一是执行某种分治策略,这点对函数式编程和命令式编程都是成立的。
可以通过下面3种方法提高处理速度。
- 使用并行策略实现并发计算,例如使用4核处理器,处理时间大约变为原来的1/4,上面的曼哈顿距离计算可以缩短到8分钟左右。
- 保存中间计算结果以避免重复计算,搞清楚需要计算的相同颜色和不同颜色的数量。
- 使用新算法。
可以将后两种方法结合,比较所有源色彩和目标色彩。与逐个像素的计算方法相比,这种方式预先穷举所有可能的映射关系来避免重复计算。本质上这是将一系列比较运算转换成了在映射对象中进行查表操作。
对一张具体的图片进行预先的、源到目标的穷举映射计算时,首先要整体评估问题规模,本教程随附的代码中包含一张名为“IMG_2705.jpg”的图片。下面的算法初步估算该图片的颜色元组的总规模:
from collections import defaultdict, Counter
palette = defaultdict(list)
for xy, rgb in pixel_iter(img):
palette[rgb].append(xy)
w, h = img.size
print("Total pixels", w * h)
print("Total colors", len(palette))
对所有像素,按照其颜色值进行分组,将相同颜色的像素放入同一个列表中,从计算结果中可以得到如下信息:
- 像素总数为9 980 928,对于一个10MB大小的图片来说,这个数字在意料之中;
- 共有210 303种颜色,计算这些颜色与133种目标颜色之间的欧氏距离,只需27 970 299次计算,大约花费76秒;
- 使用3比特遮罩
0b11100000
时,在总共 2^3x2^3x2^3=512 种可能的颜色集合中,实际用到214种; - 使用4比特遮罩
0b11110000
时,实际用到1150种; - 使用5比特遮罩
0b11111000
时,实际用到5845种; - 使用6比特遮罩
0b11111100
时,在总共 2^6x2^6x2^6=262144 种颜色集合中,实际用到27726种。
这有助于我们理解如何重组数据结构、快速实现颜色适配计算并重建图像,以避免进行十亿级的比较运算。
使用遮罩的作用是保留作用大的比特,去掉作用小的比特,比如有一个红色值为200的颜色,可以使用Python bin()
函数查看其二进制表示。
>>> bin(200)
'0b11001000'
>>> 200 & 0b11100000
192
>>> bin(192)
'0b11000000'
表达式200 & 0b11100000
擦除了颜色的最低5比特,保留了最高3比特,最终结果是大小为192的红色值。
对RGB三元组的遮罩处理如下所示:
masked_color = tuple(map(lambda x: x & 0b11100000, c))
使用 &
运算符选择特定的比特,只保留红、绿、蓝的前3比特代替之前完整的颜色值,基于它们创建Counter
对象后,可知使用遮罩后共产生214个颜色值,不到理论颜色值数量的一半。