r vector 4 elements_智能设计建模&仿真(4):编码设计

5d3f55d907b1d850056689454013508f.png

智能设计-建模&仿真(4):

交互式遗传算法(IGA)的编码设计

刘肖健

浙江工业大学

这次要做一个智能算法的案例:交互式遗传算法(Interactive Genetic Algorithms,IGA)。

先做最基础的一步:编码设计。

“编码设计”的意思是:用一系列数字变量来表达我们的设计方案。编码设计主要是为了方便程序处理。算法处理数字变量最得心应手,所以得把设计方案表达为数字变量。

创意之代码曾发过几篇与编码有关的文章:

图像编码:面向色彩意象的设计再现 编码设计:产品的参数化定义 《营造法原》瓦花墙洞纹样设计

有关交互式遗传算法,创意之代码发布过一个完整的色彩优化案例《配色设计:交互式遗传算法源代码》。该案例是对一个固定的设计方案的色彩进行优化调整,比较粗糙,但是具备了IGA的基本功能。这次做个稍复杂的新案例,里面有一些比色彩更多的设计要素需要考虑。

1.      案例描述

1)写一个Function:WallPaperMaker(),用来画一幅壁纸图案。

比如这种的:

d621fccc42cc08fb1523235887291304.png

上图来自创意之代码的文章《共享插件028:随机四方连续》。

生成的图案可以有一定的复杂度,但程序不要太复杂。比如上面这个四方连续的做法,就是从隔壁页面里随便抓几个shape,复制、旋转、缩放、上色,然后位置随机摆放一下就完了。隔壁页面是个库,里面存放着各种树叶子供复制粘帖:

f9a745382c98c3c115d106028a02afa5.png

各种方式都可以,根据自己的喜好来。

2)给这个Function定义参数。

比如上面这个四方连续Function,它的参数包括单元里的树叶数量、种类、色彩、大小比例、分布等。即使是随机数,也会有一个随机范围(不一定非要使用随机数不可)。

用这些参数组成一个数组,这个数组就是生成的四方连续的“编码”。

3)编写基于IGA的交互式优化壁纸设计软件。

仿照《配色设计:交互式遗传算法源代码》的案例开发,只是把色彩编码换成壁纸编码。

2.      编码设计

在动手编写执行函数(就是实际做设计的函数)WallPaperMaker之前,先规划一下最终要做的作品,最终梳理出WallPaperMaker函数需要输入哪些参数。

这个过程即“编码设计”,WallPaperMaker()的参数序列即遗传算法中的个体编码,一个确定下来的参数序列在程序中就代表一个设计方案,这是程序用来识别设计方案的方法。

这里只做一个单元而不是整幅四方连续。

参数序列大致有如下几种:

1)叶子类型

在给定的n种叶子元素里选择,需要区别出每一种叶子的选中概率,因为有的用户可能偏爱某一种类型的叶子。可以用一个数组来表示概率密度,比如总共有4种叶子,则数组Array(1,1,2,4)表示四种叶子的选中概率分别为1/8、1/8、2/8、4/8。

2)叶子数量

这个简单,给个整数就行了。如果不是整数,就先圆成整数。

3)叶子尺寸

设置叶子(不论哪个)的最大与最小尺寸,可以采用用页宽的比值,如数组Array(0.05,0.15)表示叶子的尺寸在[10.5,31.5]范围内随机变化(页面宽210mm)。叶子的角度在360内随机旋转,就不用参数了。

4)叶子密度

为了避免叶子的位置太随机失去了设计感,对它们的分布密度做一些有意的规划。

采用最简单的密度分布方式:指定页面中的某个坐标位置为密度中心,即该处密度最大,离得它越远,密度越小。可以用数组Array(x,y)确定密度中心,然后以该点为极坐标圆点,在极坐标空间内产生随机点(随机的极径和极角),就会形成从原点向外密度逐渐减小的分布效果。

5)叶子颜色

简单一点,在某个固定的色彩范围内变化。用HSB色,给出一个固定的色彩值,然后在周围不远处变化

6)背景色

也简单一点:前景的叶子用亮色,背景用暗色。背景也用HSB,调整三个分量很容易搞出各种暗色。

上述6类内容构成了设计方案的编码,这个编码已经有点复杂了。

3.      WallPaperMaker() 3.1.    用程序生成元素

把元素库做在文档里是个很方便的主意,用户可以自己添加喜欢的元素。只是程序拷贝复制是个挺耗时的活儿,这里介绍一个小技巧,可以把元素的生成做到程序里,不使用库,也就是用程序来生成库里的元素。

具体步骤:

1)画一个元素,或copy一个过来也行;

bce0414759e30d0bc9825c04505afc3e.png

2)启动代码录制开关,记录代码;

3)进入节点编辑模式:

0807badbb2fefcfb04dad215897d245d.png

4)随便拖动一个节点,再拖回原位,不要修改节点的位置,只是产生一个拖动的动作,以便代码记录器录下代码;

5)停止记录,打开记录下来的代码,发现录下了长长的一坨代码,是这样的:

7c3720a08dc225425357ee07866682d3.png

代码实在太长,当中用省略号代替了。

观察一下这堆代码,我们发现只拖动修改一个节点的位置,代码记录器则把所有节点的位置重写了一遍,也就是把这条曲线整个重建了一遍。实际上修改某个节点的位置CorelDraw是有这个函数的,但是代码记录器太笨,重写了所有节点的位置。这样反倒给我们提供了一个方便,就是可以中这种方式把创建一个新元素的代码写进函数里,而不用费劲的去一个个敲那些坐标值了。

6)把记录下来的代码改成函数,函数是这样的:

17e186a59afd4baeb1712f5f457f3d9f.png

注意,函数的最后一句跟记录的代码不太一样,因为函数需要创建一个新对象,而不是修改。

函数创建的叶子是在页面中的固定位置,可以对其进行移动、缩放、旋转、上色等后续操作。

这个函数没有参数。

7)同样的元素生成函数多做几个。这个案例做了4个,设计方案就用这四个叶子来构建。

a759bacabfe2bb33865a1246ed5039e2.png

3.2.    初步测试

先编写一个最简单的初步测试程序放在一个按钮里,程序如下:

6c7eb56bb1f4ef5fc9442b6b4abb8bbe.png

上面这个程序的执行结果是这样的:

e089c792bcbbe47e784a82bb3532d0f2.png

 

四个叶子选哪个,既要有随机性,还要体现不同的概率密度。前面说过了,用一个数组来指示概率。

数组Array(a,b,c,d)的四个成员变量表示四种叶子被选中的相对概率,它们的绝对概率分别是:

a/(a+b+c+d)

b/(a+b+c+d)

c/(a+b+c+d)

d/(a+b+c+d)

如果只是用轮盘赌算法函数Roulette()来确定选哪个叶子,并不需要算出绝对概率。但是交互式遗传算法在优化过程中需要进行编码的交叉互换,相对概率的尺度不一致,会造成混乱。比如(1,1,1,1)和(10,10,10,10)两个编码实际上是一样的,它们进行交叉的结果应该也是平均分配每个叶子的概率,但是这两个编码交叉的结果可能是(10,1,1,1),第一个叶子的选中概率被虚假放大。

绝对概率的换算可以放在后面的算法流程中处理。

叶子的位置、大小、旋转、色彩暂时都设为随机。

3.3.    编码梳理

上面这个测试用的Sub,最终要修改成一个带参数的函数(Function),令其产生一个完整的设计作品,它的参数序列就是设计作品的编码。

我们先来看看,结合刚才的编码设计,哪些数据可以定义成变量,然后移到Function标题中作为参数。

1)概率密度

P_Elements这个数组可以从外部输入,函数改成这样:

Function MakeWallpaper(P_Elements)

P_Elements定义和赋值那句可以删除了。P_Elements是数组,可以不指定数据类型。

2)叶子的数量

我们是给了一个固定值,20个叶子,因此循环里的数字19可以定义成变量放在Function的参数表里,我们定义它为nLeaves:

Function MakeWallpaper(P_Elements, nLeaves As Integer)

循环中的19也改成nLeaves。

3)叶子的尺寸

定义叶子尺寸的语句是:r = 5 + Rnd * 15

这句涉及两个数字:5和15,即叶子在原尺寸的5倍到20倍(5+15)之间随机变化。我们把两个数字定义为一个数组rScale=Array(min,max):

Function MakeWallpaper(P_Elements, nLeaves As Integer, rScale)

里面的那句改为:

r=rScale(0)+Rnd*(rScale(1)-rScale(0))

注意,rScale给出的不是最小值(5)和增量(15),而是最小值和最大值(20),所以语句的写法要把增量15改成最大最小值的差:rScale(1)-rScale(0)。

r的设置是从大到小均匀的,如果希望大叶子少一点,小叶子多一点,可以给随机数Rnd加指数,变成:

r=rScale(0)+(Rnd)^3*(rScale(1)-rScale(0))

Rnd的指数3可以设计成编码放进参数表,不过本例没有这么做,感觉不是很有必要,这个参数的作用太细微了。

4)叶子密度

这个需要改的稍微多一点。刚才的程序是随机摆放叶子,而我们希望能稍稍体现一下设计感(即使只是一个练习),让叶子的分布有梳有密。我们用最简单的方式,在页面中设置一个“密度中心”,越靠近密度中心叶子的分布越密,反之稀疏。

Function MakeWallpaper(P_Elements, nLeaves As Integer, rScale,cDense)

密度中心是一个坐标位置:cDense=Array(x,y)

叶子位置的计算语句修改为:

x =Rnd * W y =Rnd * H p =math.PointConcentrate(x, y, cDense, 2, W, H) s1.PositionX= p(0) s1.PositionY= p(1)

解释一下上面的代码:先在页面内随机生成一个点坐标(x,y),然后用math.PointConcentrate函数让这个点坐标向密度中心cDense靠拢。

PointConcentrate()函数写在math模块里,程序不长:

Function PointConcentrate(x As Double, y As Double, p, n As Integer, wPage As Double,hPage As Double) Dim px As Double, py As Double If x< p(0) Then     px = p(0) - p(0) * ((p(0) - x) / p(0)) ^ n Else     px = p(0) + (wPage - p(0)) * ((x - p(0)) /(wPage - p(0))) ^ n End If If y< p(1) Then     py = p(1) - p(1) * ((p(1) - y) / p(1)) ^ n Else     py = p(1) + (hPage - p(1)) * ((y - p(1)) /(hPage - p(1))) ^ n End If PointConcentrate= Array(px, py) End Function

这段代码请自己理解。它的作用是让页面(宽wPage、高hPage)内的任意一个点(x,y)向点p点(密度中心)靠拢。但是有个条件:原来就在页面边缘上的点还得保持在边缘上,就是说,如果x=wPage或y=hPage,经过这么一番处理后,它们的值不变。

这个要求是为了保证修改密度后的页面不能出现真空地带,我们把页面的坐标系从均匀网格修改成了不均匀网格,有疏有密,但坐标系中不能出现破洞,坐标系的整体大小也不能发生变化。

参数n的作用是调整密度的大小,n越大,(x,y)向密度中心靠拢的越紧密。实际上n是一个指数,它作用于0~1之间的数时,输出仍是一个0~1之间的数,但n越大,输出的数越靠近0。想想看,0.1的平方是0.01,更靠近0了;但1的平方还是1,所以坐标系中不会出现“真空”,因为定义域和值域都没变。

这里没有把这个指数n列入设计作品的编码。

5)叶子颜色

如果我们需要色彩在某个色彩周围变化,可以直接给出一个色彩数组clrEle:

Function MakeWallpaper(P_Elements, nLeaves As Integer, rScale,cDense, clrEle)

为HSB模式的色彩定义相关变量:

Dim hh As Double, ss As Double, bb As Double

色彩在一个随机范围内变化,这个随机范围我们暂且设置为固定值,30%,暂不将其定义为参数。

“色彩”部分的程序做如下修改:

r =0.3 '变化范围:30%

ss =math.Rnd_AtoB(clrEle(1) * (1 - r), clrEle(1) + (255 - clrEle(1)) * r)

bb =math.Rnd_AtoB(clrEle(2) * (1 - r), clrEle(2) + (255 - clrEle(2)) * r)

hh =math.Rnd_AtoB(clrEle(0) - 360 * (r / 2), clrEle(0) + 360 * (r / 2))

If hh< 0 Then

    hh = hh + 360

ElseIf hh > 360 Then

    hh = hh - 360

End If

s1.Fill.ApplyUniformFill CreateHSBColor(hh, ss, bb)

hh、ss、bb是HSB的三个色彩分量,在前面要定义,它们的值域分别是360、255、255。

这段代码请自行理解。注意,HSB色彩模式中,H的计算方式与另外两个略有不同,因为H的值是个无缝的环状结构,如果它的值小于0或大于360度,要把它圈回去,而不是用算法强行将其限制在0~360之间(另外两个色彩分量可以这么做)。

函数Rnd_AtoB()是产生一个A和B之间的随机数,很简单,只有一句:

Function Rnd_AtoB(A As Double, B As Double) As Double

Rnd_AtoB= A + Rnd * (B - A)

End Function

6)背景色

给出一个确定的色彩数组clrBack:

Function MakeWallpaper(P_Elements, nLeaves As Integer, rScale,cDense, clrEle, clrBack)

7)其他

还有一些边边角角的工作要做。

这个Function的输出应该是一个完整的作品,是一个而不是一堆Shape。所以要把所有图形组合成一个整体,并且把背景边框之外的部分裁掉。都是比较简单的操作,就不详解了。

编码梳理完了,程序比原来稍微长了一些:

60b23f90f6197cfb416ce4b66a38d33a.png

写个按钮Sub简单测试一下:

647a5736e4f444c3650dccd404590780.png

除了背景统一用黑色,叶子最大和最小尺寸比例固定,其他编码都是随机的。

给作品的前景部分增加了20%的透明度(未进入编码),因为大叶子会遮挡后面的小叶子。

看看作品,做了一组12个,这算一个种群:

f329b960ad15b91b350fce901eb4ce17.png

上面12件作品是随机生成的。

一件作品中各叶子元素的明暗(HSB中的B)在一个固定点附近小幅变化,这导致了作品的层次感不强,要么整体亮,要么整体暗。针对这个问题,专门写了一个调整明暗层次的函数AiryPerspective(),我把它叫做“空气透视”,其实对黑背景的图案叫“景深效果”更好一些。

99e141832eb64c0818c7c908ec42aa36.png

上图左下角的是12幅作品的第二幅,由于整体偏暗,所以用空气透视函数调理了一下。

上图的右下、右上、左上三图做了两方面调整:

1)把叶子从大到小调整顺序,大的在前面,小的在后面;就是说,大的会挡住小的;这是为了制造一种近大远小的透视印象;

2)调整各叶子HSB色彩中的B值,即亮度,近的亮,远的暗,这也是一种透视印象。

右下图的B值是从近到远均匀降低。右上图是则是陡降,所以亮的很少,大部分暗。左上图把参数均衡了下,近处亮的大片叶子多了一些,整体的层次感拉开了一点,有一种“水面水下”的视觉落差。

调序和景深参数未进入编码,是在MakeWallpaper()函数外面执行的,作品完成后手工修改。

至此,执行模块开发和设计作品的编码构造完成,共有15个参数,上面的设计方案用了其中10个,另外5个是固定的。

此外还有5个用到的参数本例暂未放入编码,它们是:尺寸分布指数、密度集聚指数、色彩变化范围、景深指数、透明度。如果都考虑进去,编码会有20个。

编码并不是越长越好。如果对某些参数的偏好明确,可以人工确定,比如背景色、画面亮度、元素大小等,就不要跑到算法里来添乱了。

搞定编码后,算法程序的其他内容都跟色彩IGA差不多了。

如果智能设计是一项需要设计师和程序员合作的工作,那么编码设计应该由设计师来完成。编码设计是智能设计的核心,一个优秀的编码方案并不是会写程序就能搞定的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值