[左边]Agent作画[右边]来源:Jack Moreh
自动化无用的任务是有趣的
没有什么比让机器人做无用的事情更好的了,在艺术方面,我感到无力,因此我决定发挥特长,并训练一名Agent,用MS paint为我手绘任何图片。
Agent绘制不带颜色的马卡龙图片(32倍速)
但是说实话,我可以轻松地编辑图像,混合添加一些颜料颜色,然后创建了与第一张看到的相同的绘画风格图像。
But that’s boring...
是的,在MS paint中绘制是很乏味,耗时且相当麻烦。但是这表明人工智能、机器学习和最优化的基础知识可以使我们做出令人惊奇的事情。在这里,我将概述如何设计该Agent以绘制任何图片。
. . . 处理图像 内容摘要: 使用OpenCV应用一个可选的高斯(高斯函数在学术领域运用的非常广泛。写工程产品时,经常用它来除去图片或者视频的噪音,平滑图片,Blur处理)滤波器(Gaussian filter),使用Canny边缘探测提取线条,并使用二进制阈值binary thresholding使边缘变黑。有超参数(hyperparameters)控制绘图的相对大小及其细节。 OpenCV Gaussian filter: https://docs.opencv.org/master/d4/d13/tutorial_py_filtering.html OpenCV Canny: https://docs.opencv.org/master/da/d22/tutorial_py_canny.html OpenCV Thresholding: https://docs.opencv.org/master/d7/d4d/tutorial_py_thresholding.html 首先,我们需要从图像中提取边缘。这样,Agent就知道要绘制哪些线的梗概。使用OpenCV,我们应用高斯滤波器,并使用Canny Edge Detection提取大部分要绘制的相关线。然后,在使用反二进制阈值(inverse binary thresholding)使边缘变黑之后,我们可以提取所需轮廓对于绘制程序! [左边]原始图像来源:BelleDeese [右边]处理后的图像 我提供了混合匹配高斯模糊、内核大小(ksize)和 Canny边缘检测阈值三个选项。通常,增加高斯模糊与降低阈值是成对的,反之亦然。我们如何决定使用哪个选项?答:这取决于图像的细节。有些图片有太多无法辨认的线条(比如狮子的鬓毛)。在这种情况下,最好使用更多模糊处理(blur),因为我们忽略了细线而注意到了更粗的线。另一方面,一些图片可能自然的有足够的,可识别的细节使得我们使用更少的模糊处理。
细线在没有过滤的情况下变得无法辨认
还有两个超参数需要调优,变量“scale”决定了我们想要用于绘制的屏幕部分。变量“detail”决定了我们希望从原始图像中保留多少细节。
在将照片适当地放入我们的专用部分之后,我们使用detail参数将照片缩小为一个假定伪尺寸的图像。这是我们规划草图路径(程序将在这些线中顺序绘制)的基本尺寸,另外,我们使用变量startX和startY来定义我们将要绘制的原点。这有助于我们定义translate方法,以便从物理图中抽象我们的实现。
但是我们要怎么去画呢?
内容摘要: 使用一个名为PythonGui的超级方便的鼠标光标控制库。构造一个由要拖动铅笔的像素组成的事件列表,何时提起铅笔,何时推动铅笔。
好了,在我们开始规划草图路径之前,我们需要回顾一下我们是如何在MS Paint上把代码转换成绘画。我们可以使用一个名为PythonGui的库来控制鼠标事件。首先,我们构造一个命令列表(下一节中讨论),其中包含要将光标拖动的点,何时释放左键以及何时按住左键单击。然后,我们处理每一个命令,看着它创造出奇迹!
Agent画一个海滩上的城堡
下面是将命令列表转换为鼠标时间的简单代码:
规划草图路径
内容摘要: 将所有边缘像素插入到KD-Tree和Hashset中。我使用了一个简单的算法来规划路径:从任何边缘像素开始。第二步,将铅笔拖到未访问的、相邻的像素上,偏好保持Agent的当前方向。重复步骤二,直到没有未访问的相邻像素。使用k近邻(K-Nearest)来找到最近的未绘制边缘像素并重复,直到图像完全绘制完毕。还对执行速度进行了其他优化。
是的,我可以轻松地拍下这张照片,然后就结束了,但这并不有趣。相反,我们将使用监督学习和一个简单的算法来高效地为Agent找到一条绘制的路线。
来自unsplash上niko的照片(非Agent绘制)
KD Tree 和 K-Nearest Neighbors
我们需要一种方法来跟踪我们的边缘像素使之有效。我们可以把所有的边缘像素坐标粘贴到哈希集(HashSet)和KD Tree中。这些将跟踪未绘制的边缘像素。这意味着一旦我们绘制完一个边缘像素,就会从两个数据结构中删除它。
现在,我们知道如何跟踪边缘像素,但我们如何确定绘制它们的顺序呢?
Momentum 和 Continuity
当我们人类画画时,会试图保持方向性。也就是说,我们用流畅、连续的笔触来描绘事物,而不是跳跃的或不规则的描绘。所以,我们使用动量(Momenturm)的概念。
我们可以将命令看作元组列表,每个元组告诉我们将光标从哪里拖到哪里。当我们的机器人规划它的路径时,从一个特定的方向进入一个像素后,我们通过选择与前一个方向最相似的未绘制边缘像素来决定我们要移动的下一个像素。换句话说,我们观察每个像素的八个相邻像素,过滤掉那些不是未绘制的边缘像素,然后选择一个最大程度保留方向性的像素。如果没有相邻的,使用k个最近的邻居从一个新的位置开始。
用计划好的路径绘制日落海滩(64倍速 )
K-Nearest Neighbors 和 K-D Trees
K-Nearest Neighbors首先不要与K-means clustering混淆!
在统计学中,k-nearest neighbors algorithm (k-NN) (k-NN)是Thomas Cover提出的一种用于分类和回归的非参数方法。在这两种情况下,输入都是由特征空间中k个最接近的训练示例组成。输出取决于k-NN是用于分类还是回归:
在k-NN分类中,输出是一个类成员。一个对象是通过其邻近的多数投票来分类的,对象被分配给它的k个最近邻居中最常见的类(k是一个正整数,通常很小)。如果k = 1,那么该对象被简单地分配给那个最近邻的类。
在k-NN回归中,输出为对象的属性值。这个值是k个最近邻值的平均值。
k-NN是一种基于实例的学习,或称为惰性学习,其中函数只在局部逼近,所有计算都延迟到函数求值。由于该算法依赖距离进行分类,对训练数据进行归一化可以显著提高算法的准确率
对于分类和回归来说,一个有用的手法是对邻域的贡献赋值,以便较近的邻域对平均值的贡献大于较远的邻域。例如,一个常用的加权方案是给每个近邻一个1/d的权重,其中d是到邻居的距离
邻居取自一组已知类(k-NN分类)或已知对象属性值(k-NN回归)的对象。这可以被认为是算法的训练集,尽管不需要明确的训练步骤。
k-NN算法的一个特点是对数据的局部结构非常敏感。
K-D Trees
在计算机科学中,k-d Tree(k-dimensional tree缩写)是一种空间划分数据结构,用于组织k维空间中的点。k-d Tree在一些应用中是一种有用的数据结构,例如涉及多维关键词的搜索(例如范围搜索和最近邻居搜索)。k-d Tree是二叉空间划分树的一种特殊情况。
现在,我们可以把这个算法总结为以下几个步骤:
1、选择任意的起点和方向
2、如果有相邻的未绘制边缘像素,将铅笔拖到最大程度保留方向性的像素上。然后,重复
3、如果没有,使用K-Nearest Neighbors移动到一些新的未绘制的边缘像素。连环操作步骤2,直到所有像素被绘制
放入代码中之后:
在第39行,您将注意到有一个轻微的优化。这里告诉Agent:如果您的移动方向与前一步完全相同,请将两个命令合并为一个。这样,我们就可以用一个笔画代替
执行时间
hashset使用[amortized](摊销分析)常数时间进行查找和删除。K-D Tree对每次查询取 O(D*Log(N))。如果用N表示图像中像素的总数,我们可以通过观察算法的各个部分来找到运行时:插入到HashSet和 K-DTree : O(N) + O(2*N*Log(N))
总像素查找和 K-NN查询 : O(N(constant + 2*Log(N))
删除总数 : O(N(constant + 2*Log(N)))
通过将所有的部分加在一起,我们得到一个总的,amortized运行时O(N*Log(N))来确定绘图路径。当然,画图的物理动作要比计算时间长得多,但是我们不用担心这个!
上色
内容摘要: 将图像上的每个点解释为表示BGR值的三维向量。使用K-means聚类,将点集分成K组,每组对应一种颜色。然后,使用上面的草图算法来绘制每一种颜色。
有一个选项供我们添加颜色到我们的图像。在这里,我们将介绍Agent在可以选择的颜色数量有限的情况下如何确定使用哪些颜色。
K-Means Clustering 首先不要与K-Nearest Neighbors混淆!K-Means Clustering是一种矢量量化方法,源于信号处理,目的是将n个观测数据划分为k个聚类,其中每个观测数据都属于均值最近的聚类(聚类中心或聚类质心),作为聚类的原型。这导致将数据空间划分为沃罗诺伊图(Voronoi)单元格。k-means clustering最小化簇内方差(平方欧几里得距离),但不是正则欧几里得距离,这将是更困难的韦伯问题:均值优化平方误差,而只有几何中值最小化欧几里得距离。例如,使用k-medians 和k-medoids可以找到更好的欧几里得解。
这个问题是计算困难的(NP-hard);然而,有效的启发式算法迅速收敛到局部最优。这些通常类似于k-means和高斯混合建模所采用的迭代求精方法对高斯分布的混合物的期望最大化算法。它们都使用集群中心来建模数据;然而,k-means clustering倾向于找到空间范围可比的聚类,而期望最大化机制允许聚类具有不同的形状。
该算法与k-nearest neighbor分类器有松散的关系,k-最近邻分类器是一种流行的机器学习技术,由于名称的关系,经常与k-means混淆。将1-最近邻分类器应用于k-means得到的聚类中心,将新数据分类到已有的聚类中。这被称为最接近质心分类器或Rocchio算法。
在此上下文中,我们应用K-Means Clustering来确定我们的Agent在填充图片时将使用哪些颜色。我们可以把图像上的每个点看作一个表示其BGR值的三维向量。
注意:OpenCV把所有东西都放在BGR值中,而不是RGB中
我们可以使用K-Means Clustering将这些像素聚为K个群集,每个群集对应于Agent被限制使用的K种颜色中的一种。这个变量K在代码中是可调的。
慢慢填充颜色
在MS paint上自动选择颜色。因此,对于每个群集,程序提示用户更改MS Paint上的笔的颜色。提示颜色的BGR值等于相关簇的平均值。
然后,我们将这个簇中的所有像素都传递到相同的素描路径算法,只是这次使用了不同的颜色。然后对每个群集重复。
此外,如果我们想在填充完所有颜色后添加额外的轮廓,我们可以将其作为一个超参数来决定。就我的品味而言,我宁愿没有轮廓,但我有什么资格去评判呢?
[左]没有轮廓 [右]有轮廓
结果
内容摘要: 是的,结果看起来很不错!
那么,这个程序可以绘制什么样的图片呢? 没错,几乎可以绘制任何东西。当然,当图片没有明确的边缘或有点模糊时,它有时不能画出适当的轮廓,但色彩看起来很奇妙。请看我们画的几幅画
[左]原版画 [右]Agent的画
[左]Agent的画 [右]原版画
[左]Agent的画 [右]原版画
结论
我们成功地创造了一个自动模仿的艺术家!这表明,即使是最基本的机器学习算法也能让我们走多远! 当然,在人工智能中使用了无数个子领域、方面和技术。但有时,无论任务多么无用,都需要创造力和一点体力劳动才能看到结果。