如何使用Python生成罗马风格的马赛克(代码实现)

使用所提供的Python代码制作你自己的马赛克,并理解其背后的基本原理。

古代的马赛克是由大理石、玻璃或石灰石等坚硬的小块材料组装而成的。这种材料的选择产生了极其持久的艺术作品,让我们了解了数千年前的生活方式。但是马赛克也代表了现代概念。首先,它们是可持续的,因为它们是自然的、可重复使用的构建块。它们具有高度的抽象,这使它们看起来很有趣。这种效果是自动产生的,因为马赛克中的元素数量是严格限制的。尽管有这个限制,物体应该是清晰可辨认的。这是创建一个新的马赛克的挑战,并要求遵守规则时,放置瓷砖。
可用的算法当我搜索从输入图像生成马赛克的工具时,我没有找到一个让我信服的。因此,我开始尝试神经风格转换,结果是惊人的马赛克,但仔细看,发现很多不现实的瓷砖。后来我发现有几篇关于马赛克计算的科学论文,我注意到一篇更早的论文(2005年的)是由Di Blasi等人写的,它产生了美丽的马赛克。我决定采用已发布的方法,并用Python实现它。事实证明,这比预期的要困难,因为这项任务出奇的复杂。此外,我必须即兴发挥很多,因为算法的最后步骤没有详细解释。现在我想把剧本分享给所有对此感兴趣的人。一些相关的代码行张贴在这里和完整的代码可以在GitHub上( 作者:Johannes Beetz )。
让我们开始首先我们加载一个源图像:
from skimage import data import matplotlib.pyplot as plt img0 = data.coffee() plt.imshow(img0)

图片如下图所示。现在我们可以开始实现算法了。
边缘检测最著名的马赛克类型(opus vermiculatum)的基本设计原则是沿着重要视觉对象的边缘放置瓷砖链。因此,我们的首要任务是从源图像中提取这些边缘特征。本文指出,传统的边缘检测算法不能很好地工作,必须开发一种基于亮度级别的自定义技术。这是在2005年写的。如今,我们可以从深度学习领域的进步中受益。整体嵌套边缘检测(HED)是在Di Blasi十年后发表的,可以使用openCV轻松实现。事实证明,在大多数情况下,HED在检测相关轮廓方面做得更好。你可以在下面的对比中看到,HED产生的背景特征明显较少,但能更好地保留杯子的边缘。
源图像是测试图像” 咖啡” 从scikit图像(由Rachel Michetti)。边缘检测图像的作者。
Guidelines现在我们有了边,可以开始在那里放置贴图了。但之后该去哪里呢?让我们准备使用贴图填充整个图像的说明。因此,我们构造了一套平行于检测到的边缘的完整的Guidelines。为此,我们首先必须计算每个像素到最近边缘的距离。幸运的是,SciPy有正确的功能:
from scipy.ndimage import morphology distances = morphology.distance_transform_edt(img_edges==0,)

在img_edges中,已经检测到的边缘被编码为” 1 “ ,并通过设置img_edges==0将其转换为” False “ 。你可以在下面的图中看到生成的图像。在这里,像素越亮,离边缘越远。
现在我们可以使用关于边缘距离的新知识了:我们移动half_tile(即一个砖块的一半大小; (由用户选择)远离边缘,并一行一行地绘制Guidelines,同时始终保留瓷砖链所需要的空间:
guidelines = np.zeros((w, h), dtype=np.uint8) guideline_mask = ( (distances.astype(int)+half_tile)%(2*half_tile)==0 ) guidelines[mask] = 1

结果如下所示。最终,瓷砖将沿着这些线放置在中心。然后,我们再次使用距离矩阵来计算每个像素的梯度。稍后我们将需要这些数据以正确的方向放置贴图。
图片作者(基于scikit-image测试图片” coffee” )
为了使Guidelines对以下步骤有用,我们必须将它们转换为有序的坐标列表。这不是一个容易的任务,但谢天谢地SciPy在这里再次提供了很大的帮助:
from scipy.ndimage import label guidelines_labeled, chain_count = label(guidelines, structure=[[1,1,1], [1,1,1], [1,1,1]])

瓷砖位置现在我们可以开始沿着Guidelines绘制多边形来实际放置贴图。有了shaely,有一个很棒的python库,用于创建和操作几何形状。
标准瓷砖的放置过程如下:
  • 将边1创建为线长2任一端; 以指南居中(下图中的虚线)
  • 根据预先计算的梯度矩阵旋转边1
  • 前进方向为2手性half_tile像素
  • 以与边1相同的方式创建边2
  • 通过取四个角点(即两边的两个端点)的凸包创建一个多边形
  • 如果新瓦与附近链上的瓦重叠,那么只保留差值
如何使用Python生成罗马风格的马赛克(代码实现)

文章图片
如果一个瓷砖是沿着一个弯曲的指南放置的,它的宽度选择较小,以便在最后的马赛克中的圆形对象看起来更好。如上图所示,边2在超过临界角时立即绘制。最后,指导链的最后一个贴图(通常是一条闭合的线)被安装到剩余的空间中,因此可能会有更小的宽度。
所有这些都是在循环中完成的,需要大量的代码行。为了演示shashape背后的简单原理,让我们画一个单一的多边形:
from shapely.geometry import LineString, MultiPoint from shapely import affinityhalf_tile = 10x0 = 0 y0 = 0 angle0 = 0 line0 = LineString([(x0,y0-half_tile),(x0,y0+half_tile)]) line0 = affinity.rotate(line0, angle0)x1 = 10 y1 = 3 angle1 = 15 line1 = LineString([(x1,y1-half_tile),(x1,y1+half_tile)]) line1 = affinity.rotate(line1, angle1)p = MultiPoint([line0.coords[0], line0.coords[1], line1.coords[0], line1.coords[1]]) p = p.convex_hull

如何使用Python生成罗马风格的马赛克(代码实现)

文章图片
在这个例子中,line0创建在左边,line1创建在右边。两条线都旋转了指定的角度。最后提取两条直线的角点并合并为凸多边形。
现在让我们继续放置循环。在下图(左边)中,你可以看到第一个链完成后的结果。标记的链也用彩色线表示。在右边的图像中,所有链都填充了贴图。但我们还没有完成,因为我们有很多瓷砖之间的间隙。
图片作者(基于scikit-image测试图片” coffee” )
瓷砖填补空白【如何使用Python生成罗马风格的马赛克(代码实现)】为了填补空白,我们使用了另一个伟大的Python库scikit-image。为了找到缺口,我们首先绘制所有现有的瓷砖:
from skimage import draw img_chains = np.zeros((h, w), dtype=np.uint8) rr,cc = draw.polygon(x, y, shape=img_chains.shape) img_chains[rr, cc] = 1

在这里,x和y是一个多边形的角坐标。” 绘制” 将它们转换为多边形内所有像素的坐标rr,cc。你可以在下面的左图中看到结果。然后我们再计算到现有贴图的距离。然后,我们使用这些信息在间隙内定义第二组Guidelines(右图)。
图片作者(基于scikit-image测试图片” coffee” )
在沿着新的Guidelines放置额外的贴图后,所有必要的多边形都创建好了。另一项任务是切断粘在图像边界外的贴图。然后,让我们看看结果。在下图中,你可以看到整个区域都覆盖着瓷砖:
如何使用Python生成罗马风格的马赛克(代码实现)

文章图片
图片作者(基于scikit-image测试图片” coffee” )
处理凹瓷砖如果不仔细观察就很难看到它,但关于贴图形状有一点还不令人满意:它有很多凹多边形!如果两个角点之间的一条线可以延伸到多边形外,则该多边形为凹形。当它与邻近多边形的重叠被移除时,它可以在放置后形成。这些形状不是很自然,因为在现实中,石头通常不是凹的,而是凸的。这就是为什么代码提供了将形状转换为凸形的选项。它实现了两种策略(见下面的草图):
  • 移除尖刺: 如果多边形的面积没有明显改变,那么多边形的尖刺部分就会被移除。去除穗后的面积必须比以前小。否则会有与其他贴图重叠的风险。
  • 这是右图上的例子。如果标记的角被移除,得到的多边形将比以前大。相反,它被分成两个凸多边形。
方便的是,这两种策略都可以使用有形状的例程来实现。
如何使用Python生成罗马风格的马赛克(代码实现)

文章图片
着色最后,所有的贴图都就位了,并且呈现出了一个凸起的形状。只是颜色少了!我们只是从输入图像中复制它。最简单的方法是选择位于多边形中心的颜色值。因为在某些情况下可以收集到奇怪的像素颜色,所以也可以在一个贴图内的所有像素上平均颜色。你得到的结果如下图所示。
一个缺点是,由此产生的马赛克看起来有点人工,因为不是所有的颜色发生在现代照片可以复制的自然材料碎片。因此,在真正的历史马赛克中可以找到的颜色集合是事先收集的。你可以选择将所有颜色值更改为与该源最接近的颜色值。在下面的例子中(右侧),我们选择了一个来自拉文纳的马赛克源,由于使用了玻璃碎片,它仍然包含了很多颜色。
图片作者(基于scikit-image测试图片” coffee” )
完成了!让我们看看一些生成的马赛克。
例如,我们可以尝试不同大小的瓷砖的效果。在左边的图像设置为15px,在右边设置为5px。较粗的图像需要1107个贴图,较细的需要7069个贴图。
图片作者(基于scikit-image测试图片” coffee” )
当然,我们可以选择任何一种输入图像:
作品来源: 维米尔的《戴珍珠耳环的女孩》,塞巴斯蒂安·比茨的教堂照片,作者的《花》和《狗》
如果你想自己尝试一下,可以从这里获取代码。只需更改输入图像的路径并运行脚本。

    推荐阅读