本文基于CSDN Python学习班微分享内容整理,主要讲解了如何通过Python程序实现Mosaic Photo效果。主讲人陈舸,8年开发经验,曾就职华为、烽火通信,目前创业中。《Python Cookbook第三版》译者,《Linux/Unix系统编程手册 下卷》以及《算法精解 C语言描述》合作译者。点击这里下载程序源码
肯定有人会问什么是Mosaic Photo,那我们先来看一看最终的效果吧。
上图是在google上搜索的mosaic风格的图片。这种风格的图片都是由其它多张小图拼接而来的,最终形成了一张大图。我们可以在一些广告和海报上找到这种风格的设计。今天分享一个用Python写的程序,帮我们实现这种效果。
我们选了一张《越狱》的海报作为目标图片。最后我们会用多张图片拼接实现一张mosaic风格的海报,如下图所示。
我们可以在网上随机搜索100张图片作为拼接的素材。这些素材图不求高像素,但求随机。我们这次下载的素材图最大也不过几十KB。
首先,我们需要将目标图片分解为大小相等的block。同时,我们将素材图批量剪裁成与block大小相等的方形小图,在这里我们称之为tile。我们只要选取“合适”的tile像贴墙砖一样贴到block上,就可以得到最终的Mosaic海报了。
过程听起来很简单,但其中有两个核心问题:
1. 什么叫“合适”?
2. 怎么确定哪个tile贴到哪个block上?
先解决第一个问题。所谓“合适”,就是指我们选取的tile和要被贴的block在颜色上看起来最接近。目标图被划分成无数个block之后,有的区域颜色比较明亮,有的则偏暗,而我们手里的tile素材图更是五花八门,需要有一种方法能在众多的tile里选择出一个颜色与对应block最相近的,那这个“合适”与否也就可以确定了。
在对比颜色之前,我们先简单说明一下像素相关知识。
像素pixel,可简单看做(R,G,B)3维向量。因此要比对出颜色最接近的tile,其实就是去比对像素的差异。两个像素间的差异就是3维向量之间的距离,距离越小表示越接近也就越相似。那我们怎么求这个距离?
一维的求解:sqrt((x-y)^2) == | x – y|
二维的求解:sqrt((x1-x2)^2 + (y1-y2)^2)
三维的求解:sqrt((x1-x2)^2 + (y1-y2)^2 + (z1-z2)^2)
以此类推,我们可以将其扩展至n维。我们可以采用此方法,针对每个block,找到整体像素差异最小的那个tile,即为“合适”。
第二个问题,怎么知道哪个tile要贴到哪个block上?
当我们把原图划分成block后,其实每个block就有了一个坐标,用来确定这个block在目标图中的位置。而我们贴图用的资源tiles,可以全部读取到python的list中去,因此tile就有了下标索引。有了坐标,有了索引,就可以建立tile与block的对应关系。
到这里,我们已经解决了两个核心的问题。至于如何贴图、缩放、改变图片大小,我们就可以交给PIL库帮助我们解决。
大家在PIL库的网站可下载适用于Python 2.x 的PIL库,Python 3则可使用Pillow。
解题思路已经敲定,开始coding!
从上述代码可以看到,首先导入我们用的库,然后定义一些全局变量。
TILE_SIZE = 3
由于我们划分的block是 3x3 的大小,因此tile的大小也是 3x3 。
ENLARGEMENT = 1
第7行代码表示我们对目标图的放大倍数是1。如果设置成其他值,比如2,那么 300x200 的目标图,最后产生的结果将是 600x400。
EOQ_VALUE = None
这个变量用来通知工作进程结束,我们在后面会详解。
下面开始编写class代码。
TileProcessor类就是用来处理我们的贴图素材tile的。init方法是接受传入的图片文件夹的路径。用get_tiles遍历目录中所有的图片,做一下裁剪后放入list。具体裁剪成方形的代码在22-27行。对于非正方形的tile,我们裁剪出正中心的方形区域作为备选的tile。
接下来我们看看TargetImage类,如下图代码所示。
TargetImage类用来处理我们的目标图,代码应该很好理解。在64行有一个if判断。因为我不希望贴图的时候出现最后还差半个或非整数个block,因此如果有这种情况,那我把原图也稍微做下裁剪,使得每行和每列要贴的block都是整数个。
如上图所示,在TileFilter这个类中,我们来确定哪个tile是“合适”的。其中,第84行的diff累加,就是之前我们说的计算3维向量的距离。get_best_fit_tile方法会返回最合适的那个tile所在的下标。此前,我们把所有tiles都裁剪为方形后存在list中。
如上图所示,MosaicImage类就是我们用来贴tile的类。初始化方法里我们根据目标图,按比例先生成一张底图,tile就是贴在这个底图之上。
除此之外,还有帮助类ProgressCounter,告诉我们贴图进度。在这里我们不赘述,大家可以下载源码查看,注释得很清楚。
接下来,我们还需要一些函数,将这些类串联起来。首先,我们来看compose函数。它非常关键,当我们拿到目标图的数据和tiles的数据后,就是调用它来完成后面的工作的,也就是合成拼图。
这里我们用了多进程,加速处理最佳tile匹配。可以看到,compose函数里起了几个新进程,有的用来处理tile匹配,有的用来贴图,通过队列queue来传递数据。因此,我们还写了一个 build_mosaic 函数(如下图所示),这个就是贴图进程所调用的函数。而最佳tile匹配就由 fit_tiles 函数(如下图所示)来完成。
最后,我们写一个main,让程序跑起来,我们看一下最终实现的效果。
希望加入Python 学习班?